JANUS.C

76.9 KB 30e1aa3c850cab53…
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*                                                                          */
/*      ------------         Bit-Bucket Software, Co.                       */
/*      \ 10001101 /         Writers and Distributors of                    */
/*       \ 011110 /          Freely Available<tm> Software.                 */
/*        \ 1011 /                                                          */
/*         ------                                                           */
/*                                                                          */
/*  (C) Copyright 1987-91, Bit Bucket Software Co., a Delaware Corporation. */
/*                                                                          */
/*                                                                          */
/*                  BinkleyTerm Janus revision 0.31, 11- 2-89               */
/*                   Full-duplex WaZOO file transfer protocol               */
/*                                                                          */
/*                                                                          */
/*    For complete  details  of the licensing restrictions, please refer    */
/*    to the License  agreement,  which  is published in its entirety in    */
/*    the MAKEFILE and BT.C, and also contained in the file LICENSE.250.    */
/*                                                                          */
/*    USE  OF THIS FILE IS SUBJECT TO THE  RESTRICTIONS CONTAINED IN THE    */
/*    BINKLEYTERM  LICENSING  AGREEMENT.  IF YOU DO NOT FIND THE TEXT OF    */
/*    THIS  AGREEMENT IN ANY OF THE  AFOREMENTIONED FILES,  OR IF YOU DO    */
/*    NOT HAVE THESE FILES,  YOU  SHOULD  IMMEDIATELY CONTACT BIT BUCKET    */
/*    SOFTWARE CO.  AT ONE OF THE  ADDRESSES  LISTED BELOW.  IN NO EVENT    */
/*    SHOULD YOU  PROCEED TO USE THIS FILE  WITHOUT HAVING  ACCEPTED THE    */
/*    TERMS  OF  THE  BINKLEYTERM  LICENSING  AGREEMENT,  OR  SUCH OTHER    */
/*    AGREEMENT AS YOU ARE ABLE TO REACH WITH BIT BUCKET SOFTWARE, CO.      */
/*                                                                          */
/*                                                                          */
/* You can contact Bit Bucket Software Co. at any one of the following      */
/* addresses:                                                               */
/*                                                                          */
/* Bit Bucket Software Co.        FidoNet  1:104/501, 1:343/491             */
/* P.O. Box 460398                AlterNet 7:491/0                          */
/* Aurora, CO 80046               BBS-Net  86:2030/1                        */
/*                                Internet f491.n343.z1.fidonet.org         */
/*                                                                          */
/* Please feel free to contact us at any time to share your comments about  */
/* our software and/or licensing policies.                                  */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/* Include this file before any other includes or defines! */

#include "includes.h"

#include "janus.h"


/* Private routines */
void getfname (word);
void sendpkt (byte *, int, int);
void sendpkt32 (byte *, int, int);
void txbyte (byte);
long procfname (void);
byte rcvpkt (void);
void rxclose (word);
void endbatch (void);
void j_message (word, char *,...);
void j_status (char *,...);
void j_msgend (word);
int  j_error (char *, char *);
void update_y (void);
void long_set_timer (long *, word);
int  long_time_gone (long *);
int  rcvrawbyte (void);
int  rxbyte (void);
void xfer_summary (char *, char *, long *, int);
void update_status (long *, long *, long, int *, int);
void through (long *, long *);
int  get_filereq (byte);
int  record_reqfile (char *);
int  timeof_reqfile (long);
byte get_reqname (byte);
void mark_done (char *);



/* Private data. I know, proper software design says you shouldn't make data */
/* global unless you really need to.  In this case speed and code size make  */
/* it more important to avoid constantly pushing & popping arguments.        */

static char *GenericError = "!%s";
static char *ReqTmp = "JANUSREQ.TMP";
static char *Rxbuf;       /* Address of packet reception buffer              */
static char *Txfname;     /* Full path of file we're sending                 */
static char *Rxfname;     /* Full path of file we're receiving               */
static byte *Rxbufptr;    /* Current position within packet reception buffer */
static byte *Rxbufmax;    /* Upper bound of packet reception buffer          */
static byte  Do_after;    /* What to do with file being sent when we're done */
static byte  WaitFlag;    /* Tells rcvrawbyte() whether or not to wait       */
static byte  SharedCap;   /* Capability bits both sides have in common       */
static int   Txfile;      /* File handle of file we're sending               */
static int   Rxfile;      /* File handle of file we're receiving             */
static int   ReqRecorded; /* Number of files obtained by this request        */
static word  TimeoutSecs; /* How long to wait for various things             */
static int   Rxblklen;    /* Length of data in last data block packet recvd  */
static int   Next_y;      /* Number of next available line on screen         */
static int   Tx_y;        /* Line number of file transmission status display */
static int   Rx_y;        /* Line number of file reception status display    */
static long  Txlen;       /* Total length of file we're sending              */
static long  Rxlen;       /* Total length of file we're receiving            */
static long  Rxfiletime;  /* Timestamp of file we're receiving               */
static long  Diskavail;   /* Bytes available in upload directory             */
static long  TotalBytes;  /* Total bytes xferred in this session             */
static long  Txsttime;    /* Time at which we started sending current file   */
static long  Rxsttime;    /* Time at which we started receiving current file */


