/* xmodem.c copyright 1986 Maple Lawn Farm, Inc. 
 * exit: 0 if successful, -1 for failure, compile with mlfcu.c:
 *      cc -i -O -s -DCU mlfcu.c xmodem.c -o cu or with xr.c:
 *      cc -i -O -s xr.c xmodem.c -o xr, ln xr xt */

#include <signal.h>
#include <stdio.h>

#ifdef CU

extern int     rlfd;        /* the open line in cu */
#define WFD    rlfd
#define RFD    rlfd
#define errf   stderr

#else

extern  FILE   *errf;       /* error file for remote */
#define WFD    1            /* stdout */
#define RFD    0            /* stdin */

#endif

#define BSIZE         128
#define DEBUG         01
#define LF            02
#define CRC           04
#define NOREAD(x, c)  (rchar(x, &c) == -1)
#define TX(c)         write(WFD, &c, 1)  
#define ever          (;;)

static  char   soh = 0x01,
               eot = 0x04,
               ack = 0x06,
               nak = 0x15,
               can = 0x18,
               crcinit = 'C',
               cksum;

static  int    debug,
               crc;

int   kleenex(),
      onalarm(); 

unsigned       crcsum;

xget(fp, opts)
FILE     *fp;
int      opts;
{
      char  buf[BSIZE], 
            b = 1,
            inch,
            crchi;
      int   iput = BSIZE; 
      register      i; 

      debug = (opts & DEBUG);
      crc = (opts & CRC);
      signal(SIGALRM, onalarm);
#ifdef CU
      signal(SIGINT, kleenex);
#else
      sleep(10);
#endif
      (crc) ? TX(crcinit) : TX(nak);
      for ever {
         if NOREAD(10, inch) {
               err("Timeout during SOH");
               cksend(crc ? crcinit : nak) ;
               continue;
         }
         if (inch == eot) 
               break;
         if (inch == can) {
               err("CAN block %u", b);
               kleenex(-1);
         }
         if (inch != soh) {
               err("Bad SOH block %u: %#x", b, (inch & 0xff));
               cksend(nak);
               continue;
         }
         if NOREAD(2, inch) {
               err("Timeout block %u during blocknum", b);
               cksend(nak);
               continue;
         }
         if ((inch & 0xff) != b) {
               err("Expected blocknum %u, got %u", b, (inch & 0xff));
               cksend(nak);
               continue;
         }
         if NOREAD(2, inch) {
               err("Timeout block %u during ~blocknum", b);
               cksend(nak);
               continue;
         }
         if ((inch & 0xff) != (~b & 0xff)) {
               err("Expected ~blocknum %u, got %u",
                       (~b & 0xff), (inch & 0xff));
               cksend(nak);
               continue;
         }
/* Read in 128 byte block without taking time for checksums or crc. */
         for (i = 0; i < BSIZE; i++)
               if NOREAD(2, buf[i]) 
                     break;
         if (i < BSIZE) {
               err("Timeout data recv, char #%d", i);
               cksend(nak);
               continue;
         }
         if (crc) {
               if NOREAD(2, crchi) {
                     err("Timeout crc hibyte");
                     cksend(nak);
                     continue;
               }
               crchi &= 0xff;
         }
             if NOREAD(2, inch) {
               err("Timeout %s", (crc) ? "crc lobyte" : "checksum");
               cksend(nak);
               continue;
         }
/* Now, when we have the whole packet, do the checksum or crc. */
         for (cksum = 0, crcsum = 0, i = 0; i < BSIZE; i++) 
               upsum(buf[i]);
         if (crc) {
               upsum(0);        /* needed for crcsum */
               upsum(0);
               if ((inch & 0xff) + (crchi << 8) != crcsum) {
                     err("Expected crc %u, got %u", 
                           crcsum, (inch & 0xff) + (crchi << 8));
                     cksend(nak);
                     continue;
               }
         }
         else {
               cksum %= 256;
               if (cksum != (inch & 0xff)) {
                     err("Expected checksum %u, got %u",
                           cksum, (inch & 0xff));
                     cksend(nak);
                     continue;
               }
         }
         TX(ack);
#ifdef CU
         putc('.', stderr);
#endif
         if (opts & LF) 
               for (i=0, iput=0; i < BSIZE; i++) {
                     if (buf[i] == 0x1a)  /* old ms-dos eof */
                           break;
                     if (buf[i] != '\r')
                           buf[iput++] = buf[i];
               }
         fwrite(buf, iput, 1, fp);
         b++;
         b %= 256;
   }
   TX(ack);
   kleenex(0);
}

