#include #include #include #include /* Tom Jennings Fido Software 30 Apr 91 Zmodem send Derived from the Zmodem protocol files Chuck Forsberg, Omen Technology Inc 05-09-88 */ /* Transmit one or more files. The list fn contains one or more file names; if explicit is true, then they should be used as is else the system path name should be used. */ far zsend(fn,namelist,listlen) char *fn; /* filename for XMODEM */ char *namelist; /* list of sent names */ int listlen; /* max. len of the list */ { char fspec[SS]; /* full file to search for */ char fname[SS]; /* full filename to send */ char sendname[SS]; /* name to transmit */ char *cp; int i,f; int error; struct _xfbuf xfbuf; totl_process= totl_files= totl_errors= totl_recoveries= 0; totl_bytes= 0L; blklen= 512; /* block size for init stuff */ rxbuflen= 512; /* assume Rx buffer size */ wantfcs32= 1; /* yes we want 32 bit CRC */ lzconv= 0; /* file conversion: none */ lzmanag= 0; /* file mgt: none */ lztrans= 0; /* file xport: none */ zctlesc= 0; /* dont need control chars escaped */ cantovio= 0; /* assume Rx can overlap disk & serial I/O */ zmabort= 0; txwindow= (zmttype != 0); /* mark if a window is set */ xferstat(0,0L,0,""); /* start the display, */ rxtimeout= 200; /* Rx timeout, 100ths */ for (i= 6000 / rxtimeout; i--; ) { /* one minute to start */ xferstat(2,0L + i,0,"Waiting to start"); if (zabort()) { error= ABORT; /* manual abort */ goto done; } stohdr(0L); zshhdr(ZRQINIT,txhdr); /* request Rx init */ f= getzrxinit(); /* get Rx response */ if (f != TIMEOUT) break; /* stop on error/response */ } if (f != OK) { xferstat(3,0L,0,"Receiver not ready"); error= TIMEOUT; goto done; /* return error code */ } rxtimeout= 1000; /* real receive timeout, 1/100ths */ error= OK; /* for no-files case */ while (*fn) { cpyarg(fspec,fn); /* get an atom, */ fn= next_arg(fn); /* for next time */ *sendname= '\0'; /* no rename yet, */ for (cp= fspec; *cp; ++cp) { if (*cp == '=') break; /* look for "NAME=NAME" */ } if (*cp == '=') { *cp++= '\0'; /* remove "=NAME" */ strcpy(sendname,cp); /* save 2nd name */ } sendname[13]= '\0'; /* force legal */ i= 0; xfbuf.s_attrib= 0; while (_find(fspec,i,&xfbuf)) { ++i; strip_path(fname,fspec); /* get the prefix, */ strcat(fname,xfbuf.name); /* add the filename */ f= open(fname,0); /* open it, */ if (f == -1) { xferstat(3,xfbuf.fsize,0,"Can't open file \"%s\"",fname); error= OK; continue; } if (*sendname) { xferstat(1,xfbuf.fsize,0,"Transmitting \"%s\" as \"%s\"",fname,sendname); strcpy(xfbuf.name,sendname); } else xferstat(1,xfbuf.fsize,0,fname);/* display it, */ totl_process= 1; /* file in progress */ error= zsendf(f,&xfbuf); close(f); /* close the file, */ totl_process= 0; /* file complete, one way or another */ if (zabort()) aborttx(); /* cancel transmission */ if (error == ZSKIP) { xferstat(3,0L,0,"File Skipped by Recvr"); error= OK; /* in case no more files */ continue; /* skip this file */ } if (error != OK) break; /* check errors */ totl_bytes += txpos; /* if OK tally byte count */ if (strlen(fname) < listlen) { /* keep a list */ strcat(namelist,fname); strcat(namelist," "); listlen -= strlen(fname); --listlen; } ++totl_files; /* count another */ } if (! i) xferstat(3,0L,0,"No file(s) match \"%s\"",fspec); if (error != OK) break; } if ((error != OK) || zmabort) ++totl_errors; /* something happened */ if (!zmabort) sendend(); /* end session unless aborted earlier */ xferstat(3,totl_bytes,0,"%d files sent",totl_files); done: xferstat(3,0L,0,"Protocol Complete"); return(error); } /* Send a single file. */ static zsendf(f,xf) int f; /* open file */ struct _xfbuf *xf; /* file info */ { int i; char *p; long ftime; for (i= zmblkmax, p= zmbuff; --i;) /* clear out the block, */ *p++= '\0'; p= zmbuff; /* point to start of buffer, */ eofseen= 0; /* not EOF yet */ cpyarg(p,xf-> name); /* add the filename */ stolower(p); /* make it lower case */ while (*p++); /* point to AFTER the null, */ ftime= dos2sec(xf-> time,xf-> date); /* convert time/date */ itoav(xf-> fsize,p,10); /* add file size, (decimal) */ while (*p) ++p; *p++= ' '; /* intervening space, */ itoav(ftime,p,8); /* add file date, (octal) */ while (*p++); /* point to after the null */ bytcnt= txpos= rxpos= 0; /* assumed starting position */ lastsync= -1; /* avoid initial ZCRCW */ return zsendfile(f,zmbuff,p - zmbuff); /* to calc buff length */ } /* Convert a long to an ASCII string in the specified base. Base must be 10 or less. */ static itoav(n,s,base) long n; char s[]; unsigned base; { int i,j; char c; i= 0; do { /* generate digits */ s[i++]= n % base + '0'; /* in reverse */ } while ((n /= base) > 0); s[i]= '\0'; for (j= 0, --i; j < i; i--, j++) { /* reverse the string */ c= s[j]; s[j]= s[i]; s[i]= c; } } /* Fill buffer with blklen chars */ static zfilbuf(f) int f; /* open file */ { int n; n= read(f,zmbuff,blklen); if (n < blklen) eofseen= 1; return(n); } /* * Get the receiver's init parameters */ static getzrxinit() { int n; char buff[80]; switch (zgethdr(rxhdr,1)) { case ZCHALLENGE: /* Echo receiver's challenge number */ xferstat(2,rxpos,0,"Challenge; responding"); stohdr(rxpos); zshhdr(ZACK,txhdr); break; case ZCOMMAND: /* They didn't see our ZRQINIT */ xferstat(2,0L,0,"COMMAND; ignored"); stohdr(0L); /* send another */ zshhdr(ZRQINIT,txhdr); break; case ZRINIT: strcpy(buff,"Receiver Ready"); rxflags= 0xff & rxhdr[ZF0]; /* set Rx flags */ txfcs32= (wantfcs32 && (rxflags & CANFC32)); zctlesc |= rxflags & TESCCTL; cantovio= !(rxflags & CANOVIO); /* ZCRCW if Rx cant overlap */ if ( !(rxflags & CANFDX)) txwindow= 0; rxbuflen= (0xff & rxhdr[ZP0]) + ((0xff & rxhdr[ZP1]) << 8); if (txfcs32) strcat(buff,": CRC32"); if (zctlesc) strcat(buff,"; Escaping Control Chars"); if (cantovio) strcat(buff,"; Can't overlap I/O"); xferstat(3,0L + rxbuflen,0,buff); /* Set initial subpacket length */ blklen= zmblkst; /* set output buffer size */ if (baudrate() < 4800) blklen /= 2; /* (512 at 2400) */ if (baudrate() < 2400) blklen /= 2; /* (256 at 1200) */ if (blklen < 64) blklen= 64; /* minimum size */ if (rxbuflen && (blklen > rxbuflen)) /* Rx's max. */ blklen= rxbuflen; /* overrides all */ return (sendzsinit()); case ZCAN: xferstat(3,0L,0,"Cancel"); return ERROR; case TIMEOUT: xferstat(3,0L,0,"Timeout"); return TIMEOUT; case ZRQINIT: if (rxhdr[ZF0] == ZCOMMAND) break; default: /** fall through */ zshhdr(ZNAK,txhdr); break; } return(TIMEOUT); } /* Send send-init information */ static sendzsinit() { char c; int n; if (!zctlesc || (rxflags & TESCCTL)) return OK; for (n= 6000 / rxtimeout; n--;) { xferstat(2,0L + errors,0,"Sender Information"); if (zabort()) return ERROR; /* manual intervention */ stohdr(0L); if (zctlesc) { txhdr[ZF0] |= TESCCTL; zshhdr(ZSINIT,txhdr); } else zsbhdr(ZSINIT,txhdr); zsdata("",0,ZCRCW); /* wait for response */ c= zgethdr(rxhdr,1); switch (c) { case ZCAN: return ERROR; case ZACK: return OK; } } return ERROR; } /* Send file name and related info */ static zsendfile(f,buf,blen) int f; /* open file */ char *buf; /* buffer to send */ unsigned blen; /* how big it is */ { int tries,n,c; long crc; /**/ for (tries= 10;;) { /* test is at 'again' */ xferstat(2,0L + blen,0,"Send File Info"); if (zabort()) return ERROR; /* manual intervention */ txhdr[ZF0]= lzconv; /* file conversion request */ txhdr[ZF1]= lzmanag; /* file management request */ txhdr[ZF2]= lztrans; /* file transport request */ txhdr[ZF3]= 0; zsbhdr(ZFILE,txhdr); zsdata(buf,blen,ZCRCW); again: if (--tries <= 0) break; /* count errors here */ c= zgethdr(rxhdr,1); switch (c) { case ZRINIT: while ((c= modin(50)) != TIMEOUT) { if (c == ZPAD) goto again; } break; case TIMEOUT: c= ZTIMEOUT; /* for report only */ case ZCAN: case ZABORT: case ZFIN: xferstat(3,0L + blen,0,"ZSendFile: %s",framenames[c]); return ERROR; case ZCRC: /* generate & send CRC */ crc= 0xFFFFFFFFL; while (read(f,&c,1) && --rxpos) crc= UPDC32(c,crc); crc= ~crc; stohdr(crc); zsbhdr(ZCRC,txhdr); errors= 0; /* reset error counter */ lseek(f,0L,0); goto again; case ZSKIP: return ZSKIP; case ZRPOS: /* * Suppress zcrcw request otherwise triggered by * lastsync==bytcnt */ lseek(f,rxpos,0); lastsync= (bytcnt= txpos= rxpos) - 1; return zsendfdata(f); } } xferstat(3,rxbytes,0,"ZSendFile: Too many retries!"); return ERROR; } /* Send the data in the file */ static zsendfdata(f) int f; /* open file */ { int c,e,n; int junkcount; /* Counts garbage chars received by Tx */ FLAG did_zcrcq; int goodinarow; int usedmaxblock,maxblock; lrxpos= 0; /* rx's last reported offset (none) */ junkcount= 0; /* extraneous characters */ beenhereb4= 0; /* same-bad-place counter */ goodinarow= 0; /* blocks in a row with no errors */ usedmaxblock= 0; /* 1 == sending at max. block size */ maxblock= zmblkmax; /* local largest-block size */ /* Look for asynchronous error packets. */ somemore: while (modstat()) { switch (modin(0)) { case CAN: case ZPAD: waitack: junkcount= 0; c= getinsync(f,1); gotack: switch (c) { case ZACK: /* we want this */ case ZRPOS: break; /* we want this */ case ZSKIP: return c; case ZRINIT: return OK; case ZCAN: default: if ((c < 0) || (c > ZTIMEOUT)) c= ZTIMEOUT; xferstat(3,0L + txpos,0,"ZSendFData: %s",framenames[c]); return ERROR; } break; case XOFF: /* Wait a while for an XON */ case XOFF|0200: modin(1000); default: ++junkcount; goodinarow= 0; break; } } stohdr(txpos); zsbhdr(ZDATA,txhdr); /* start a new frame */ if (zmttype <= 64) txwsize= blklen * zmttype; /* go this many blocks */ else txwsize= zmttype & ~1023; /* or Ks */ txwspace= txwsize >> 1; /* without ACK */ did_zcrcq= 0; /* windowing: ZCRCQ once */ do { /* If many good blocks in a row, increase the block size. The flag 'usedmaxblock' is set when we increase the block size up to the maximum; if it remains set (ie. no errors) and we get another 20 good ones, increase the maximum block size itself. The flag is set only when the block size is actually increased; any error clears the flag. */ if (++goodinarow >= 20) { /* if many good blocks */ if (usedmaxblock) { /* if good error rate */ n= maxblock; /* increase ceiling */ maxblock *= 2; /* half max. block size */ maxblock= min(maxblock,zmblkmax); if (n != maxblock) xferstat(3,txpos,0,"Good error rate: %d byte ceiling",maxblock); } n= blklen; /* (save for report) */ blklen += blklen; /* double block size */ blklen= min(blklen,BLKSIZE); /* Zmodem spec maximum */ blklen= min(blklen,maxblock); /* our local buffer */ if (rxbuflen != 0) /* if Rx gave buffer size */ blklen= min(blklen,rxbuflen); /* use that */ if (blklen != n) { /* increased block size */ ++usedmaxblock; xferstat(3,txpos,0,"%d byte blocks",blklen); if (zmttype <= 64) { /* if block based */ txwsize= blklen * zmttype; /* calc Ks */ txwspace= txwsize >> 1; /* window size */ } } goodinarow= 0; /* reset it */ } /* If an error, drop the block size. If the error occurred with 'usedmaxblock' set (meaning: we just increased to the max. block) decrease the maximum block size. This will not happen on runs of errors, only after a block increase. */ if (beenhereb4 > 1) { /* shorten block size */ if (usedmaxblock) { /* if we errored using */ n= maxblock; /* max block, */ maxblock /= 2; /* half max. block size */ maxblock= min(maxblock,256); /* 256 min max block size */ if (n != maxblock) xferstat(3,txpos,0,"Bad error rate: %d byte ceiling",maxblock); } n= blklen; /* shorten current */ blklen /= 2; /* block size */ if (blklen < 64) blklen= 64; if (blklen != n) { /* report block size */ xferstat(3,txpos,0,"%d byte blocks",blklen); } usedmaxblock= 0; /* obviously we did not */ } /* Now we finally get around to sending some data. */ n= zfilbuf(f); /* read some file data */ if (eofseen) e= ZCRCE; /* will be last block */ else if (junkcount > 3) e= ZCRCW; /* demand a response */ else if (bytcnt == lastsync) e= ZCRCW; /* ditto (expect EOF) */ else if (rxbuflen) e= ZCRCW; /* receiver has a small buffer */ else if (cantovio) e= ZCRCW; /* cant overlap disks & chars */ else if (txwindow && (txpos - lrxpos >= txwspace)) { e= did_zcrcq++ ? ZCRCG : ZCRCQ; } else e= ZCRCG; xferstat(2,txpos,0,"Send Data (%s)",Zendnames[e - ZCRCE & 3]); if (zabort()) return ERROR; /* manual intervention */ zsdata(zmbuff,n,e); /* send data & type */ bytcnt= (txpos += n); /* we sent this much */ if (e == ZCRCW) goto waitack; /* now go wait */ /* Look for error packets */ while (modstat()) { /* check for any response */ switch (modin(0)) { case CAN: case ZPAD: /* some sort of error */ c= getinsync(f,1); if (c == ZACK) break; goodinarow= 0; /* oops got a bad 'un */ xferstat(3,lrxpos,0,"Error recovery"); ++totl_recoveries; if (c != ZRPOS) zsdata("",0,ZCRCE); goto gotack; case XOFF: /* Wait a while for an XON */ case XOFF|0200: /* Fall through */ modin(rxtimeout); default: ++junkcount; goodinarow= 0; /* be conservative */ break; } } /* If we have a window in force, wait for ZACK or ZRPOS before continuing; send empty data packets to fill time. */ if (txwindow) { while (txpos - lrxpos >= txwsize) { xferstat(2,txpos,0,"Window Sync"); if (e != ZCRCQ) zsdata("",0,e= ZCRCQ); /* NOTE: Since the modem and system might have a lot of in-transit data in it, we might have to wait a while for a response. If we get a TIMEOUT, try for at least 60 seconds. */ for (n= 6000 / rxtimeout; n--;) { c= getinsync(f,1); /* wait for anything */ if (c != TIMEOUT) break;/* but TIMEOUT */ } if (c != ZACK) { xferstat(3,lrxpos,0,"Window error recovery"); ++totl_recoveries; zsdata("",0,ZCRCE); goodinarow= 0; goto gotack; } } } } while (!eofseen); /* End of file; say so and wait for a response. */ for (n= 10; n--;) { stohdr(txpos); /* send total Tx byte count */ zsbhdr(ZEOF,txhdr); switch (getinsync(f,0)) { case ZACK: break; /* old ACKs? */ case ZRPOS: goto somemore; /* oops, need to retransmit */ case ZSKIP: return c; /* skip this file */ case ZRINIT: return OK; /* huh? */ default: return ERROR; /* what the fuck */ } if (zabort()) break; } return ERROR; } /* * Respond to receiver's complaint, get back in sync with receiver */ static getinsync(f,flag) int f; /* open file */ int flag; { int tries,n,c; /**/ for (tries= 10; tries--;) { if (zabort()) return ERROR; /* manual intervention */ c= zgethdr(rxhdr,0); switch (c) { case ZCAN: case ZABORT: case ZFIN: xferstat(3,0L + lrxpos,0,"GetInSync: %s",framenames[c]); return ERROR; case ZRPOS: /* (flush modem output buffer here) */ lseek(f,rxpos,0); /* reseek */ eofseen= 0; /* rediscover EOF later, maybe */ bytcnt= lrxpos= txpos= rxpos; /* OK, start from here */ if (lastsync == rxpos) /* detect repeated tries */ ++beenhereb4; /* flag repeats */ else beenhereb4= 0; lastsync= rxpos; return c; case ZACK: lrxpos= rxpos; if (flag || txpos == rxpos) return ZACK; continue; case ZRINIT: case ZSKIP: return c; case ERROR: default: zsbhdr(ZNAK,txhdr); continue; } } xferstat(3,rxbytes,0,"GetInSync: Too many retries!"); return ERROR; } /* End the transmit session, try to do it cleanly */ static sendend() { int tries; /**/ for (tries= 10; tries--;) { if (zabort()) return ERROR; /* manual intervention */ stohdr(0L); /* CAF Was zsbhdr - minor change */ zshhdr(ZFIN,txhdr); /* to make debugging easier */ switch (zgethdr(rxhdr,0)) { case ZFIN: modout('O'); modout('O'); flush(0); case ZCAN: case TIMEOUT: return; } } xferstat(3,rxbytes,0,"SendEnd: Too many retries!"); return ERROR; }