/*****************************************************************************/
/* Super-duper neato-whizbang full-duplex streaming ACKless batch file       */
/* transfer protocol for use in WaZOO mail sessions                          */
/*****************************************************************************/
void Janus (void)
{
   byte  xstate;          /* Current file transmission state                 */
   byte  rstate;          /* Current file reception state                    */
   byte  pkttype;         /* Type of packet last received                    */
   byte  tx_inhibit;      /* Flag to wait and send after done receiving      */
   char *holdname;        /* Name of hold area                               */
   byte  fsent;           /* Did we manage to send anything this session?    */
   byte  sending_req;     /* Are we currently sending requested files?       */
   byte  attempting_req;  /* Are we waiting for the sender to start our req? */
   byte  req_started;     /* Has the sender started servicing our request?   */
   int   txoldeta;        /* Last transmission ETA displayed                 */
   int   rxoldeta;        /* Last reception ETA displayed                    */
   word  blklen;          /* Length of last data block sent                  */
   word  txblklen;        /* Size of data block to try to send this time     */
   word  txblkmax;        /* Max size of data block to send at this speed    */
   word  goodneeded;      /* # good bytes to send before upping txblklen     */
   word  goodbytes;       /* Number of good bytes sent at this block size    */
   word  rpos_count;      /* Number of RPOS packets sent at this position    */
   long  xmit_retry;      /* Time to retransmit lost FNAMEPKT or EOF packet  */
   long  txpos;           /* Current position within file we're sending      */
   long  lasttx;          /* Position within file of last data block we sent */
   long  starttime;       /* Time at which we started this Janus session     */
   long  txstpos;         /* Initial data position of file we're sending     */
   long  rxstpos;         /* Initial data position of file we're receiving   */
   long  txoldpos;        /* Last transmission file position displayed       */
   long  rxoldpos;        /* Last reception file position displayed          */
   long  rpos_retry;      /* Time at which to retry RPOS packet              */
   long  brain_dead;      /* Time at which to give up on other computer      */
   long  rpos_sttime;     /* Time at which we started current RPOS sequence  */
   long  last_rpostime;   /* Timetag of last RPOS which we performed         */
   long  last_blkpos;     /* File position of last out-of-sequence BLKPKT    */
   FILE *reqfile;         /* File handle for .REQ file                       */


   set_prior (3);                               /* Time Critical             */
   XON_DISABLE ();
   if (un_attended && fullscreen)
      {
      clear_filetransfer ();
      sb_show ();
      }
   else
      {
      set_xy (NULL);
      Next_y = locate_y;
      }
   Tx_y = Rx_y = 0;
   SharedCap = 0;
   TotalBytes = 0;
   (void) time ((time_t *) &starttime);

   /*------------------------------------------------------------------------*/
   /* Allocate memory                                                        */
   /*------------------------------------------------------------------------*/
   Rxbuf = (char *) Txbuf + 4096 + 8;
   Txfname = Rxfname = NULL;
   if (((Txfname = malloc (PATHLEN)) == NULL)
       || ((Rxfname = malloc (PATHLEN)) == NULL))
      {
      status_line (MSG_TXT (M_MEM_ERROR));
      mdm_hangup ();
      goto freemem;
      }
   Rxbufmax = (byte *) (Rxbuf + BUFMAX + 8);

   /*------------------------------------------------------------------------*/
   /* Initialize file transmission variables                                 */
   /*------------------------------------------------------------------------*/
   tx_inhibit = FALSE;
   last_rpostime = last_blkpos = lasttx = txstpos = rxstpos = xmit_retry = 0L;
   TimeoutSecs = (unsigned int) (40960 / cur_baud.rate_value);
   if (TimeoutSecs < 30)
      TimeoutSecs = 30;
   long_set_timer (&brain_dead, 120);
   txblkmax = cur_baud.rate_value / 300 * 128;
   if (txblkmax > BUFMAX)
      txblkmax = BUFMAX;
   txblklen = txblkmax;
   goodbytes = goodneeded = 0;
   Txfile = -1;
   sending_req = fsent = FALSE;
   xstate = XSENDFNAME;
   getfname (INITIAL_XFER);

   /*------------------------------------------------------------------------*/
   /* Initialize file reception variables                                    */
   /*------------------------------------------------------------------------*/
   holdname = HoldAreaNameMunge (&called_addr);
   (void) sprintf (Abortlog_name, "%s%s.Z\0", holdname, Hex_Addr_Str (&called_addr));

   if ((Diskavail = zfree (CURRENT.sc_Inbound)) <= 0L)
      Diskavail = 0x7FFFFFF;

   Rxbufptr = NULL;
   rpos_retry = rpos_count = 0;
   attempting_req = req_started = FALSE;
   rstate = RRCVFNAME;

   /*------------------------------------------------------------------------*/
   /* Send and/or receive stuff until we're done with both                   */
   /*------------------------------------------------------------------------*/
   do
      {                                         /* while (xstate || rstate)  */

      /*---------------------------------------------------------------------*/
      /* If nothing useful (i.e. sending or receiving good data block) has   */
      /* happened within the last 2 minutes, give up in disgust              */
      /*---------------------------------------------------------------------*/
      if (long_time_gone (&brain_dead))
         {
         j_status (MSG_TXT (M_OTHER_DIED));     /* "He's dead, Jim." */
         goto giveup;
         }

      /*---------------------------------------------------------------------*/
      /* If we're tired of waiting for an ACK, try again                     */
      /*---------------------------------------------------------------------*/
      if (xmit_retry)
         {
         if (long_time_gone (&xmit_retry))
            {
            j_message (Tx_y, MSG_TXT (M_TIMEOUT));
            xmit_retry = 0L;

            switch (xstate)
               {
               case XRCVFNACK:
                  xstate = XSENDFNAME;
                  break;
               case XRCVFRNAKACK:
                  xstate = XSENDFREQNAK;
                  break;
               case XRCVEOFACK:
                  errno = 0;
                  if (lseek (Txfile, txpos = lasttx, SEEK_SET) == -1L)
                     {
                     (void) j_error (MSG_TXT (M_SEEK_MSG), Txfname);
                     goto giveup;
                     }
                  xstate = XSENDBLK;
                  break;
               }
            }
         }

      /*---------------------------------------------------------------------*/
      /* Transmit next part of file, if any                                  */
      /*---------------------------------------------------------------------*/
      switch (xstate)
         {
         case XSENDBLK:
            if (tx_inhibit)
               break;
            *((long *) Txbuf) = lasttx = txpos;
            errno = 0;
            blklen = read (Txfile, Txbuf + sizeof (txpos), txblklen);
            if (j_error (MSG_TXT (M_READ_MSG), Txfname))
               goto giveup;
            txpos += blklen;
            sendpkt (Txbuf, sizeof (txpos) + blklen, BLKPKT);
            update_status (&txpos, &txoldpos, Txlen - txpos, &txoldeta, Tx_y);
            fsent = TRUE;
            if (txpos >= Txlen || blklen < txblklen)
               {
               long_set_timer (&xmit_retry, TimeoutSecs);
               xstate = XRCVEOFACK;
               }
            else
               long_set_timer (&brain_dead, 120);

            if (txblklen < txblkmax && (goodbytes += txblklen) >= goodneeded)
               {
               txblklen <<= 1;
               goodbytes = 0;
               }
            break;

         case XSENDFNAME:
            blklen = (int) (strchr (strchr ((char *) Txbuf, '\0') + 1, '\0') - (char *) Txbuf) + 1;
            Txbuf[blklen++] = OURCAP;
            sendpkt (Txbuf, blklen, FNAMEPKT);
            txoldpos = txoldeta = -1;
            long_set_timer (&xmit_retry, TimeoutSecs);
            xstate = XRCVFNACK;
            break;

         case XSENDFREQNAK:
            sendpkt (NULL, 0, FREQNAKPKT);
            long_set_timer (&xmit_retry, TimeoutSecs);
            xstate = XRCVFRNAKACK;
            break;
         }

      /*---------------------------------------------------------------------*/
      /* Catch up on our reading; receive and handle all outstanding packets */
      /*---------------------------------------------------------------------*/
      while ((pkttype = rcvpkt ()) != 0)
         {
         if (pkttype != BADPKT)
            long_set_timer (&brain_dead, 120);
         switch (pkttype)
            {

            /*---------------------------------------------------------------*/
            /* File data block or munged block                               */
            /*---------------------------------------------------------------*/
            case BADPKT:
            case BLKPKT:
               if (rstate == RRCVBLK)
                  {
                  if (pkttype == BADPKT || *((long *) Rxbuf) != Rxpos)
                     {
                     if (pkttype == BLKPKT)
                        {
                        if (*((long *) Rxbuf) < last_blkpos)
                           rpos_retry = rpos_count = 0;
                        last_blkpos = *((long *) Rxbuf);
                        }
                     if (long_time_gone (&rpos_retry))
                        {

                        /*---------------------------------------------------*/
                        /* If we're the called machine, and we're trying to  */
                        /* send stuff, and it seems to be screwing up our    */
                        /* ability to receive stuff, maybe this connection   */
                        /* just can't hack full-duplex.  Try waiting till    */
                        /* the sending system finishes before sending our    */
                        /* stuff to it                                       */
                        /*---------------------------------------------------*/
                        if (rpos_count > 4)
                           {
                           if (xstate && !isOriginator && !tx_inhibit)
                              {
                              tx_inhibit = TRUE;
                              j_status (MSG_TXT (M_GOING_ONE_WAY));
                              }
                           rpos_count = 0;
                           }
                        if (++rpos_count == 1)
                           (void) time ((time_t *) &rpos_sttime);
                        j_message (Rx_y, MSG_TXT (M_J_BAD_PACKET), Rxpos);
                        *((long *) Rxbuf) = Rxpos;
                        *((long *) (Rxbuf + sizeof (Rxpos))) = rpos_sttime;
                        sendpkt ((byte *) Rxbuf, sizeof (Rxpos) + sizeof (rpos_sttime), RPOSPKT);
                        long_set_timer (&rpos_retry, TimeoutSecs / 2);
                        }
                     }
                  else
                     {
                     last_blkpos = Rxpos;
                     rpos_retry = rpos_count = 0;
                     errno = 0;
                     (void) write (Rxfile, Rxbuf + sizeof (Rxpos), Rxblklen -= sizeof (Rxpos));
                     if (j_error (MSG_TXT (M_WRITE_MSG), Rxfname))
                        goto giveup;
                     Diskavail -= Rxblklen;
                     Rxpos += Rxblklen;
                     update_status (&Rxpos, &rxoldpos, Rxlen - Rxpos, &rxoldeta, Rx_y);
                     if (Rxpos >= Rxlen)
                        {
                        rxclose (GOOD_XFER);
                        Rxlen -= rxstpos;
                        through (&Rxlen, &Rxsttime);
                        j_status ("%s-J%s %s", MSG_TXT (M_FILE_RECEIVED), (SharedCap & CANCRC32) ? "/32" : " ", Rxfname);
                        j_msgend (Rx_y);
                        update_files (0);
                        rstate = RRCVFNAME;
                        }
                     }
                  }
               if (rstate == RRCVFNAME)
                  sendpkt (NULL, 0, EOFACKPKT);
               break;

            /*---------------------------------------------------------------*/
            /* Name and other data for next file to receive                  */
            /*---------------------------------------------------------------*/
            case FNAMEPKT:
               if (rstate == RRCVFNAME)
                  Rxpos = rxstpos = procfname ();
               if (!Rxfname[0] && get_filereq (req_started))
                  {
                  sendpkt ((byte *) Rxbuf, strlen (Rxbuf) + 2, FREQPKT);
                  attempting_req = TRUE;
                  req_started = FALSE;
                  }
               else
                  {
                  if (attempting_req)
                     {
                     attempting_req = FALSE;
                     req_started = TRUE;
                     }
                  *((long *) Rxbuf) = Rxpos;
                  Rxbuf[sizeof (Rxpos)] = (char) SharedCap;
                  sendpkt ((byte *) Rxbuf, sizeof (Rxpos) + 1, FNACKPKT);
                  rxoldpos = rxoldeta = -1;
                  if (Rxpos > -1)
                     rstate = (byte) ((Rxfname[0]) ? RRCVBLK : RDONE);
                  else
                     j_status (MSG_TXT (M_REFUSING), Rxfname);
                  if (!rstate)
                     tx_inhibit = FALSE;
                  if (!(xstate || rstate))
                     goto breakout;
                  }
               break;

            /*---------------------------------------------------------------*/
            /* ACK to filename packet we just sent                           */
            /*---------------------------------------------------------------*/
            case FNACKPKT:
               if (xstate == XRCVFNACK)
                  {
                  xmit_retry = 0L;
                  if (Txfname[0])
                     {
                     SharedCap = (byte) ((Rxblklen > sizeof (long)) ? Rxbuf[sizeof (long)] : 0);
                     if ((txpos = *((long *) Rxbuf)) > -1L)
                        {
                        if (txpos)
                           status_line (MSG_TXT (M_SYNCHRONIZING), txpos);
                        errno = 0;
                        if (lseek (Txfile, txstpos = txpos, SEEK_SET) == -1L)
                           {
                           (void) j_error (MSG_TXT (M_SEEK_MSG), Txfname);
                           goto giveup;
                           }
                        xstate = XSENDBLK;
                        }
                     else
                        {
                        j_status (MSG_TXT (M_REMOTE_REFUSED), Txfname);
                        if (sending_req)
                           {
                           if (!(sending_req = get_reqname (FALSE)))
                              getfname (GOOD_XFER);
                           }
                        else
                           {
                           Do_after = NOTHING_AFTER;
                           getfname (GOOD_XFER);
                           }
                        xstate = XSENDFNAME;
                        }
                     }
                  else
                     {
                     sent_mail = 1;
                     xstate = XDONE;
                     }
                  }
               if (!(xstate || rstate))
                  goto breakout;
               break;

            /*---------------------------------------------------------------*/
            /* Request to send more stuff rather than end batch just yet     */
            /*---------------------------------------------------------------*/
            case FREQPKT:
               if (xstate == XRCVFNACK)
                  {
                  xmit_retry = 0L;
                  SharedCap = *(strchr (Rxbuf, '\0') + 1);
                  (void) sprintf ((char *) Txbuf, request_template, CURRENT.sc_Inbound,
                                  Hex_Addr_Str (&(alias[0])));
                  errno = 0;
                  reqfile = fopen ((char *) Txbuf, "wt");
                  if (reqfile != (FILE *) NULL)
                     errno = 0;
                  (void) j_error (MSG_TXT (M_OPEN_MSG), (char *) Txbuf);
                  (void) fputs (Rxbuf, reqfile);
                  (void) fputs ("\n", reqfile);
                  (void) fclose (reqfile);
                  (void) unlink (ReqTmp);
                  ReqRecorded = 0;              /* counted by record_reqfile */
                  (void) respond_to_file_requests (0, record_reqfile, timeof_reqfile);
                  CURRENT.rq_Limit -= ReqRecorded;
                  if ((sending_req = get_reqname (TRUE)) != 0)
                     xstate = XSENDFNAME;
                  else
                     xstate = XSENDFREQNAK;
                  }
               break;

            /*---------------------------------------------------------------*/
            /* Our last file request didn't match anything; move on to next  */
            /*---------------------------------------------------------------*/
            case FREQNAKPKT:
               attempting_req = FALSE;
               req_started = TRUE;
               sendpkt (NULL, 0, FRNAKACKPKT);
               break;

            /*---------------------------------------------------------------*/
            /* ACK to no matching files for request error; try to end again  */
            /*---------------------------------------------------------------*/
            case FRNAKACKPKT:
               if (xstate == XRCVFRNAKACK)
                  {
                  xmit_retry = 0L;
                  getfname (GOOD_XFER);
                  xstate = XSENDFNAME;
                  }
               break;

            /*---------------------------------------------------------------*/
            /* ACK to last data block in file                                */
            /*---------------------------------------------------------------*/
            case EOFACKPKT:
               if (xstate == XRCVEOFACK || xstate == XRCVFNACK)
                  {
                  xmit_retry = 0L;
                  if (xstate == XRCVEOFACK)
                     {
                     Txlen -= txstpos;
                     through (&Txlen, &Txsttime);
                     j_status ("%s-J%s %s", MSG_TXT (M_FILE_SENT), (SharedCap & CANCRC32) ? "/32" : " ", Txfname);
                     j_msgend (Tx_y);
                     update_files (1);
                     if (sending_req)
                        {
                        if (!(sending_req = get_reqname (FALSE)))
                           getfname (GOOD_XFER);
                        }
                     else
                        getfname (GOOD_XFER);
                     }
                  xstate = XSENDFNAME;
                  }
               break;

            /*---------------------------------------------------------------*/
            /* Receiver says "let's try that again."                         */
            /*---------------------------------------------------------------*/
            case RPOSPKT:
               if (xstate == XSENDBLK || xstate == XRCVEOFACK)
                  {
                  if (*((long *) (Rxbuf + sizeof (txpos))) != last_rpostime)
                     {
                     last_rpostime = *((long *) (Rxbuf + sizeof (txpos)));
                     xmit_retry = 0L;
                     CLEAR_OUTBOUND ();
                     errno = 0;
                     if (lseek (Txfile, txpos = lasttx = *((long *) Rxbuf), SEEK_SET) == -1L)
                        {
                        (void) j_error (MSG_TXT (M_SEEK_MSG), Txfname);
                        goto giveup;
                        }
                     j_status (MSG_TXT (M_SYNCHRONIZING), txpos);
                     txblklen >>= 2;
                     if (txblklen < 64)
                        txblklen = 64;
                     goodbytes = 0;
                     goodneeded += 1024;
                     if (goodneeded > 8192)
                        goodneeded = 8192;
                     xstate = XSENDBLK;
                     }
                  }
               break;

            /*---------------------------------------------------------------*/
            /* Debris from end of previous Janus session; ignore it          */
            /*---------------------------------------------------------------*/
            case HALTACKPKT:
               break;

            /*---------------------------------------------------------------*/
            /* Abort the transfer and quit                                   */
            /*---------------------------------------------------------------*/
            default:
               j_status (MSG_TXT (M_UNKNOWN_PACKET), pkttype);
               /* fallthrough */
            case HALTPKT:
giveup:
               j_status (MSG_TXT (M_SESSION_ABORT));
               if (Txfname[0])
                  getfname (ABORT_XFER);
               if (rstate == RRCVBLK)
                  {
                  TotalBytes += (Rxpos - rxstpos);
                  rxclose (FAILED_XFER);
                  }
               goto abortxfer;

            }                                   /* switch (pkttype)  */
         }                                      /* while (pkttype)   */
      } while (xstate || rstate);

   /*------------------------------------------------------------------------*/
   /* All done; make sure other end is also finished (one way or another)    */
   /*------------------------------------------------------------------------*/
breakout:
   if (!fsent)
      j_status (MSG_TXT (M_NOTHING_TO_SEND), Full_Addr_Str (&called_addr));
abortxfer:
   through (&TotalBytes, &starttime);
   endbatch ();

   /*------------------------------------------------------------------------*/
   /* Release allocated memory                                               */
   /*------------------------------------------------------------------------*/
freemem:
   if (Txfname)
      free (Txfname);
   if (Rxfname)
      free (Rxfname);
   set_prior (4);                               /* Always High                                     */
}



