/*->c.szmodem */

/* send files using zmodem */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#include <string.h>

#include <time.h>



#include "kernel.h"
#include "h.os"
#include "h.bbc"
#include "h.wimp"
#include "h.swis"


#include "h.def"

#include "h.wos"
#include "h.timex"



#include "h.ftp"
#include "h.serial"
#include "h.batch"
#include "h.view"


#include "h.zmodem"
#include "h.zmextra"





/*****************************************************************************/

#ifdef JUNK



/* Send command and related info */

int zsendcmd(char * buf,int blen)
{
 register c;
 long cmdnum=0;

/*       cmdnum = getpid(); */

 errors = 0;
 for(;;)
 {
  stohdr(cmdnum);
  Txhdr[ZF0] = Cmdack1;
  zsbhdr(ZCOMMAND, Txhdr);
  zsdata(buf, blen, ZCRCW);
listen:
       Rxtimeout=100;                /* Ten second wait for resp. */
       c=zgethdr(Rxhdr, 1);

       switch(c)
       {
           case ZRINIT:
                       goto listen;    /* CAF 8-21-87 */

            case ERROR:
          case TIMEOUT:
                       if(++errors>Cmdtries)  return(ERROR);
                       continue;

             case ZCAN:
           case ZABORT:
             case ZFIN:
            case ZSKIP:
            case ZRPOS:
                       return(ERROR);
               default:
                       if(++errors>20) return(ERROR);
                       continue;

           case ZCOMPL:
                       Exitcode = (int) Rxpos;
                       sendsaybibi();
                       return OK;

          case ZRQINIT:

#ifdef vax11c          /* YAMP :== Yet Another Missing Primitive */
                       return ERROR;
#else
                       zdeb1("******** RZ *******");
                       system("rz");
                       zdeb1("******** SZ *******");
                       goto listen;
#endif
               }
       }
}





/* Send send-init information */

static int sendzsinit(void)
{
 register c;

 if(!Zctlesc || (Rxflags & TESCCTL)) return OK;

 errors = 0;

 for(;;)
 {
  stohdr(0L);
  if(Zctlesc)
  {
   Txhdr[ZF0]|=TESCCTL;
   zshhdr(ZSINIT,Txhdr);
  }
  else
   zsbhdr(ZSINIT,Txhdr);

  zsdata(Myattn,1+strlen(Myattn),ZCRCW);
  c=zgethdr(Rxhdr, 1);

  switch(c)
  {
     case ZCAN:
               return ERROR;
 
     case ZACK:
               return OK;

       default:
               if(++errors>19) return ERROR;
               continue;
  }
 }
}







/*
 * Get the receiver's init parameters
 */


static int getzrxinit(void)
{
 register n;

 for(n=10;--n>=0;)
 {
  switch(zgethdr(Rxhdr, 1))
  {
   case ZCHALLENGE:        /* Echo receiver's challenge numbr */
                   stohdr(Rxpos);
                   zshhdr(ZACK, Txhdr);
                   continue;

     case ZCOMMAND:          /* They didn't see out ZRQINIT */
                   stohdr(0L);
                   zshhdr(ZRQINIT, Txhdr);
                   continue;

       case ZRINIT:
                   Rxflags=0377 & Rxhdr[ZF0];
                   Txfcs32 = (Wantfcs32 && (Rxflags & CANFC32));

                   Zctlesc |= Rxflags & TESCCTL;
                   Rxbuflen = (0377 & Rxhdr[ZP0])+((0377 & Rxhdr[ZP1])<<8);
                   if(!(Rxflags & CANFDX)) Txwindow = 0;

                   zdeb3("Rxbuflen=%d Tframlen=%d", Rxbuflen, Tframlen);


                   /* Override to force shorter frame length */
                   if(Rxbuflen && (Rxbuflen>Tframlen) && (Tframlen>=32))
                               Rxbuflen = Tframlen;
                   if(!Rxbuflen && (Tframlen>=32) && (Tframlen<=1024))
                               Rxbuflen = Tframlen;

                   zdeb2("Rxbuflen=%d", Rxbuflen);


                   /* Set initial subpacket length */
                   if(blklen<1024)
                   {  /* Command line override? */
                    if(txbitrate>300)  blklen = 256;
                    if(txbitrate>1200) blklen = 512;
                    if(txbitrate>2400) blklen = 1024;
                   }

                   if(Rxbuflen && blklen>Rxbuflen) blklen=Rxbuflen;
                   if(blkopt && blklen > blkopt)   blklen=blkopt;

                   zdeb3("Rxbuflen=%d blklen=%d", Rxbuflen, blklen);
                   zdeb3("Txwindow = %u Txwspac = %d", Txwindow, Txwspac);

                   return (sendzsinit());

         case ZCAN:
      case TIMEOUT:
                   return ERROR;
      
      case ZRQINIT:
                   if(Rxhdr[ZF0] == ZCOMMAND) continue;

           default:
                   zshhdr(ZNAK, Txhdr);
                   continue;
  }
 }
 return ERROR;
}