xput(fp, opts)
FILE     *fp;
int      opts;
{
   register      i; 
   char    buf[BSIZE],
           b = 1,
           cb,
           crclo,
           inch;
   int     cread;

#ifdef CU
   signal(SIGINT, kleenex);
#endif
   signal(SIGALRM, onalarm);
   debug = (opts & DEBUG);
   rchar(60, &cb);
   if (cb == crcinit)
         crc = 1;
   else if (cb == nak)
         crc = 0;
   else  {
         err("No startup %s", (crc) ? "'C'" : "NAK");
         kleenex(-1);
   }
   cread = fillbuf(fp, buf, (opts & LF));
   while (cread) {
         for (i = cread; i < BSIZE; i++) 
               buf[i] = 0;
         TX(soh);
         TX(b);
         cb = (~b & 0xff);
         TX(cb);
         write(WFD, buf, BSIZE);
         for (cksum = 0, crcsum = 0, i = 0; i < BSIZE; i++) 
               upsum (buf[i]);
         if (crc) {
               upsum(0);        /* needed for crcsum */
               upsum(0);
               crclo = crcsum;
               cb = (crcsum >> 8);
               TX(cb);
               TX(crclo);
         }
         else {
               cksum %= 256;
               TX(cksum);
         }
         if NOREAD(15, inch) {
               err("Timeout after block %u", b);
               continue;
         }
         if (inch == can) {
               err("CAN after block %u", b);
               kleenex(-1);
         }
         if (inch != ack) {
               err("Non-ACK after block %u: %#x", b, inch);
               continue;
         }
#ifdef CU
         putc('.', stderr);
#else
         if (debug) 
               fprintf(errf, "Validated block %u\n", b);
#endif
         cread = fillbuf(fp, buf, (opts & LF));
         b++;
         b %= 256;
      }
      for ever {
            TX(eot);
            if NOREAD(15, inch) {
                  err("Timeout during EOT");
                  continue;
            }
            if (inch == can) {
                  err("CAN during EOT");
                  kleenex(-1);
            }
            if (inch != ack) {
                  err("Non-ACK during EOT: %#x", inch);
                  continue;
            }
            break;
      }
      kleenex(0);
}

fillbuf(fp, buf, lf)
FILE *fp;
char *buf;
int lf;
{
      int      i = 0, c; 
      static   int      cr_held; 

      if (cr_held) {
            buf[i] = '\n';
            i++;
            cr_held--;
      }
      for (; i < BSIZE; i++) {
            if ((c = getc(fp)) == EOF)
                  break;
            if (c == '\n' && lf) {
                  buf[i] = '\r';
                  if (i == 127) {
                        cr_held++;
                        return BSIZE;
                  }
                  buf[i+1] = '\n';
                  i++;
            }
            else
                  buf[i] = c;
      }
      return i;
}

upsum(c)
char      c;
{
      register  unsigned  shift;
      register  unsigned  flag;

      if (crc)
            for (shift = 0x80; shift; shift >>= 1)  {
                  flag = (crcsum & 0x8000);
                  crcsum <<= 1;
                  crcsum |= ((shift & c) ? 1 : 0);
                  if (flag)
                        crcsum ^= 0x1021;
            }
      else
            cksum += c;
}

/* Timeout in rchar() works by deliberately interrupting the read()
 * system call.  errno=EINTR, so no reason for a perror() autopsy. */
rchar(timeout, cp)
unsigned timeout;
char *cp;
{
   int      c; 

   alarm(timeout);
   if ((c = read(RFD, cp, 1)) == -1)
         return -1;
   alarm(0);
   return c;
}

onalarm()
{
   signal(SIGALRM, onalarm);
}

kleenex(sig)
int sig;
{
#ifdef CU
   if (sig > 0)
         cksend(can);
   else 
         fprintf(stderr, "\r\nFile transfer %s.",
               (sig) ? "cancelled" : "complete");
   fprintf(stderr, "\r\n");
#else
   printf("File transfer %s.\r\n", (sig) ? "cancelled" : "complete");
   resetline();
#endif
   exit(sig);
}

cksend(ch)
char ch;
{
   int j;
   char cp;

   do {
         j = rchar(2, &cp);
   } while (j != -1);
   TX(ch);
}

/* VARARGS1 */
err(s, i, j)
char     *s;
int      i, j;
{
      if (debug) {
            fprintf(errf, s, i, j);
#ifndef CU
            fprintf(errf, "\n");
      }
#else
            fprintf(errf, "\r\n");
      }
      else
            putc('%', stderr);
#endif
}