/*****************************************************************************/
/* Get name and info for next file to be transmitted, if any, and build      */
/* FNAMEPKT.  Packet contents as per ZModem filename info packet, to allow   */
/* use of same method of aborted-transfer recovery.  If there are no more    */
/* files to be sent, build FNAMEPKT with null filename.  Also open file and  */
/* set up for transmission.  Set Txfname, Txfile, Txlen.  Txbuf must not be  */
/* modified until FNACKPKT is received.                                      */
/*****************************************************************************/
void getfname (word xfer_flag)
{
   static byte    floflag, bad_xfers;
   static char    outboundname[PATHLEN];
   static long    floname_pos;
   static FILE   *flofile;
   char          *holdname;

   register char *p;
   int            i;
   long           curr_pos;
   struct stat    f;

   /*------------------------------------------------------------------------*/
   /* Initialize static variables on first call of the batch                 */
   /*------------------------------------------------------------------------*/
   if (xfer_flag == INITIAL_XFER)
      {
      floflag = outboundname[0] = '\0';
      flofile = NULL;
      }
   else
   /*------------------------------------------------------------------------*/
   /* If we were already sending a file, close it and clean up               */
   /*------------------------------------------------------------------------*/
   if (Txfile != -1)
      {
      errno = 0;
      (void) close (Txfile);
      Txfile = -1;
      /*---------------------------------------------------------------------*/
      /* If xfer completed, do post-xfer cleanup                             */
      /*---------------------------------------------------------------------*/
      if (xfer_flag == GOOD_XFER)
         {
         /*------------------------------------------------------------------*/
         /* Perform post-xfer file massaging if neccessary                   */
         /*------------------------------------------------------------------*/
         switch (Do_after)
            {
            case DELETE_AFTER:
            case SHOW_DELETE_AFTER:
               j_status (MSG_TXT (M_UNLINKING_MSG), Txfname);
               (void) unlink (Txfname);
               break;
            case TRUNC_AFTER:
               j_status (MSG_TXT (M_TRUNC_MSG), Txfname);
               Txfile = open (Txfname, O_TRUNC | O_RDWR, S_IREAD | S_IWRITE);
               if (Txfile != -1)
                  errno = 0;
               (void) j_error (MSG_TXT (M_TRUNC_MSG), Txfname);
               (void) close (Txfile);
               Txfile = -1;
            }
         /*------------------------------------------------------------------*/
         /* If processing .?LO file, flag filename as sent (name[0] = '~')   */
         /*------------------------------------------------------------------*/
   skipname:
         if (floflag)
            {
            curr_pos = ftell (flofile);
            if (curr_pos == -1L)
               (void) j_error (MSG_TXT (M_SEEK_MSG), outboundname);
            if (fseek (flofile, floname_pos, SEEK_SET) == -1L)
               (void) j_error (MSG_TXT (M_SEEK_MSG), outboundname);
            if (fputc (Txfname[0] = '~', flofile) != EOF)
               errno = 0;
            (void) j_error (MSG_TXT (M_WRITE_MSG), outboundname);
            if (fseek (flofile, curr_pos, SEEK_SET) == -1L)
               (void) j_error (MSG_TXT (M_SEEK_MSG), outboundname);
            }
         }
      else
         {
   abort:
         ++bad_xfers;
         }
      }

   /*------------------------------------------------------------------------*/
   /* Find next file to be sent and build FNAMEPKT.  If reading .FLO-type    */
   /* file get next entry from it; otherwise check for next .OUT/.FLO file   */
   /*------------------------------------------------------------------------*/

   holdname = HoldAreaNameMunge (&called_addr);
   if (!floflag)
      {
      /*---------------------------------------------------------------------*/
      /* If first getfname() for this batch, init filename to .OUT           */
      /*---------------------------------------------------------------------*/
      if (!outboundname[0])
         {
         (void) sprintf (outboundname, "%s%s.OUT", holdname, Hex_Addr_Str (&called_addr));
         *ext_flags = 'O';
         }
      /*---------------------------------------------------------------------*/
      /* Increment outbound filename until match found or all checked        */
      /* .OUT->.DUT->.CUT->.HUT->.FLO->.DLO->.CLO->.HLO->null name           */
      /*---------------------------------------------------------------------*/
      else
         {
   nxtout:
         p = strchr (outboundname, '\0') - 3;
         for (i = 0; i < NUM_FLAGS; ++i)
            if (ext_flags[i] == *p)
               break;
         if (i < NUM_FLAGS - 1)
            {
            *p = ext_flags[i + 1];
#ifndef JACK_DECKER
            if (isOriginator && *p == 'H')
               goto nxtout;
#endif
            }
         else
            {
            /*---------------------------------------------------------------*/
            /* Finished ?,D,C,H sequence; wrap .OUT->.FLO, or .FLO->done     */
            /*---------------------------------------------------------------*/
            if (!floflag)
               {
               *p++ = *ext_flags = 'F';
               *p++ = 'L';
               *p = 'O';
               ++floflag;
               }
            else
               outboundname[0] = Txfname[0] = Txbuf[0] = Txbuf[1] = floflag = '\0';
            }
         }
      /*---------------------------------------------------------------------*/
      /* Check potential outbound name; if file doesn't exist keep looking   */
      /*---------------------------------------------------------------------*/
      if (outboundname[0])
         {
         if (!dexists (outboundname))
            goto nxtout;
         if (floflag)
            goto rdflo;
         (void) strcpy (Txfname, outboundname);
         /*------------------------------------------------------------------*/
         /* Start FNAMEPKT using .PKT alias                                  */
         /*------------------------------------------------------------------*/
         invent_pkt_name ((char *) Txbuf);
         Do_after = DELETE_AFTER;
         }
      /*---------------------------------------------------------------------*/
      /* Read and process next entry from .?LO-type file                     */
      /*---------------------------------------------------------------------*/
      }
   else
      {
rdflo:
      /*---------------------------------------------------------------------*/
      /* Open .?LO file for processing if neccessary                         */
      /*---------------------------------------------------------------------*/
      if (!flofile)
         {
         bad_xfers = 0;
         errno = 0;
         flofile = share_fopen (outboundname, "rb+", DENY_WRITE);
         if (flofile == (FILE *) NULL)
            {
            j_error (MSG_TXT (M_OPEN_MSG), outboundname);
            goto nxtout;
            }
         }
      errno = 0;
      floname_pos = ftell (flofile);
      if (floname_pos == -1L)
         (void) j_error (MSG_TXT (M_SEEK_MSG), outboundname);
      if (fgets (p = Txfname, PATHLEN, flofile))
         {
         /*------------------------------------------------------------------*/
         /* Got an attached file name; check for handling flags, fix up name */
         /*------------------------------------------------------------------*/
         while (*p > ' ')
            ++p;
         *p = '\0';
         switch (Txfname[0])
            {
            case '\0':
            case '~':
            case ';':
               goto rdflo;
            case TRUNC_AFTER:
            case DELETE_AFTER:
            case SHOW_DELETE_AFTER:
               Do_after = Txfname[0];
               (void) strcpy (Txfname, Txfname + 1);
               break;
            default:
               Do_after = NOTHING_AFTER;
               break;
            }
         /*------------------------------------------------------------------*/
         /* Start FNAMEPKT with simple filename                              */
         /*------------------------------------------------------------------*/
         while (p >= Txfname && *p != '\\' && *p != ':')
            --p;
         (void) strcpy ((char *) Txbuf, ++p);
         }
      else
         {
         /*------------------------------------------------------------------*/
         /* Finished reading this .?LO file; clean up and look for another   */
         /*------------------------------------------------------------------*/
         errno = 0;
         (void) fclose (flofile);
         flofile = NULL;
         if (!bad_xfers)
            {
            (void) unlink (outboundname);
            }
         goto nxtout;
         }
      }

   /*------------------------------------------------------------------------*/
   /* If we managed to find a valid file to transmit, open it, finish        */
   /* FNAMEPKT, and print nice message for the sysop.                        */
   /*------------------------------------------------------------------------*/
   if (Txfname[0])
      {
      if (xfer_flag == ABORT_XFER)
         goto abort;
      j_status (MSG_TXT (M_SENDING), Txfname);
      errno = 0;
      Txfile = share_open (Txfname, O_RDONLY | O_BINARY, DENY_WRITE);
      if (Txfile != -1)
         errno = 0;
      if (j_error (MSG_TXT (M_OPEN_MSG), Txfname))
         goto skipname;


      if (isatty (Txfile))                      /* Check for character devices     */
         {
         (void) close (Txfile);                 /* return errors if it is the case */
         errno = 1;
         (void) j_error (MSG_TXT (M_DEVICE_MSG), Txfname);
         goto skipname;
         }

      (void) stat (Txfname, &f);
      (void) sprintf (strchr ((char *) Txbuf, '\0') + 1, "%lu %lo %o", Txlen = f.st_size, f.st_mtime, f.st_mode);

      p = strchr (Txfname, '\0');
      while (p >= Txfname && *p != ':' && *p != '\\')
         --p;
      if (!un_attended || !fullscreen)
         Tx_y = Next_y;
      else
         Tx_y = 1;
      xfer_summary (MSG_TXT (M_SEND), ++p, &Txlen, Tx_y);

      (void) time ((time_t *) &Txsttime);
      }
}