int getnak(void)
{
 int firstch;

 Lastrx = 0;
 for (;;)
 {
  switch(firstch=readline(800))
  {
      case ZPAD:
                if(getzrxinit()) return ERROR;
                Ascii=0;      /* Receiver does the conversion */
                return(FALSE);

   case TIMEOUT:
                zdeb1("Timeout on pathname");
                return TRUE;

       case NAK:
                return FALSE;

       case CAN:
                if((firstch=readline(20))==CAN && Lastrx==CAN)
                               return TRUE;
        default:
                break;
  }
  Lastrx=firstch;
 }
}








#endif 




/*****************************************************************************/


/* Fill buffer with blklen chars */

static int zfilbuf(void)
{
 int n;
 n=ftpread(txbuf,1,blklen,in);
 if(n<blklen) Eofseen = 1;
 return(n);
}




/*
 * Respond to receiver's complaint, get back in sync with receiver
 */

static int getinsync(int flag)
{
 register c;

 for (;;)
 {
  c=zgethdr(Rxhdr, 0);
 
  switch(c)
  {
      case ZCAN:
    case ZABORT:
      case ZFIN:
   case TIMEOUT:
                return ERROR;

     case ZRPOS:
                /* ************************************* */
                /*  If sending to a buffered modem, you  */
                /*   might send a break at this point to */
                /*   dump the modem's buffer.            */
                clearerr(in);   /* In case file EOF seen */

                if(ftpreadfseek(in, Rxpos, 0)) return ERROR;
                Eofseen = 0;
                bytcnt = Lrxpos = Txpos = Rxpos;
                if(Lastsync == Rxpos)
                {
                 if(++Beenhereb4>4)
                      if(blklen > 32)
                            blklen /= 2;
                }
                Lastsync=Rxpos;
                return c;

      case ZACK:
                Lrxpos = Rxpos;
                if(flag || Txpos == Rxpos) return ZACK;
                continue;

    case ZRINIT:
     case ZSKIP:
                ftpcloseread(in,"{ZM00}",0);
                return c;

     case ERROR:
        default:
                zsbhdr(ZNAK, Txhdr);
                continue;
  }
 }
}






/* Send the data in the file               */
/* FILE * in, is open close before exiting */

