/* Utils.c: Miscellaneous support routines for xprzmodem.library; Version 1.0, 29 July 1989, by Rick Huebner. Released to the Public Domain; do as you like with this code. */ #include #include "aztec.h" #include "xproto.h" #include "zmodem.h" #include "defs.h" /* Label and version info for .library file */ char XPRname[] = "xprzmodem.library"; char XPRid[] = "xprzmodem 1.0, 29 July 89\r\n"; short XPRrevision = 0; /* Version number is XPRVERSION in rtag.asm */ /* Transfer options to use if XProtocolSetup not called */ char Default_Config[] = "T?,ON,B16,F0"; #ifdef DEBUG UBYTE DebugName[] = "Log:ZDebug.log"; long DebugLog = NULL; #endif /* Called by terminal program to set transfer options */ long XProtocolSetup(io) register struct XPR_IO *io; { UBYTE buf[256], t, o; register UBYTE *p; register long len, b, f; /* Allocate memory for transfer options string */ if (!io->xpr_data) { io->xpr_data = AllocMem((long)CONFIGLEN,0L); if (!io->xpr_data) { ioerr(io,"Not enough memory for ZModem config string"); return 0; } /* Start out with default options; merge user changes into defaults */ strcpy(io->xpr_data,Default_Config); } /* Extract current settings from options string */ t = *(strchr(io->xpr_data,'T')+1); o = *(strchr(io->xpr_data,'O')+1); b = atol(strchr(io->xpr_data,'B')+1); f = atol(strchr(io->xpr_data,'F')+1); /* If config string passed by term prog, use it; else prompt user */ if (io->xpr_filename) strcpy(buf,io->xpr_filename); else { /* Start buffer with current settings so user can see/edit them in place */ strcpy(buf,io->xpr_data); if (io->xpr_gets) callaa(io->xpr_gets,"ZModem options:",buf); } /* Upshift config string for easier parsing */ for (p=buf; *p; ++p) *p = toupper(*p); /* Merge new T(ext) option into current settings if given */ /* "TY" = Force Text mode on, "TN" = Force Text mode off, "T?" = Use other end's text mode suggestion (default to binary) */ if (p = strchr(buf,'T')) { ++p; if (*p == 'Y' || *p == 'N' || *p == '?') t = *p; else ioerr(io,"Invalid T flag ignored; should be Y, N, or ?"); } /* Merge new O(verwrite) option into current settings if given */ /* "OY" = Yes, delete old file and replace with new one, "ON" = No, prevent overwrite by appending ".dup" to avoid name collision, "OR" = Resume transfer at end of existing file, "OS" = Skip file if it already exists; go on to next */ if (p = strchr(buf,'O')) { ++p; if (*p == 'R' && !io->xpr_finfo) ioerr(io,"Can't Resume; xpr_finfo() not supported"); else if (*p == 'Y' || *p == 'N' || *p == 'R' || *p == 'S') o = *p; else ioerr(io,"Invalid O flag ignored; should be Y, N, R, or S"); } /* Merge new B(uffer) setting into current settings if given */ /* Size of file I/O buffer in kilobytes */ if (p = strchr(buf,'B')) { len = atol(++p); if (len < 1) len = 1; b = len; } /* Merge new F(ramelength) setting into other settings if given */ /* Number of bytes we're willing to send or receive between ACKs. 0 = unlimited; nonstop streaming data */ if (p = strchr(buf,'F')) { len = atol(++p); if (len < 0) len = 0; if (len > 0 && len < MINBLOCK) len = MINBLOCK; f = len; } /* Update config string with new settings */ sprintf(io->xpr_data,"T%c,O%c,B%ld,F%ld",t,o,b,f); return 1; } /* Called by terminal program to give us a chance to clean up before program ends */ long XProtocolCleanup(io) register struct XPR_IO *io; { /* Release config option memory, if any */ if (io->xpr_data) { FreeMem(io->xpr_data,(long)CONFIGLEN); io->xpr_data = NULL; } return 1; } /* Perform setup and initializations common to both Send and Receive routines */ struct Vars *setup(io) register struct XPR_IO *io; { static long bauds[] = { 110,300,1200,2400,4800,9600,19200,38400,38400,57600,76800,115200 }; register struct Vars *v; register long newstatus; /* Make sure terminal program supports the required call-back functions */ if (!io->xpr_update) return NULL; if (!io->xpr_fopen || !io->xpr_fclose || !io->xpr_fread || !io->xpr_fwrite || !io->xpr_fseek || !io->xpr_sread || !io->xpr_swrite) { ioerr(io,"Term prog missing required function(s); see docs"); return NULL; } /* Hook in default transfer options if XProtocolSetup wasn't called */ if (!io->xpr_data) { io->xpr_data = AllocMem((long)CONFIGLEN,0L); if (!io->xpr_data) { ioerr(io,"Not enough memory for ZModem config string"); return NULL; } strcpy(io->xpr_data,Default_Config); } /* Allocate memory for our unshared variables, to provide reentrancy */ if (!(v = AllocMem((long)sizeof(struct Vars),MEMF_CLEAR))) { nomem: ioerr(io,"Not enough memory for xprzmodem"); return NULL; } /* Allocate memory for our file I/O buffer; if we can't get as much as requested, keep asking for less until we hit minimum before giving up */ v->Filebufmax = atol(strchr(io->xpr_data,'B')+1) * 1024; while (!(v->Filebuf = AllocMem(v->Filebufmax,0L))) { if (v->Filebufmax > 1024) v->Filebufmax -= 1024; else { FreeMem(v,(long)sizeof(struct Vars)); goto nomem; } } /* Copy caller's io struct into our Vars for easier passing */ v->io = *io; #ifdef DEBUG if (!DebugLog) DebugLog = callaa(v->io.xpr_fopen,DebugName,"w"); #endif /* Initialize Vars as required */ switch(*(strchr(io->xpr_data,'T')+1)) { case 'Y': v->Rxascii = TRUE; v->Rxbinary = FALSE; v->Lzconv = ZCNL; break; case 'N': v->Rxascii = FALSE; v->Rxbinary = TRUE; v->Lzconv = ZCBIN; break; case '?': v->Rxascii = v->Rxbinary = FALSE; v->Lzconv = 0; break; } v->Tframlen = atol(strchr(io->xpr_data,'F')+1); /* Get baud rate; set serial port mode if necessary (and possible) */ if (v->io.xpr_setserial) { v->Oldstatus = calld(v->io.xpr_setserial,-1L); /* ZModem requires 8 data bits, no parity (full transparency), leave other settings alone */ newstatus = v->Oldstatus & 0xFFFFE0BC; /* newstatus |= on_flags; */ if (newstatus != v->Oldstatus) calld(v->io.xpr_setserial,newstatus); v->Baud = bauds[(newstatus>>16) & 0xFF]; #ifdef DEBUG sprintf(v->Msgbuf,"Old serial status = %lx, new = %lx, baud = %ld\n",v->Oldstatus,newstatus,v->Baud); dlog(v,v->Msgbuf); #endif /* If no xpr_setserial(), muddle along with most likely guess */ } else v->Baud = 2400; return v; } /* send cancel string to get the other end to shut up */ void canit(v) register struct Vars *v; { static char canistr[] = { 24,24,24,24,24,24,24,24,24,24,8,8,8,8,8,8,8,8,8,8,0 }; zmputs(v,canistr); } /* Send a string to the modem, with processing for \336 (sleep 1 sec) and \335 (break signal, ignored since XPR spec doesn't support it) */ void zmputs(v,s) register struct Vars *v; register UBYTE *s; { register short c; while (*s) { switch (c = *s++) { case '\336': TimeOut(50L); case '\335': break; default: sendline(v,c); } } } /* Write one character to the modem */ void xsendline(v,c) register struct Vars *v; UWORD c; { UBYTE buf = c; callad(v->io.xpr_swrite,&buf,1L); } /* Get a byte from the modem; return TIMEOUT if no read within timeout tenths of a second, return RCDO if carrier lost (supposedly; XPR spec doesn't support carrier detect, though). Added in some buffering so we wouldn't hammer the system with single-byte serial port reads. Also, the buffering makes char_avail() a lot easier to implement. */ short readock(v,tenths) register struct Vars *v; short tenths; { /* If there's data waiting in the buffer, return next byte */ if (v->Modemcount) { gotdata: --v->Modemcount; return *v->Modemchar++; } /* Buffer is empty; try to read more data into it */ v->Modemcount = calladd(v->io.xpr_sread,v->Modembuf,(long)sizeof(v->Modembuf),tenths*100000L); if (v->Modemcount < 1) { /* Didn't get anything within time limit; timeout */ v->Modemcount = 0; return TIMEOUT; } else { /* Got something; return first byte of it */ v->Modemchar = v->Modembuf; /* Reset buffer pointer to start of data */ goto gotdata; } } /* Check if there's anything available to read from the modem */ char char_avail(v) register struct Vars *v; { if (v->Modemcount) return TRUE; /* No data in our buffer; check system's input buffer */ v->Modemcount = calladd(v->io.xpr_sread,v->Modembuf,(long)sizeof(v->Modembuf),0L); if (v->Modemcount < 1) { /* Nothing in system buffer either */ v->Modemcount = 0; return FALSE; } else { /* System buffer had something waiting for us */ v->Modemchar = v->Modembuf; return TRUE; } } /* Update the elapsed time, expected total time, and effective data transfer rate values for status display */ void update_rate(v) register struct Vars *v; { static char *timefmt = "%2d:%02d:%02d"; register long sent, elapsed, expect; register short hr, min; /* Compute effective data rate so far in characters per second */ sent = v->xpru.xpru_bytes - v->Strtpos; /* Actual number of chars transferred */ elapsed = time(NULL) - v->Starttime; /* Time it took to send them */ if (elapsed < 1) elapsed = 1; /* If we haven't transferred anything yet (just starting), make reasonable guess (95% throughput); otherwise, compute actual effective transfer rate */ v->xpru.xpru_datarate = (sent) ? sent / elapsed : v->Baud * 95L / 1000; /* Compute expected total transfer time based on data rate so far */ if (v->xpru.xpru_filesize < 0) expect = 0; /* Don't know filesize; display time=0 */ else expect = (v->xpru.xpru_filesize - v->Strtpos) / v->xpru.xpru_datarate; hr = expect / (60*60); /* How many whole hours */ expect -= hr * (60*60); /* Remainder not counting hours */ min = expect / 60; /* How many whole minutes */ expect -= min * 60; /* Remaining seconds */ sprintf(v->Msgbuf,timefmt,hr,min,(short)expect); v->xpru.xpru_expecttime = (char *)v->Msgbuf; /* Compute elapsed time for this transfer so far */ hr = elapsed / (60*60); elapsed -= hr * (60*60); min = elapsed / 60; elapsed -= min * 60; sprintf(v->Msgbuf+20,timefmt,hr,min,(short)elapsed); v->xpru.xpru_elapsedtime = (char *)v->Msgbuf+20; } /* Buffered file I/O fopen() interface routine */ long bfopen(v,mode) register struct Vars *v; UBYTE *mode; { /* Initialize file-handling variables */ v->Filebufpos = v->Filebuflen = v->Filebufcnt = 0; v->Fileflush = FALSE; v->Filebufptr = v->Filebuf; /* Open the file */ return callaa(v->io.xpr_fopen,v->Filename,mode); } /* Buffered file I/O fclose() interface routine */ void bfclose(v) register struct Vars *v; { /* If bfwrite() left data lingering in buffer, flush it out before closing */ if (v->Fileflush) calladda(v->io.xpr_fwrite,v->Filebuf,1L,v->Filebufcnt,v->File); /* Close the file */ calla(v->io.xpr_fclose,v->File); v->File = NULL; } /* Buffered file I/O fseek() interface routine */ void bfseek(v,pos) register struct Vars *v; register long pos; { register long offset; /* If new file position is within currently buffered section, reset pointers */ if (pos >= v->Filebufpos && pos <= v->Filebufpos + v->Filebuflen - 1) { offset = pos - v->Filebufpos; v->Filebufptr = v->Filebuf + offset; v->Filebufcnt = v->Filebuflen - offset; /* Otherwise, fseek() file and discard buffer contents to force new read */ } else { calladd(v->io.xpr_fseek,v->File,pos,0L); v->Filebuflen = v->Filebufcnt = 0; v->Filebufpos = pos; } } /* Buffered file I/O fread() interface routine */ long bfread(v,buf,length) register struct Vars *v; UBYTE *buf; register long length; { register long count, total = 0; /* If there's already data buffered up, try to get what we need from there */ if (v->Filebufcnt) { readmore: count = (length <= v->Filebufcnt) ? length : v->Filebufcnt; CopyMem(v->Filebufptr,buf,count); #ifdef DEBUG sprintf(v->Msgbuf,"bfread got %ld bytes from buffer\n",count); dlog(v,v->Msgbuf); #endif total += count; v->Filebufptr += count; v->Filebufcnt -= count; } /* If there wasn't enough in the buffer, read next buffer's worth and try again */ if (total < length) { v->Filebufpos += v->Filebuflen; v->Filebufptr = v->Filebuf; v->Filebufcnt = v->Filebuflen = calladda(v->io.xpr_fread,v->Filebuf,1L,v->Filebufmax,v->File); #ifdef DEBUG sprintf(v->Msgbuf,"bfread read %ld bytes\n",v->Filebuflen); dlog(v,v->Msgbuf); #endif if (v->Filebufcnt) goto readmore; /* else we couldn't read as much as requested; return partial count */ } return total; } /* Buffered file I/O fwrite() interface routine */ long bfwrite(v,buf,length) register struct Vars *v; register UBYTE *buf; register long length; { register long count, total = 0; /* Keep going until entire request completed */ while (length > 0) { /* Copy as much as will fit into the buffer */ count = v->Filebufmax - v->Filebufcnt; if (length < count) count = length; CopyMem(buf,v->Filebufptr,count); #ifdef DEBUG sprintf(v->Msgbuf,"bfwrite buffered %ld bytes\n",count); dlog(v,v->Msgbuf); #endif buf += count; total += count; length -= count; v->Filebufptr += count; v->Filebufcnt += count; v->Fileflush = TRUE; /* If we've filled the buffer, write it out */ if (v->Filebufcnt == v->Filebufmax) { count = calladda(v->io.xpr_fwrite,v->Filebuf,1L,v->Filebufcnt,v->File); #ifdef DEBUG sprintf(v->Msgbuf,"bfwrite wrote %ld bytes\n",count); dlog(v,v->Msgbuf); #endif if (count < v->Filebufcnt) return -1; v->Filebufptr = v->Filebuf; v->Filebufcnt = 0; v->Fileflush = FALSE; } } return total; } /* Have the terminal program display an error message for us, using a temporary XPR_UPDATE structure; used to display errors before Vars gets allocated */ void ioerr(io,msg) register struct XPR_IO *io; char *msg; { struct XPR_UPDATE xpru; if (io->xpr_update) { xpru.xpru_updatemask = XPRU_ERRORMSG; xpru.xpru_errormsg = msg; calla(io->xpr_update,&xpru); } } /* Have the terminal program display an error message for us, using the normal XPR_IO structure allocated in Vars */ void upderr(v,msg) register struct Vars *v; char *msg; { v->xpru.xpru_updatemask = XPRU_ERRORMSG; v->xpru.xpru_errormsg = msg; calla(v->io.xpr_update,&v->xpru); #ifdef DEBUG dlog(v,msg); dlog(v,"\n"); #endif } /* Have the terminal program display a normal message for us */ void updmsg(v,msg) register struct Vars *v; char *msg; { v->xpru.xpru_updatemask = XPRU_MSG; v->xpru.xpru_msg = msg; calla(v->io.xpr_update,&v->xpru); #ifdef DEBUG dlog(v,msg); dlog(v,"\n"); #endif } /* Figure out how many bytes are free on the drive we're uploading to. Stubbed out for now; not supported by XPR spec. */ long getfree() { return 0x7FFFFFFF; } /* Check whether file already exists; used to detect potential overwrites */ char exist(v) register struct Vars *v; { register long file; file = callaa(v->io.xpr_fopen,v->Filename,"r"); if (file) { calla(v->io.xpr_fclose,file); return TRUE; } else return FALSE; } #ifdef DEBUG /* Write a message to the debug log */ dlog(v,s) register struct Vars *v; register UBYTE *s; { /* Open the debug log if it isn't already open */ if (!DebugLog) DebugLog = callaa(v->io.xpr_fopen,DebugName,"a"); calladda(v->io.xpr_fwrite,s,1L,(long)strlen(s),DebugLog); /* Close file to flush output buffer; comment these two lines out if you aren't crashing your system and don't mind waiting until the transfer finishes to look at your log file. */ calla(v->io.xpr_fclose,DebugLog); DebugLog = NULL; } #endif /** * * The following functions setup the proper registers for the call-back * functions. * **/ #asm public _calla _calla: movea.l 8(sp),a0 ; Second argument goes in a0 ; Clever trick to allow indirect JSR without using register move.l 4(sp),-(sp) ; Push address of function to call rts ; "Return" to new function; its rts will... ; ...return to function who called us public _callaa _callaa: movea.l 8(sp),a0 ; Second argument goes in a0 movea.l 12(sp),a1 ; Third argument goes in a1 move.l 4(sp),-(sp) ; First argument is function rts public _callad _callad: movea.l 8(sp),a0 ; Second argument goes in a0 move.l 12(sp),d0 ; Third argument goes in d0 move.l 4(sp),-(sp) ; First argument is function rts public _calladd _calladd: movea.l 8(sp),a0 ; Second argument goes in a0 move.l 12(sp),d0 ; Third argument goes in d0 move.l 16(sp),d1 ; Fourth argument goes in d1 move.l 4(sp),-(sp) ; First argument is function rts public _calladda _calladda: movea.l 8(sp),a0 ; Second argument goes in a0 move.l 12(sp),d0 ; Third argument goes in d0 move.l 16(sp),d1 ; Fourth argument goes in d1 movea.l 20(sp),a1 ; Fifth argument goes in a1 move.l 4(sp),-(sp) ; First argument is function rts public _calld _calld: move.l 8(sp),d0 ; Second argument goes in d0 move.l 4(sp),-(sp) ; First argument is function rts public _calldaa _calldaa: move.l 8(sp),d0 ; Second argument goes in d0 movea.l 12(sp),a0 ; Third argument goes in a0 movea.l 16(sp),a1 ; Fourth argument goes in a1 move.l 4(sp),-(sp) ; First argument is function rts #endasm