/*****************************************************************************/
/* Build and send a packet of any type.                                      */
/* Packet structure is: PKTSTRT,contents,packet_type,PKTEND,crc              */
/* CRC is computed from contents and packet_type only; if PKTSTRT or PKTEND  */
/* get munged we'll never even find the CRC.                                 */
/*****************************************************************************/
void sendpkt (register byte * buf, int len, int type)
{
   register word crc;

   if ((SharedCap & CANCRC32) && type != FNAMEPKT)
      sendpkt32 (buf, len, type);
   else
      {
      BUFFER_BYTE (DLE);
      BUFFER_BYTE (PKTSTRTCHR ^ 0x40);

      crc = 0;
      while (--len >= 0)
         {
         txbyte (*buf);
         crc = xcrc (crc, ((word) (*buf++)));
         }

      BUFFER_BYTE ((byte) type);
      crc = xcrc (crc, type);

      BUFFER_BYTE (DLE);
      BUFFER_BYTE (PKTENDCHR ^ 0x40);

      txbyte ((byte) (crc >> 8));
      txbyte ((byte) (crc & 0xFF));

      UNBUFFER_BYTES ();
      }
}


/*****************************************************************************/
/* Build and send a packet using 32-bit CRC; same as sendpkt in other ways   */
/*****************************************************************************/
void sendpkt32 (register byte * buf, register int len, int type)
{
   unsigned long crc32;

   BUFFER_BYTE (DLE);
   BUFFER_BYTE (PKTSTRTCHR32 ^ 0x40);

   crc32 = 0xFFFFFFFF;
   while (--len >= 0)
      {
      txbyte (*buf);
      crc32 = Z_32UpdateCRC (((word) * buf), crc32);
      ++buf;
      }

   BUFFER_BYTE ((byte) type);
   crc32 = Z_32UpdateCRC (type, crc32);

   BUFFER_BYTE (DLE);
   BUFFER_BYTE (PKTENDCHR ^ 0x40);

   txbyte ((byte) (crc32 >> 24));
   txbyte ((byte) ((crc32 >> 16) & 0xFF));
   txbyte ((byte) ((crc32 >> 8) & 0xFF));
   txbyte ((byte) (crc32 & 0xFF));

   UNBUFFER_BYTES ();
}