static int zsendfdata(void)
{
 register c, e, n;
 register newcnt;
 register long tcount = 0;
 int junkcount;            /* Counts garbage chars received by TX */

 Lrxpos=0;
 junkcount=0;
 Beenhereb4=FALSE;

somemore:
 if(0)
 {
waitack:
  junkcount = 0;
  c=getinsync(0);

gotack:
  switch(c)
  {
        default:
      case ZCAN:
                ftpcloseread(in,"{ZM01}",FTPCLOSEERROR);
                return(ERROR);

     case ZSKIP:
                ftpcloseread(in,"{ZM00}",0);
                return(c);

      case ZACK:
     case ZRPOS:
                break;

    case ZRINIT:
                ftpcloseread(in,"{ZM02}",0);   /* OK */
                return(OK);
  }

/*
 *  If the reverse channel can be tested for data,
 *  this logic may be used to detect error packets
 *  sent by the receiver, in place of setjmp/longjmp
 *  rdchk(fdes) returns non 0 if a character is available
 */
  while(rdchk())
  {
   switch(readline(1))
   {
          case CAN:
         case ZPAD:
                   c=getinsync(1);
                   goto gotack;

         case XOFF:              /* Wait a while for an XON */
    case XOFF|0200:
                  readline(100);
   }
  }

  if(!ftponline())
  {
   ftpcloseread(in,"{ZM03}",FTPCLOSEERROR);
   return(ERROR);
  }
  else
  if(!ftp_ok)
  {
   ftpcloseread(in,"{ZM01}",FTPCLOSEERROR);
   return(ERROR);
  }


 } /* end of if(0) */

 newcnt=Rxbuflen;
 Txwcnt=0;
 stohdr(Txpos);
 zsbhdr(ZDATA, Txhdr);

 do
 {
  n=zfilbuf();
  if(Eofseen)          e=ZCRCE;
  else
  if(junkcount>3)      e=ZCRCW;
  else
  if(bytcnt==Lastsync) e=ZCRCW;
  else
  if(Rxbuflen && (newcnt-=n)<= 0) e=ZCRCW;
  else
  if(Txwindow && (Txwcnt += n) >= Txwspac)
  {
   Txwcnt=0;
   e=ZCRCQ;
  }
  else                 e=ZCRCG;

  zdeb3("\r%7ld ZMODEM%s    ",Txpos, Crc32t?" CRC-32":"");

  ftpsetmode(Crc32t?"CRC32":"CRC16");

  zsdata(txbuf, n, e);
  bytcnt=Txpos+=n;

  if(e==ZCRCW) goto waitack;

 /*
  * If the reverse channel can be tested for data,
  *  this logic may be used to detect error packets
  *  sent by the receiver, in place of setjmp/longjmp
  *  rdchk(fdes) returns non 0 if a character is available
 */

  while(rdchk())
  {
   switch(readline(1))
   {
          case CAN:
         case ZPAD:
                   c=getinsync(1);
                   if(c==ZACK) break;

                   /* zcrce - dinna wanna starta ping-pong game */
                   zsdata(txbuf, 0, ZCRCE);
                   goto gotack;

         case XOFF:   /* Wait a while for an XON */
    case XOFF|0200:
                   readline(100);

           default:
                   ++junkcount;
   }
  }


  if(!ftponline())
  {
   ftpcloseread(in,"{ZM03}",FTPCLOSEERROR);
   return(ERROR);
  }
  else
  if(!ftp_ok)
  {
   ftpcloseread(in,"{ZM01}",FTPCLOSEERROR);
   return(ERROR);
  }


  if(Txwindow)
  {
   while((tcount=Txpos-Lrxpos)>= Txwindow)
   {
    zdeb3("%ld window >= %u", tcount, Txwindow);

    if(e!=ZCRCQ) zsdata(txbuf,0,e = ZCRCQ);
    c=getinsync(1);
    if(c!=ZACK)
    {
     zsdata(txbuf, 0, ZCRCE);
     goto gotack;
    }
   }
   zdeb2("window = %ld", tcount);
  }

 }while(!Eofseen);


 for(;;)
 {
  stohdr(Txpos);
  zsbhdr(ZEOF, Txhdr);
  switch(getinsync(0))
  {
       case ZACK:
                 continue;

      case ZRPOS:
                 goto somemore;

     case ZRINIT:
                 ftpcloseread(in,"{ZM02}",0);
                 return OK;

      case ZSKIP:
                 ftpcloseread(in,"{ZM00}",0);
                 return ZSKIP;   /* changed from c - DJP */

         default:
                 ftpcloseread(in,"{ZM04}",FTPCLOSEERROR);
                 return ERROR;
  }
 }
}



/* Send file name and related info         */
/* in FILE * open here, close in exit path */