/*****************************************************************************/
/* Transmit cooked escaped byte(s) corresponding to raw input byte.  Escape  */
/* DLE, XON, and XOFF using DLE prefix byte and ^ 0x40. Also escape          */
/* CR-after-'@' to avoid Telenet/PC-Pursuit problems.                        */
/*****************************************************************************/
void txbyte (register byte c)
{
   static byte lastsent;

   switch (c)
      {
      case CR:
         if (lastsent != '@')
            goto sendit;
         /* fallthrough */
      case DLE:
      case XON:
      case XOFF:
         BUFFER_BYTE (DLE);
         c ^= 0x40;
         /* fallthrough */
      default:
   sendit:BUFFER_BYTE (lastsent = c);
      }
}


/*****************************************************************************/
/* Process FNAMEPKT of file to be received.  Check for aborted-transfer      */
/* recovery and solve filename collisions.    Check for enough disk space.   */
/* Return initial file data position to start receiving at, or -1 if error   */
/* detected to abort file reception.  Set Rxfname, Rxlen, Rxfile.            */
/*****************************************************************************/
long procfname (void)
{
   register char *p;
   char           linebuf[128], *fileinfo, *badfname;
   long           filestart, bytes;
   FILE          *abortlog;
   struct stat    f;
   int            i;

   /*------------------------------------------------------------------------*/
   /* Initialize for file reception                                          */
   /*------------------------------------------------------------------------*/
   badfname = NULL;
   Rxfname[0] = Resume_WaZOO = 0;

   /*------------------------------------------------------------------------*/
   /* Save info on WaZOO transfer in case of abort                           */
   /*------------------------------------------------------------------------*/
   (void) strcpy (Resume_name, fancy_str (Rxbuf));
   fileinfo = strchr (Rxbuf, '\0') + 1;
   p = strchr (fileinfo, '\0') + 1;
   SharedCap = (byte) ((Rxblklen > p - Rxbuf) ? *p & OURCAP : 0);

   /*------------------------------------------------------------------------*/
   /* If this is a null FNAMEPKT, return OK immediately                      */
   /*------------------------------------------------------------------------*/
   if (!Rxbuf[0])
      return 0L;

   (void) strcpy (linebuf, Rxbuf);
   (void) strlwr (linebuf);
   p = check_netfile (linebuf);
   j_status ("#%s %s %s", MSG_TXT (M_RECEIVING), (p) ? p : " ", Rxbuf);

   /*------------------------------------------------------------------------*/
   /* Extract and validate filesize                                          */
   /*------------------------------------------------------------------------*/
   Rxlen = -1;
   Rxfiletime = 0;
   if (sscanf (fileinfo, "%ld %lo", &Rxlen, &Rxfiletime) < 1 || Rxlen < 0)
      {
      j_status (MSG_TXT (M_NO_LENGTH));
      return -1L;
      }
   (void) sprintf (Resume_info, "%ld %lo", Rxlen, Rxfiletime);

   /*------------------------------------------------------------------------*/
   /* Check if this is a failed WaZOO transfer which should be resumed       */
   /*------------------------------------------------------------------------*/
   if (dexists (Abortlog_name))
      {
      errno = 0;
      abortlog = fopen (Abortlog_name, read_ascii);
      if (abortlog != (FILE *) NULL)
         errno = 0;
      if (!j_error (MSG_TXT (M_OPEN_MSG), Abortlog_name))
         {
         while (!feof (abortlog))
            {
            linebuf[0] = '\0';
            if (!fgets (p = linebuf, sizeof (linebuf), abortlog))
               break;
            while (*p >= ' ')
               ++p;
            *p = '\0';
            p = strchr (linebuf, ' ');
            *p = '\0';
            if (!stricmp (linebuf, Resume_name))
               {
               p = strchr ((badfname = ++p), ' ');
               *p = '\0';
               if (!stricmp (++p, Resume_info))
                  {
                  ++Resume_WaZOO;
                  break;
                  }
               }
            }
         errno = 0;
         (void) fclose (abortlog);
         }
      }

   /*------------------------------------------------------------------------*/
   /* Open either the old or a new file, as appropriate                      */
   /*------------------------------------------------------------------------*/
   p = strchr (strcpy (Rxfname, CURRENT.sc_Inbound), '\0');
   errno = 0;
   if (Resume_WaZOO)
      {
      (void) strcpy (p, badfname);
      Rxfile = open (Rxfname, O_CREAT | O_RDWR | O_BINARY, S_IREAD | S_IWRITE);
      }
   else
      {
      (void) strcpy (p, Rxbuf);
      /*---------------------------------------------------------------------*/
      /* If the file already exists:                                         */
      /* 1) And the new file has the same time and size, skip it             */
      /* 2) And OVERWRITE is turned on, delete the old copy                  */
      /* 3) Else create a unique file name in which to store new data        */
      /*---------------------------------------------------------------------*/
      if (dexists (Rxfname))
         {
         (void) stat (Rxfname, &f);
         if (Rxlen == f.st_size && Rxfiletime == f.st_mtime)
            {
            j_status (MSG_TXT (M_ALREADY_HAVE), Rxfname);
            return -1L;
            }
         i = strlen (Rxfname) - 1;
         if ((!overwrite) || (is_arcmail (Rxfname, i)))
            {
            unique_name (Rxfname);
            j_status (MSG_TXT (M_RENAME_MSG), Rxfname);
            }
         else
            {
            (void) unlink (Rxfname);
            }
         }
      Rxfile = open (Rxfname, O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IREAD | S_IWRITE);
      }
   if (Rxfile != -1)
      errno = 0;
   if (j_error (MSG_TXT (M_OPEN_MSG), Rxfname))
      return -1L;


   if (isatty (Rxfile))                         /* Check for character devices     */
      {
      (void) close (Rxfile);                    /* Return errors if it is the case */
      errno = 1;
      (void) j_error (MSG_TXT (M_DEVICE_MSG), Rxfname);
      return -1L;
      }


   /*------------------------------------------------------------------------*/
   /* Determine initial file data position                                   */
   /*------------------------------------------------------------------------*/
   if (Resume_WaZOO)
      {
      (void) stat (Rxfname, &f);
      j_status (MSG_TXT (M_SYNCHRONIZING_OFFSET), filestart = f.st_size);
      p = Rxbuf;
      errno = 0;
      if (lseek (Rxfile, filestart, SEEK_SET) == -1L)
         {
         (void) j_error (MSG_TXT (M_SEEK_MSG), Rxfname);
         (void) close (Rxfile);
         return -1L;
         }
      }
   else
      filestart = 0L;

   /*------------------------------------------------------------------------*/
   /* Check for enough disk space                                            */
   /*------------------------------------------------------------------------*/
   bytes = Rxlen - filestart + 10240;
   if (bytes > Diskavail)
      {
      j_status (MSG_TXT (M_OUT_OF_DISK_SPACE));
      (void) close (Rxfile);
      return -1L;
      }

   /*------------------------------------------------------------------------*/
   /* Print status message for the sysop                                     */
   /*------------------------------------------------------------------------*/
   if (!un_attended || !fullscreen)
      Rx_y = Next_y;
   else
      Rx_y = 2;
   xfer_summary (MSG_TXT (M_RECV), p, &Rxlen, Rx_y);

   (void) time ((time_t *) &Rxsttime);

   return filestart;
}


/*****************************************************************************/
/* Receive, validate, and extract a packet if available.  If a complete      */
/* packet hasn't been received yet, receive and store as much of the next    */
/* packet as possible.    Each call to rcvpkt() will continue accumulating a */
/* packet until a complete packet has been received or an error is detected. */
/* Rxbuf must not be modified between calls to rcvpkt() if NOPKT is returned.*/
/* Returns type of packet received, NOPKT, or BADPKT.  Sets Rxblklen.        */
/*****************************************************************************/
byte rcvpkt ()
{
   static byte     rxcrc32;
   static word     crc;
   static unsigned long crc32;
   register byte  *p;
   register int    c;
   int             i;
   unsigned long   pktcrc;

   /*------------------------------------------------------------------------*/
   /* Abort transfer if operator pressed ESC                                 */
   /*------------------------------------------------------------------------*/

   if (got_ESC ())
      {
      j_status (GenericError, MSG_TXT (M_KBD_MSG));
      return HALTPKT;
      }

   /*------------------------------------------------------------------------*/
   /* If not accumulating packet yet, find start of next packet              */
   /*------------------------------------------------------------------------*/
   WaitFlag = FALSE;
   p = Rxbufptr;
   if (!p)
      {
      do
         c = rxbyte ();
      while (c >= 0 || c == PKTEND);

      switch (c)
         {
         case PKTSTRT:
            rxcrc32 = FALSE;
            p = (byte *) Rxbuf;
            crc = 0;
            break;
         case PKTSTRT32:
            rxcrc32 = TRUE;
            p = (byte *) Rxbuf;
            crc32 = 0xFFFFFFFF;
            break;
         case NOCARRIER:
            j_status (GenericError, &(MSG_TXT (M_NO_CARRIER)[1]));
            return HALTPKT;
         default:
            return NOPKT;
         }
      }

   /*------------------------------------------------------------------------*/
   /* Accumulate packet data until we empty buffer or find packet delimiter  */
   /*------------------------------------------------------------------------*/
   if (rxcrc32)
      {
      while ((c = rxbyte ()) >= 0 && p < Rxbufmax)
         {
         *p++ = (byte) c;
         crc32 = Z_32UpdateCRC (c, crc32);
         }
      }
   else
      {
      while ((c = rxbyte ()) >= 0 && p < Rxbufmax)
         {
         *p++ = (byte) c;
         crc = xcrc (crc, c);
         }
      }

   /*------------------------------------------------------------------------*/
   /* Handle whichever end-of-packet condition occurred                      */
   /*------------------------------------------------------------------------*/
   switch (c)
      {
      /*---------------------------------------------------------------------*/
      /* PKTEND found; verify valid CRC                                      */
      /*---------------------------------------------------------------------*/
      case PKTEND:
         WaitFlag = TRUE;
         pktcrc = 0;
         for (i = (rxcrc32) ? 4 : 2; i; --i)
            {
            if ((c = rxbyte ()) < 0)
               break;
            pktcrc = (pktcrc << 8) | c;
            }
         if (!i)
            {
            if ((rxcrc32 && pktcrc == crc32) || pktcrc == crc)
               {
               /*------------------------------------------------------------*/
               /* Good packet verified; compute packet data length and       */
               /* return packet type                                         */
               /*------------------------------------------------------------*/
               Rxbufptr = NULL;
               Rxblklen = (int) (--p - (byte *) Rxbuf);
               return *p;
               }
            }
         /* fallthrough */

      /*---------------------------------------------------------------------*/
      /* Bad CRC, carrier lost, or buffer overflow from munged PKTEND        */
      /*---------------------------------------------------------------------*/
      default:
         if (c == NOCARRIER)
            {
            j_status (GenericError, &(MSG_TXT (M_NO_CARRIER)[1]));
            return HALTPKT;
            }
         else
            {
            Rxbufptr = NULL;
            return BADPKT;
            }

      /*---------------------------------------------------------------------*/
      /* Emptied buffer; save partial packet and let sender do something     */
      /*---------------------------------------------------------------------*/
      case BUFEMPTY:
         time_release ();  /* Also give other tasks a chance */
         Rxbufptr = p;
         return NOPKT;

      /*---------------------------------------------------------------------*/
      /* PKTEND was trashed; discard partial packet and prep for next one    */
      /*---------------------------------------------------------------------*/
      case PKTSTRT:
         rxcrc32 = FALSE;
         Rxbufptr = (byte *) Rxbuf;
         crc = 0;
         return BADPKT;

      case PKTSTRT32:
         rxcrc32 = TRUE;
         Rxbufptr = (byte *) Rxbuf;
         crc32 = 0xFFFFFFFF;
         return BADPKT;
      }
}



/*****************************************************************************/
/* Close file being received and perform post-reception aborted-transfer     */
/* recovery cleanup if neccessary.                                           */
/*****************************************************************************/
void rxclose (word xfer_flag)
{
   register char *p;
   char           namebuf[PATHLEN], linebuf[128];
   byte           c;
   FILE          *abortlog, *newlog;
   struct utimbuf utimes;

   /*------------------------------------------------------------------------*/
   /* Close file we've been receiving                                        */
   /*------------------------------------------------------------------------*/
   errno = 0;
   (void) close (Rxfile);
   if (Rxfiletime)
      {
      utimes.UT_ACTIME = Rxfiletime;
      utimes.modtime = Rxfiletime;
      (void) utime (Rxfname, (UTIMBUF *) &utimes);
      }

   /*------------------------------------------------------------------------*/
   /* If we completed a previously-aborted transfer, kill log entry & rename */
   /*------------------------------------------------------------------------*/
   if (xfer_flag == GOOD_XFER && Resume_WaZOO)
      {
      abortlog = fopen (Abortlog_name, read_ascii);
      if (abortlog != (FILE *) NULL)
         errno = 0;
      if (!j_error (MSG_TXT (M_OPEN_MSG), Abortlog_name))
         {
         c = 0;
         (void) strcpy (strchr (strcpy (namebuf, Abortlog_name), '\0') - 1, "TMP");
         newlog = fopen (namebuf, write_ascii);
         if (newlog != (FILE *) NULL)
            errno = 0;
         if (!j_error (MSG_TXT (M_OPEN_MSG), namebuf))
            {
            while (!feof (abortlog))
               {
               linebuf[0] = '\0';
               if (!fgets (p = linebuf, sizeof (linebuf), abortlog))
                  break;
               while (*p > ' ')
                  ++p;
               *p = '\0';
               if (stricmp (linebuf, Resume_name))
                  {
                  *p = ' ';
                  (void) fputs (linebuf, newlog);
                  if (j_error (MSG_TXT (M_WRITE_MSG), namebuf))
                     break;
                  ++c;
                  }
               }
            errno = 0;
            (void) fclose (abortlog);
            (void) fclose (newlog);
            (void) unlink (Abortlog_name);
            if (c)
               {
               if (!rename (namebuf, Abortlog_name))
                  errno = 0;
               (void) j_error (MSG_TXT (M_RENAME_MSG), namebuf);
               }
            else
               {
               (void) unlink (namebuf);
               }
            }
         else
            {
            (void) fclose (abortlog);
            }
         }
      j_status (MSG_TXT (M_FINISHED_PART), Resume_name);
      unique_name (strcat (strcpy (namebuf, CURRENT.sc_Inbound), Resume_name));
      if (!rename (Rxfname, namebuf))
         {
         errno = 0;
         (void) strcpy (Rxfname, namebuf);
         }
      else
         (void) j_error (MSG_TXT (M_RENAME_MSG), Rxfname);
      /*------------------------------------------------------------------------*/
      /* If transfer failed and was not an attempted resumption, log for later  */
      /*------------------------------------------------------------------------*/
      }
   else
   if (xfer_flag == FAILED_XFER && !Resume_WaZOO)
      {
      j_status (MSG_TXT (M_SAVING_PART), Rxfname);
      unique_name (strcat (strcpy (namebuf, CURRENT.sc_Inbound), "BadWaZOO.001"));
      if (!rename (Rxfname, namebuf))
         errno = 0;
      (void) j_error (MSG_TXT (M_RENAME_MSG), Rxfname);

      abortlog = fopen (Abortlog_name, "at");
      if (abortlog != (FILE *) NULL)
         errno = 0;
      if (!j_error (MSG_TXT (M_OPEN_MSG), Abortlog_name))
         {
         (void) fprintf (abortlog, "%s %s %s\n", Resume_name, namebuf + strlen (CURRENT.sc_Inbound), Resume_info);
         (void) j_error (MSG_TXT (M_WRITE_MSG), Abortlog_name);
         (void) fclose (abortlog);
         }
      else
         {
         (void) unlink (namebuf);
         }
      }
}