static int zsendfile(char * buf,int blen)
{
 int             c;
 unsigned long crc;

 for(;;)
 {
  Txhdr[ZF0] = Lzconv;    /* file conversion request */
  Txhdr[ZF1] = Lzmanag;   /* file management request */
  if(Lskipnocor) Txhdr[ZF1] |= ZMSKNOLOC;
  Txhdr[ZF2] = Lztrans;   /* file transport request */
  Txhdr[ZF3] = 0;
  zsbhdr(ZFILE, Txhdr);
  zsdata(buf, blen, ZCRCW);

  again:
        c=zgethdr(Rxhdr, 1);
        switch(c)
        {
         case ZRINIT:
                     while((c=readline(50))>0)
                     if(c==ZPAD)
                     {
                      goto again;
                     }
                       /* **** FALL THRU TO **** */
             default:
                     continue;

           case ZCAN:
        case TIMEOUT:
         case ZABORT:
           case ZFIN:
                     ftpcloseread(in,"{ZM04}",FTPCLOSEERROR);
                     return ERROR;

           case ZCRC:
                     crc=0xFFFFFFFFL;

                     while(((c=getc(in))!=EOF) && --Rxpos) crc=UPDC32(c, crc);
                     crc=~crc;
                     clearerr(in);   /* Clear EOF */
                     ftpreadfseek(in, 0L, 0);

                     stohdr(crc);
                     zsbhdr(ZCRC, Txhdr);
                     goto again;

          case ZSKIP:
                     ftpcloseread(in,"{ZM00}",0);
                     return(c);

          case ZRPOS:
                     /*
                      * Suppress zcrcw request otherwise triggered by
                      * lastyunc==bytcnt
                      */

                     if(Rxpos && ftpreadfseek(in, Rxpos, 0)) return(ERROR);
                     Lastsync=(bytcnt = Txpos = Rxpos)-1;
                     return(zsendfdata());
        }
 }
}






/* Say "bibi" to the receiver, try to do it cleanly */

void sendsaybibi(void)
{
 for(;;)
 {
  stohdr(0L);                    /* CAF Was zsbhdr - minor change */
  zshhdr(ZFIN,Txhdr);            /* to make debugging easier      */
  switch(zgethdr(Rxhdr,0))
  {
    case ZFIN:
              sendline('O');
              sendline('O');
              flushmo();

    case ZCAN:
 case TIMEOUT:
              return;
  }
 }
}




/***********************************************************************/
/* stuff to synthesise UNIX attributes/time stamp                      */

/* Parts of st_mode field */

#define S_IFMT          0170000         /* type of file */
#define   S_IFDIR       0040000         /* directory */
#define   S_IFREG       0100000         /* regular */
#define S_IREAD         0000400         /* read permission, owner */
#define S_IWRITE        0000200         /* write permission, owner */
#define S_IEXEC         0000100         /* execute permission, owner */

/* Parts of st_attribs field */

#define S_AREAD         0x01            /* Read access for user */
#define S_AWRITE        0x02            /* Write access for user */
#define S_ALOCK         0x08            /* File is locked */



#define UG_READ         ( (S_IREAD)  | (S_IREAD >> 3)  )
#define UG_WRITE        ( (S_IWRITE) | (S_IWRITE >> 3) )
#define OTHER_READ      ( S_IREAD  >> 6 )
#define OTHER_WRITE     ( S_IWRITE >> 6 )
#define ALL_EXEC        ( (S_IEXEC)  | (S_IEXEC >> 3)  | (S_IEXEC >> 6)  )




static int filemode(fstat * f)
{
 int mode;

 mode=0;

 if(f->type==2) /* is it a directory? */
                mode|=S_IFDIR;
 else
                mode|=S_IFREG;

 if(f->acc & S_AREAD)       mode|=UG_READ;

 if(f->acc & S_AWRITE)      mode|=UG_WRITE;

 if(f->acc & (S_AREAD<<4))  mode|=OTHER_READ;

 if(f->acc & (S_AWRITE<<4)) mode|=OTHER_WRITE;

 mode|=ALL_EXEC;               /* Always allow execute */

 return(mode);
}




/*
 * converts the calendar time pointed to by timer into a broken-down time,
 * expressed a local time.
 * Returns: a pointer to that object.
 */





typedef struct
{
 unsigned char t[5];     /* Low byte first - ie. t[0] is low */
}
sys_time;



static struct tm *sys_localtime(const sys_time *timer)
{
 static struct tm t;
 _kernel_swi_regs r;
 static char p[30];
 int         buffer[10];