/*****************************************************************************/
/* Try REAL HARD to disengage batch session cleanly                          */
/*****************************************************************************/
void endbatch (void)
{
   register int done, timeouts;
   long         timeval, brain_dead;

   /*------------------------------------------------------------------------*/
   /* Tell the other end to halt if it hasn't already                        */
   /*------------------------------------------------------------------------*/
   done = timeouts = 0;
   long_set_timer (&brain_dead, 120);
   sendpkt (NULL, 0, HALTPKT);
   long_set_timer (&timeval, TimeoutSecs);

   /*------------------------------------------------------------------------*/
   /* Wait for the other end to acknowledge that it's halting                */
   /*------------------------------------------------------------------------*/
   while (!done)
      {
      if (long_time_gone (&brain_dead))
         break;

      switch (rcvpkt ())
         {
         case NOPKT:
         case BADPKT:
            if (long_time_gone (&timeval))
               {
               if (++timeouts > 2)
                  ++done;
               else
                  goto reject;
               }
            break;

         case HALTPKT:
         case HALTACKPKT:
            ++done;
            break;

         default:
            timeouts = 0;
      reject:sendpkt (NULL, 0, HALTPKT);
            long_set_timer (&timeval, TimeoutSecs);
            break;
         }
      }

   /*------------------------------------------------------------------------*/
   /* Announce quite insistently that we're done now                         */
   /*------------------------------------------------------------------------*/
   for (done = 0; done < 10; ++done)
      sendpkt (NULL, 0, HALTACKPKT);

   wait_for_clear ();
}


/*****************************************************************************/
/* Print a message in the message field of a transfer status line            */
/*****************************************************************************/
void j_message (word pos, char *va_alist,...)
{
   va_list arg_ptr;
   int     y, l;

#ifdef MILQ
   int     Ln = FILE_LN_1;

#endif

   char    buf[128];

   y = pos;
   va_start (arg_ptr, va_alist);

#ifndef MILQ
   if (!un_attended || !fullscreen)
      gotoxy (MSG_X, y);
   else
      sb_move (file_hWnd, y, MSG_X);
#endif

   (void) vsprintf (buf, va_alist, arg_ptr);

   for (l = 25 - strlen (buf); l > 0; --l)
      (void) strcat (buf, " ");

#ifdef MILQ
   if (un_attended)
      {
      if (2 == y)
         Ln = FILE_LN_2;
      FlLnModeSet (Ln, 0);
      sb_puts (GetDlgItem (file_hWnd, Ln + GD_STATUS), buf);
#else
   if (!un_attended || !fullscreen)
      {
      (void) cputs (buf);
      }
   else
      {
      sb_puts (file_hWnd, buf);
#endif
      sb_show ();
      }

   va_end (arg_ptr);
}


/*****************************************************************************/
/* Clear out a line in the log status display                                */
/*****************************************************************************/

void j_msgend (word pos)
{

   if (un_attended && fullscreen)
      {
      sb_move (file_hWnd, pos, 2);
#ifndef MILQ
      /* 72 blanks */
      sb_puts (file_hWnd, "                                                                        ");
#else
      sb_puts (GetDlgItem (file_hWnd, FILE_LN_2), "");
#endif
      sb_show ();
      }
}


/*****************************************************************************/
/* Print & log status message without messing up display                     */
/*****************************************************************************/
void j_status (char *va_alist,...)
{
   va_list arg_ptr;

   char    buf[128];

   va_start (arg_ptr, va_alist);

#ifndef MILQ
   if (!un_attended || !fullscreen)
      gotoxy (1, Next_y - 1);
#endif

   (void) vsprintf (buf, va_alist, arg_ptr);

   status_line (buf);

   if (!un_attended || !fullscreen)
      update_y ();

   va_end (arg_ptr);
}


/*****************************************************************************/
/* Print & log error message without messing up display                      */
/*****************************************************************************/
int j_error (char *msg, char *fname)
{
   register int e;

   if ((e = (int) errno) != 0)
      {

#ifndef MILQ
      if (!un_attended || !fullscreen)
         gotoxy (1, Next_y - 1);
#endif

      (void) got_error (msg, fname);

      if (!un_attended || !fullscreen)
         update_y ();
      }
   return e;
}


/*****************************************************************************/
/* Update screen position variables after printing a message                 */
/*****************************************************************************/
void update_y ()
{

   set_xy (NULL);               /* Bump cursor to next line after printing   */
   if (locate_y == Next_y)
      {                         /* If we didn't go anywhere, screen scrolled;*/
      if (Tx_y > 1)             /* so decrement status line numbers          */
         --Tx_y;
      if (Rx_y > 1)
         --Rx_y;
      }
   else
      Next_y = locate_y;
}


/*****************************************************************************/
/* Compute future timehack for later reference                               */
/*****************************************************************************/
void long_set_timer (long *Buffer, word Duration)
{

   (void) time ((time_t *) Buffer);
   *Buffer += (long) Duration;
}


/*****************************************************************************/
/* Return TRUE if timehack has been passed, FALSE if not                     */
/*****************************************************************************/
int long_time_gone (long *TimePtr)
{

   return (time (NULL) > *TimePtr);
}


/*****************************************************************************/
/* Receive cooked escaped byte translated to avoid various problems.         */
/* Returns raw byte, BUFEMPTY, PKTSTRT, PKTEND, or NOCARRIER.                */
/*****************************************************************************/
int rxbyte (void)
{
   register int c, w;

   if ((c = rcvrawbyte ()) == DLE)
      {
      w = WaitFlag++;
      if ((c = rcvrawbyte ()) >= 0)
         {
         switch (c ^= 0x40)
            {
            case PKTSTRTCHR:
               c = PKTSTRT;
               break;
            case PKTSTRTCHR32:
               c = PKTSTRT32;
               break;
            case PKTENDCHR:
               c = PKTEND;
               break;
            }
         }
      WaitFlag = (byte) w;
      }
   return c;
}


/*****************************************************************************/
/* Receive raw non-escaped byte.  Returns byte, BUFEMPTY, or NOCARRIER.      */
/* If waitflag is true, will wait for a byte for Timeoutsecs; otherwise      */
/* will return BUFEMPTY if a byte isn't ready and waiting in inbound buffer. */
/*****************************************************************************/
int rcvrawbyte (void)
{
   long timeval;

   if ((int) PEEKBYTE () >= 0)
      return MODEM_IN ();

   if (!CARRIER)
      return NOCARRIER;
   if (!WaitFlag)
      return BUFEMPTY;

   timeval = time (NULL) + TimeoutSecs;

   while ((int) PEEKBYTE () < 0)
      {
      if (!CARRIER)
         return NOCARRIER;
      if (time (NULL) > timeval)
         return BUFEMPTY;
      time_release ();
      }

   return MODEM_IN ();
}


/*****************************************************************************/
/* Display start-of-transfer summary info                                    */
/*****************************************************************************/
void xfer_summary (char *xfertype, char *fname, long *len, int y)
{
   char buf[128];

#ifdef MILQ
   int  FlLn = FILE_LN_1;

   if (2 == y)
      FlLn = FILE_LN_2;
   FlLnModeSet (FlLn, 1);
#endif

#ifndef MILQ
   if (!un_attended || !fullscreen)
      gotoxy (2, y);
   else
      sb_move (file_hWnd, y, 2);

   (void) sprintf (buf, "%s %12.12s;        0/%8ldb,%4d min.                       ",
                   xfertype, fname, *len, (int) ((*len * 10 / cur_baud.rate_value * 100 / JANUS_EFFICIENCY + 59) / 60));
#endif

   if (!un_attended || !fullscreen)
      {
#ifndef MILQ
      (void) cputs (buf);
      (void) cputs (local_CEOL);
#endif
      update_y ();
      }
   else
      {
#ifndef MILQ
      sb_puts (file_hWnd, buf);
#else
      (void) sprintf (buf, "%s %12.12s",
                      xfertype, fname);
      sb_puts (GetDlgItem (file_hWnd, FlLn), buf);
      sb_puts (GetDlgItem (file_hWnd, FlLn + GD_TOTAL), "       0");
      sb_puts (GetDlgItem (file_hWnd, FlLn + GD_STATUS), "");
      (void) sprintf (buf, "%8ld", *len);
      sb_puts (GetDlgItem (file_hWnd, FlLn + GD_SIZE), buf);
#endif
      sb_show ();
      }
}


/*****************************************************************************/
/* Update any status line values which have changed                          */
/*****************************************************************************/
void            update_status (long *pos, long *oldpos, long left, int *oldeta, int y)
{
   char         buf[16];
   register int eta;

#ifdef MILQ
   int          Ln = FILE_LN_1;

   if (2 == y)
      Ln = FILE_LN_2;
   FlLnModeSet (Ln, 1);
#endif

   elapse_time ();

   if (*pos != *oldpos)
      {
      (void) sprintf (buf, "%8ld", *oldpos = *pos);
#ifndef MILQ
      if (!un_attended || !fullscreen)
         {
         gotoxy (POS_X, y);
         (void) cputs (buf);
         }
      else
         {
         sb_move (file_hWnd, y, POS_X);
         sb_puts (file_hWnd, buf);
         }
#else
      sb_puts (GetDlgItem (file_hWnd, Ln + GD_TOTAL), buf);
#endif
      }

   eta = (int) ((left * 10 / cur_baud.rate_value * 100 / JANUS_EFFICIENCY + 59) / 60);
   if (eta != *oldeta)
      {
      (void) sprintf (buf, "%4d Min", *oldeta = eta);

#ifndef MILQ
      if (!un_attended || !fullscreen)
         {
         gotoxy (ETA_X, y);
         (void) cputs (buf);
         }
      else
         {
         sb_move (file_hWnd, y, ETA_X);
         sb_puts (file_hWnd, buf);
         }
#else
      sb_puts (GetDlgItem (file_hWnd, Ln + GD_DTTM), buf);
#endif
      }

   if (un_attended && fullscreen)
      sb_show ();
}

/*****************************************************************************/
/* Compute and print throughput                                              */
/*****************************************************************************/
void through (long *bytes, long *started)
{
   static char   *scrn = "+CPS: %u (%lu bytes)  Efficiency: %lu%%%%";
   unsigned long  elapsed;
   register word  cps;

   elapsed = time (NULL) - *started;
   cps = (elapsed) ? (word) (*bytes / elapsed) : 0;
   j_status (scrn, cps, *bytes, cps * 1000L / cur_baud.rate_value);
   TotalBytes += *bytes;
}


/*****************************************************************************/
/* Get next file to request, if any                                          */
/*****************************************************************************/
int get_filereq (byte req_started)
{
   char           reqname[PATHLEN], linebuf[128];
   register char *p;
   int            gotone = FALSE;
   FILE          *reqfile;

   (void) strcpy (reqname, Abortlog_name);
   (void) strcpy (strchr (reqname, 'Z'), "REQ");

   if (req_started)
      mark_done (reqname);

   if (dexists (reqname))
      {
      if (!(remote_capabilities & WZ_FREQ))
         j_status (MSG_TXT (M_FREQ_DECLINED));
      else
      if (!(SharedCap & CANFREQ))
         j_status (MSG_TXT (M_REMOTE_CANT_FREQ));
      else
         {
         errno = 0;
         reqfile = fopen (reqname, read_ascii);
         if (reqfile != (FILE *) NULL)
            errno = 0;
         if (!j_error (MSG_TXT (M_OPEN_MSG), reqname))
            {
            while (!feof (reqfile))
               {
               linebuf[0] = '\0';
               if (!fgets (p = linebuf, sizeof (linebuf), reqfile))
                  break;
               while (*p >= ' ')
                  ++p;
               *p = '\0';
               if (linebuf[0] != ';')
                  {
                  (void) strcpy (Rxbuf, linebuf);
                  *(strchr (Rxbuf, '\0') + 1) = SharedCap;
                  gotone = TRUE;
                  break;
                  }
               }
            errno = 0;
            (void) fclose (reqfile);
            if (!gotone)
               {
               (void) unlink (reqname);
               }
            }
         }
      }

   return gotone;
}


/*****************************************************************************/
/* Record names of files to send in response to file request; callback       */
/* routine for respond_to_file_requests()                                    */
/*****************************************************************************/
int record_reqfile (char *fname)
{
   FILE *tmpfile;

   errno = 0;
   tmpfile = fopen (ReqTmp, "at");
   if (tmpfile != (FILE *) NULL)
      errno = 0;
   if (!j_error (MSG_TXT (M_OPEN_MSG), ReqTmp))
      {
      (void) fputs (fname, tmpfile);
      (void) j_error (MSG_TXT (M_WRITE_MSG), ReqTmp);
      (void) fputs ("\n", tmpfile);
      (void) j_error (MSG_TXT (M_WRITE_MSG), ReqTmp);
      (void) fclose (tmpfile);
      ++ReqRecorded;
      return TRUE;
      }
   return FALSE;
}

/*****************************************************************************/
/* Estimate transfer time for requested file(s); callback                    */
/* routine for respond_to_file_requests()                                    */
/*****************************************************************************/
int timeof_reqfile (long filesize)
{
   int i;

   i = (int) (filesize * 10 / cur_baud.rate_value * 100 / JANUS_EFFICIENCY);

   /*
    * Since actual transfers don't occur while in file request code, we have to "reverse engineer" the testing for end-time. 
    */

   if (CURRENT.time_Limit != 0)
      {
      if (((long) time (NULL) + i - freq_accum.time) > CURRENT.time_Limit)
         return i;
      freq_accum.time -= i;                     /* Add time to (now-start) interval */
      return 0;
      }
   return i;
}

/*****************************************************************************/
/* Get next file which was requested, if any                                 */
/*****************************************************************************/
byte get_reqname (byte first_req)
{
   register char *p;
   byte           gotone = FALSE;
   FILE          *tmpfile;
   struct stat    f;

   if (!first_req)
      {
      errno = 0;
      (void) close (Txfile);
      Txfile = -1;
      mark_done (ReqTmp);
      }

   if (dexists (ReqTmp))
      {
      errno = 0;
      tmpfile = fopen (ReqTmp, read_ascii);
      if (tmpfile != (FILE *) NULL)
         errno = 0;
      if (!j_error (MSG_TXT (M_OPEN_MSG), ReqTmp))
         {
         while (!feof (tmpfile))
            {
            Txfname[0] = '\0';
            if (!fgets (p = Txfname, PATHLEN, tmpfile))
               break;
            while (*p >= ' ')
               ++p;
            *p = '\0';
            if (Txfname[0] != ';')
               {
               j_status (MSG_TXT (M_SENDING), Txfname);
               errno = 0;
               Txfile = share_open (Txfname, O_RDONLY | O_BINARY, DENY_WRITE);
               if (Txfile != -1)
                  errno = 0;
               if (j_error (MSG_TXT (M_OPEN_MSG), Txfname))
                  continue;
               while (p >= Txfname && *p != '\\' && *p != ':')
                  --p;
               (void) strcpy ((char *) Txbuf, ++p);
               (void) stat (Txfname, &f);
               (void) sprintf (strchr ((char *) Txbuf, '\0') + 1, "%lu %lo %o", Txlen = f.st_size, f.st_mtime, f.st_mode);
               if (!un_attended || !fullscreen)
                  Tx_y = Next_y;
               else
                  Tx_y = 1;
               xfer_summary (MSG_TXT (M_SEND), p, &Txlen, Tx_y);
               (void) time ((time_t *) &Txsttime);
               gotone = TRUE;
               break;
               }
            }
         (void) fclose (tmpfile);
         if (!gotone)
            {
            (void) unlink (ReqTmp);
            }
         }
      }

   return gotone;
}


/*****************************************************************************/
/* Mark first unmarked line of file as done (comment it out)                 */
/*****************************************************************************/
void mark_done (char *fname)
{
   char  linebuf[128];
   FILE *fh;
   long  pos;

   if (dexists (fname))
      {
      errno = 0;
      fh = fopen (fname, "rb+");
      if (fh != (FILE *) NULL)
         errno = 0;
      if (!j_error (MSG_TXT (M_OPEN_MSG), fname))
         {
         while (!feof (fh))
            {
            pos = ftell (fh);
            if (pos == -1L)
               (void) j_error (MSG_TXT (M_SEEK_MSG), fname);
            if (!fgets (linebuf, sizeof (linebuf), fh))
               break;
            if (linebuf[0] != ';')
               {
               if (fseek (fh, pos, SEEK_SET) == -1L)
                  (void) j_error (MSG_TXT (M_SEEK_MSG), fname);
               (void) fputc (';', fh);
               (void) j_error (MSG_TXT (M_WRITE_MSG), fname);
               break;
               }
            }
         (void) fclose (fh);
         }
      }
}