 r.r[1]=(int)(timer->t);
 r.r[2]=(int)buffer;
 if(_kernel_swi(Territory_ConvertTimeToUTCOrdinals,&r,&r))
 {
  r.r[0] = (int)(timer->t);
  r.r[1] = (int)p;
  r.r[2] = 30;
  r.r[3] = (int)"%se %mi %24 %dy %mn %ce%yr %wn %dn";

  _kernel_swi(0xC1,&r,&r);
  sscanf(p,"%d %d %d %d %d %d %d %d",&t.tm_sec,&t.tm_min,&t.tm_hour,
               &t.tm_mday,&t.tm_mon,&t.tm_year,&t.tm_wday,&t.tm_yday);

  t.tm_year -= 1900;
  --t.tm_mon;
  --t.tm_wday;
  --t.tm_yday;

  t.tm_isdst = 0;
 }
 else
 {
  t.tm_sec=buffer[1];
  t.tm_min=buffer[2];   /* minutes after the hour, 0 to 59 */
  t.tm_hour=buffer[3];  /* hours since midnight, 0 to 23 */
  t.tm_mday=buffer[4];  /* day of the month, 1 to 31 */
  t.tm_mon=buffer[5]-1;  /* months since January, 0 to 11 */
  t.tm_year=buffer[6]-1900;  /* years since 1900 */
  t.tm_wday=buffer[7]-1;     /* days since Sunday, 0 to 6 */
  t.tm_yday=buffer[8]-1;
  t.tm_isdst=0;
 }
 return(&t);
}



static int filetime(fstat * f)
{
 sys_time timer;
 int      time;

 timer.t[0]=f->exec       & 0xFF;
 timer.t[1]=f->exec >> 8  & 0xFF;
 timer.t[2]=f->exec >> 16 & 0xFF;
 timer.t[3]=f->exec >> 24 & 0xFF;
 timer.t[4]=f->load       & 0xFF;

 time=mktime(sys_localtime(&timer));

 return(time);
}

/************************************************************************/


/*
 * generate and transmit pathname block consisting of
 *  pathname (null terminated),
 *  file length, mode time and file mode in octal
 *  as provided by the Unix fstat call.
 *  N.B.: modifies the passed name, may extend it!
 */


static int wctxpn(int bn)
{
 fstat  f;
 char * p;
 int    fmode;
 int    ftime;

 if((in=ftpopenread(bn,0))==NULL) return(ERROR);

 ftpinfo("{ZM05}");

 /* some of these are wrong, time and access bits ! */

 memset(txbuf,0,1024);

 strcpy(txbuf,vtable[TXBFILE][bn].rname);
 p=txbuf+strlen(txbuf)+1;

 f=vtable[TXBFILE][bn].stat;

 if(!Ascii)
 {
  fmode=filemode(&f);
  ftime=filetime(&f);

  sprintf(p,"%lu %lo %o 0 %d %ld",(long)f.length,(long)ftime,fmode,
                                                 Filesleft,Totalleft);
 }

 Totalleft-=f.length;
 if(--Filesleft<=0) Totalleft=0;
 if(Totalleft<0)    Totalleft=0;

 /* force 1k blocks if name won't fit in 128 byte block */
 if(txbuf[125]) blklen=1024;
 else
 {          /* A little goodie for IMP/KMD */
/*  txbuf[127]=(f.length+127)>>7;
  txbuf[126]=(f.length+127)>>15; */
  int * q;

  q=(int*)(&txbuf[116]);
  *q++=vtable[TXBFILE][bn].stat.exec;
  *q++=vtable[TXBFILE][bn].stat.load;
  *q++=0x3;
  p=(char*)q;
 }

 return(zsendfile(txbuf,(p-txbuf)));
}



int wcs(int bn)
{
 BEofseen=0;
 Eofseen=0;

 if(blklen<1024)
 {
  if(txbitrate>300)  blklen = 256;
  if(txbitrate>1200) blklen = 512;
  if(txbitrate>2400) blklen = 1024;
 }

 if(Rxbuflen && blklen>Rxbuflen) blklen=Rxbuflen;
 if(blkopt && blklen > blkopt)   blklen=blkopt;

 switch(wctxpn(bn))
 {
  case ERROR:
             return ERROR;
  case ZSKIP:
             return OK;
 }

 return(OK);
}

