#define VERSION "sz 1.26 03-10-87"
#define PUBDIR "/usr/spool/uucppublic"
/*% cc -O -K -i -DNFGVMIN -DREADCHECK sz.c -lx -o sz; size sz
* (above for SYS III/V Xenix)
* sz2.c By Chuck Forsberg
*
* cc -O sz?.c -o sz USG (SYS III/V) Unix
* cc -O -DSVR2 sz.c -o sz Sys V Release 2 with non-blocking input
* Define to allow reverse channel checking
* cc -O -DV7 sz.c -o sz Unix Version 7, 2.8 - 4.3 BSD
*
* cc -O -K -i -DNFGVMIN -DREADCHECK sz.c -lx -o sz Xenix
*
* ln sz sb **** All versions ****
*
*
* ******* Some systems (Venix, Coherent, Regulus) do not *******
* ******* support tty raw mode read(2) identically to *******
* ******* Unix. ONEREAD must be defined to force one *******
* ******* character reads for these systems. *******
*
* A program for Unix to send files and commands to computers running
* Professional-YAM, PowerCom, YAM, IMP, or programs supporting Y/XMODEM.
*
* Sz uses buffered I/O to greatly reduce CPU time compared to UMODEM.
*
* USG UNIX (3.0) ioctl conventions courtesy Jeff Martin
*/
extern char *substr(), *getenv();
#define LOGFILE "/tmp/szlog"
#define zperr vfile
#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#include <ctype.h>
#define PATHLEN 256
#define OK 0
#define FALSE 0
#define TRUE 1
#define ERROR (-1)
#define HOWMANY 2
extern int Zmodem; /* ZMODEM protocol requested */
extern unsigned Baudrate;
extern int Fromcu; /* Were called from cu or yam */
#include "bb.c" /* most of the system dependent stuff here */
#include "zmodem2.h"
#define sendline(c) putchar(c & Wcsmask)
extern int iofd, Twostop, Nozmodem;
int onintr();
extern char *Progname;
/*
* Attention string to be executed by receiver to interrupt streaming data
* when an error is detected. A pause (0336) may be needed before the
* ^C (03) or after it.
*/
extern char Myattn[];
extern FILE *in;
/* Ward Christensen / CP/M parameters - Don't change these! */
#define ENQ 005
#define CAN ('X'&037)
#define XOFF ('s'&037)
#define XON ('q'&037)
#define SOH 1
#define STX 2
#define EOT 4
#define ACK 6
#define NAK 025
#define CPMEOF 032
#define WANTCRC 0103 /* send C not NAK to get crc not checksum */
#define WANTG 0107 /* Send G not NAK to get nonstop batch xmsn */
#define TIMEOUT (-2)
#define RCDO (-3)
#define RETRYMAX 10
#define SECSIZ 128 /* cp/m's Magic Number record size */
#define KSIZE 1024
extern char Lastrx;
extern char Crcflg;
extern int Wcsmask;
extern int Verbose;
extern int Modem; /* MODEM - don't send pathnames */
extern int Restricted; /* restricted; no /.. or ../ in filenames */
extern int Quiet; /* overrides logic that would otherwise set verbose */
extern int Ascii; /* Add CR's for brain damaged programs */
extern int Fullname; /* transmit full pathname */
extern int Unlinkafter; /* Unlink file after it is sent */
extern int Dottoslash; /* Change foo.bar.baz to foo/bar/baz */
extern int firstsec;
extern int errcnt; /* number of files unreadable */
extern int blklen; /* length of transmitted records */
extern int Optiong; /* Let it rip no wait for sector ACK's */
extern int Noeofseen;
extern int Totsecs; /* total number of sectors this file */
extern char txbuf[];
extern int Filcnt; /* count of number of files opened */
extern int Lfseen;
extern unsigned Rxbuflen; /* Receiver's max buffer length */
extern int Tframlen; /* Override for tx frame length */
extern int blkopt; /* Override value for zmodem blklen */
extern int Rxflags;
extern int Wantfcs32; /* want to send 32 bit FCS */
extern char Lzconv; /* Local ZMODEM file conversion request */
extern char Lzmanag; /* Local ZMODEM file management request */
extern char Lztrans;
extern char zconv; /* ZMODEM file conversion request */
extern char zmanag; /* ZMODEM file management request */
extern char ztrans; /* ZMODEM file transport request */
extern int Command; /* Send a command, then exit. */
extern char *Cmdstr; /* Pointer to the command string */
extern int Cmdtries;
extern int Cmdack1; /* Rx ACKs command, then do it */
extern int Exitcode;
extern int Testattn; /* Force receiver to send Attn, etc with qbf. */
extern char *qbf;
extern long Lastread; /* Beginning offset of last buffer read */
extern int Lastc; /* Count of last buffer read or -1 */
extern int Dontread; /* Don't read the buffer, it's still there */
extern jmp_buf tohere; /* For the interrupt on RX timeout */
extern jmp_buf intrjmp; /* For the interrupt on RX CAN */
extern int alrm();
/*
* readock(timeout, count) reads character(s) from file descriptor 0
* (1 <= count <= 3)
* it attempts to read count characters. If it gets more than one,
* it is an error unless all are CAN
* (otherwise, only normal response is ACK, CAN, or C)
* Only looks for one if Optiong, which signifies cbreak, not raw input
*
* timeout is in tenths of seconds
*/
readock(timeout, count)
{
register int c;
static char byt[5];
if (Optiong)
count = 1; /* Special hack for cbreak */
fflush(stdout);
if (setjmp(tohere)) {
logent("TIMEOUT\n");
return TIMEOUT;
}
c = timeout/10;
if (c<2)
c=2;
if (Verbose>3) {
fprintf(stderr, "Timeout=%d Calling alarm(%d) ", timeout, c);
byt[1] = 0;
}
signal(SIGALRM, alrm); alarm(c);
#ifdef ONEREAD
c=read(iofd, byt, 1); /* regulus raw read is unique */
#else
c=read(iofd, byt, count);
#endif
alarm(0);
if (Verbose>5)
fprintf(stderr, "ret cnt=%d %x %x\n", c, byt[0], byt[1]);
if (c<1)
return TIMEOUT;
if (c==1)
return (byt[0]&0377);
else
while (c)
if (byt[--c] != CAN)
return ERROR;
return CAN;
}
readline(n)
{
return (readock(n, 1));
}
purgeline()
{
#ifdef USG
ioctl(iofd, TCFLSH, 0);
#else
lseek(iofd, 0L, 2);
#endif
}
/* send cancel string to get the other end to shut up */
canit()
{
static char canistr[] = {
24,24,24,24,24,24,24,24,24,24,8,8,8,8,8,8,8,8,8,8,0
};
printf(canistr);
fflush(stdout);
}
/*VARARGS1*/
logent(a, b, c)
char *a, *b, *c;
{
if(Verbose)
fprintf(stderr, a, b, c);
}
/*
* return 1 iff stdout and stderr are different devices
* indicating this program operating with a modem on a
* different line
*/
from_cu()
{
struct stat a, b;
fstat(1, &a); fstat(2, &b);
return (a.st_rdev != b.st_rdev);
}
/*
* substr(string, token) searches for token in string s
* returns pointer to token within string if found, NULL otherwise
*/
char *
substr(s, t)
register char *s,*t;
{
register char *ss,*tt;
/* search for first char of token */
for (ss=s; *s; s++)
if (*s == *t)
/* compare token with substring */
for (ss=s,tt=t; ;) {
if (*tt == 0)
return s;
if (*ss++ != *tt++)
break;
}
return NULL;
}
char *babble[] = {
"Send file(s) with ZMODEM/YMODEM/XMODEM Protocol",
" (Y) = Option applies to YMODEM only",
" (Z) = Option applies to ZMODEM only",
"Usage: sz [-12+aBbdefkLlNnquvXy] [-] file ...",
" sz [-1Beqv] -c COMMAND",
" 1 Use stdout for modem input",
#ifdef CSTOPB
" 2 Use 2 stop bits",
#endif
" + Append to existing destination file (Z)",
" a (ASCII) change NL to CR/LF",
" b Binary file transfer override",
" c send COMMAND (Z)",
" d Change '.' to '/' in pathnames (Y/Z)",
" e Escape all control characters (Z)",
" f send Full pathname (Y/Z)",
" i send COMMAND, ack Immediately (Z)",
" k Send 1024 byte packets (Y)",
" L N Limit subpacket length to N bytes (Z)",
" l N Limit frame length to N bytes (l>=L) (Z)",
" n send file if source Newer or longer (Z)",
" N send file if source different length or date (Z)",
" o Use 16 bit CRC instead of 32 bit CRC (Z)",
" p Protect existing destination file (Z)",
" r Resume/Recover interrupted file transfer (Z)",
" q Quiet (no progress reports)",
" u Unlink file after transmission",
" v Verbose - debugging information",
" X XMODEM protocol - send no pathnames",
" y Yes, overwrite existing file (Z)",
"- as pathname sends standard input as sPID.sz or environment ONAME",
""
};
usage()
{
char **pp;
for (pp=babble; **pp; ++pp)
fprintf(stderr, "%s\n", *pp);
fprintf(stderr, "%s for %s by Chuck Forsberg ", VERSION, OS);
exit(1);
}
/*
* Get the receiver's init parameters
*/
getzrxinit()
{
register n;
struct stat f;
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));
Rxbuflen = (0377 & Rxhdr[ZP0])+((0377 & Rxhdr[ZP1])<<8);
vfile("Rxbuflen=%d Tframlen=%d", Rxbuflen, Tframlen);
if ( !Fromcu)
signal(SIGINT, SIG_IGN);
#ifdef USG
mode(2); /* Set cbreak, XON/XOFF, etc. */
#endif
#ifndef READCHECK
#ifndef USG
/* Use 1024 byte frames if no sample/interrupt */
if (Rxbuflen < 32 || Rxbuflen > 1024) {
Rxbuflen = 1024;
vfile("Rxbuflen=%d", Rxbuflen);
}
#endif
#endif
/* Override to force shorter frame length */
if (Rxbuflen && (Rxbuflen>Tframlen) && (Tframlen>=32))
Rxbuflen = Tframlen;
if ( !Rxbuflen && (Tframlen>=32) && (Tframlen<=1024))
Rxbuflen = Tframlen;
vfile("Rxbuflen=%d", Rxbuflen);
/* If using a pipe for testing set lower buf len */
fstat(iofd, &f);
if ((f.st_mode & S_IFMT) != S_IFCHR
&& (Rxbuflen == 0 || Rxbuflen > 4096))
Rxbuflen = 4096;
/*
* If input is not a regular file, force ACK's each 1024
* (A smarter strategey could be used here ...)
*/
fstat(fileno(in), &f);
if (((f.st_mode & S_IFMT) != S_IFREG)
&& (Rxbuflen == 0 || Rxbuflen > 1024))
Rxbuflen = 1024;
vfile("Rxbuflen=%d", Rxbuflen);
return (sendzsinit());
case ZCAN:
case TIMEOUT:
return ERROR;
case ZRQINIT:
if (Rxhdr[ZF0] == ZCOMMAND)
continue;
default:
zshhdr(ZNAK, Txhdr);
continue;
}
}
return ERROR;
}
/* Send send-init information */
sendzsinit()
{
register c;
register errors;
if (Myattn[0] == '\0')
return OK;
errors = 0;
for (;;) {
stohdr(0L);
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 > 9)
return ERROR;
continue;
}
}
}
/* Send file name and related info */
zsendfile(buf, blen)
char *buf;
{
register c;
for (;;) {
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:
c = zgethdr(Rxhdr, 1);
switch (c) {
case ZRINIT:
goto again;
case ZCAN:
case TIMEOUT:
case ZABORT:
case ZFIN:
return ERROR;
case ZSKIP:
fclose(in); return c;
case ZRPOS:
fseek(in, Rxpos, 0);
Txpos = Rxpos; Lastc = -1; Dontread = FALSE;
return zsendfdata();
default:
continue;
}
}
}
/* Send the data in the file */
zsendfdata()
{
register c, e;
register newcnt;
register long tcount = 0;
static int tleft = 6; /* Counter for test mode */
if (Baudrate > 300)
blklen = 256;
if (Baudrate > 2400)
blklen = KSIZE;
if (Rxbuflen && blklen>Rxbuflen)
blklen = Rxbuflen;
if (blkopt && blklen > blkopt)
blklen = blkopt;
vfile("Rxbuflen=%d blklen=%d", Rxbuflen, blklen);
somemore:
if (setjmp(intrjmp)) {
waitack:
c = getinsync();
switch (c) {
default:
case ZCAN:
fclose(in);
return ERROR;
case ZSKIP:
fclose(in);
return c;
case ZACK:
case ZRPOS:
break;
case ZRINIT:
return OK;
}
#ifdef READCHECK
/*
* 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(iofd)) {
#ifdef SVR2
switch (checked)
#else
switch (readline(1))
#endif
{
case CAN:
case ZPAD:
goto waitack;
case XOFF: /* Wait a while for an XON */
case XOFF|0200:
readline(100);
}
}
#endif
}
if ( !Fromcu)
signal(SIGINT, onintr);
newcnt = Rxbuflen;
stohdr(Txpos);
zsbhdr(ZDATA, Txhdr);
/*
* Special testing mode. This should force receiver to Attn,ZRPOS
* many times. Each time the signal should be caught, causing the
* file to be started over from the beginning.
*/
if (Testattn) {
if ( --tleft)
while (tcount < 20000) {
printf(qbf); fflush(stdout);
tcount += strlen(qbf);
#ifdef READCHECK
while (rdchk(iofd)) {
#ifdef SVR2
switch (checked)
#else
switch (readline(1))
#endif
{
case CAN:
case ZPAD:
#ifdef TCFLSH
ioctl(iofd, TCFLSH, 1);
#endif
goto waitack;
case XOFF: /* Wait for XON */
case XOFF|0200:
readline(100);
}
}
#endif
}
signal(SIGINT, SIG_IGN); canit();
sleep(3); purgeline(); mode(0);
printf("\nsz: Tcount = %ld\n", tcount);
if (tleft) {
printf("ERROR: Interrupts Not Caught\n");
exit(1);
}
exit(0);
}
do {
if (Dontread) {
c = Lastc;
} else {
c = zfilbuf(txbuf, blklen);
Lastread = Txpos; Lastc = c;
}
if (Verbose > 10)
vfile("Dontread=%d c=%d", Dontread, c);
Dontread = FALSE;
if (c < blklen)
e = ZCRCE;
else if (Rxbuflen && (newcnt -= c) <= 0)
e = ZCRCW;
else
e = ZCRCG;
zsdata(txbuf, c, e);
Txpos += c;
if (e == ZCRCW)
goto waitack;
#ifdef READCHECK
/*
* 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
*/
fflush(stdout);
while (rdchk(iofd)) {
#ifdef SVR2
switch (checked)
#else
switch (readline(1))
#endif
{
case CAN:
case ZPAD:
#ifdef TCFLSH
ioctl(iofd, TCFLSH, 1);
#endif
/* zcrce - dinna wanna start a ping-pong game */
zsdata(txbuf, 0, ZCRCE);
goto waitack;
case XOFF: /* Wait a while for an XON */
case XOFF|0200:
readline(100);
}
}
#endif
} while (c == blklen);
if ( !Fromcu)
signal(SIGINT, SIG_IGN);
for (;;) {
stohdr(Txpos);
zsbhdr(ZEOF, Txhdr);
switch (getinsync()) {
case ZACK:
continue;
case ZRPOS:
goto somemore;
case ZRINIT:
return OK;
case ZSKIP:
fclose(in);
return c;
default:
fclose(in);
return ERROR;
}
}
}
/*
* Respond to receiver's complaint, get back in sync with receiver
*/
getinsync()
{
register c;
for (;;) {
if (Testattn) {
printf("\r\n\n\n***** Signal Caught *****\r\n");
Rxpos = 0; c = ZRPOS;
} else
c = zgethdr(Rxhdr, 0);
switch (c) {
case ZCAN:
case ZABORT:
case ZFIN:
case TIMEOUT:
return ERROR;
case ZRPOS:
if (Lastc >= 0 && Lastread == Rxpos) {
Dontread = TRUE;
} else {
clearerr(in); /* In case file EOF seen */
fseek(in, Rxpos, 0);
}
Txpos = Rxpos;
return c;
case ZACK:
return c;
case ZRINIT:
case ZSKIP:
fclose(in);
return c;
case ERROR:
default:
zsbhdr(ZNAK, Txhdr);
continue;
}
}
}
/* Say "bibi" to the receiver, try to do it cleanly */
saybibi()
{
for (;;) {
stohdr(0L);
zsbhdr(ZFIN, Txhdr);
switch (zgethdr(Rxhdr, 0)) {
case ZFIN:
sendline('O'); sendline('O'); flushmo();
case ZCAN:
case TIMEOUT:
return;
}
}
}
/* Local screen character display function */
bttyout(c)
{
if (Verbose)
putc(c, stderr);
}
/* Send command and related info */
zsendcmd(buf, blen)
char *buf;
{
register c, errors;
long cmdnum;
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:
continue;
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 > 10)
return ERROR;
continue;
case ZCOMPL:
Exitcode = Rxpos;
saybibi();
return OK;
case ZRQINIT:
vfile("******** RZ *******");
system("rz");
vfile("******** SZ *******");
goto listen;
}
}
}
/*
* If called as sb use YMODEM protocol
*/
chkinvok(s)
char *s;
{
register char *p;
p = s;
while (*p == '-')
s = ++p;
while (*p)
if (*p++ == '/')
s = p;
if (*s == 'v') {
Verbose=1; ++s;
}
Progname = s;
if (s[0]=='s' && s[1]=='b') {
Nozmodem = TRUE; blklen=KSIZE;
}
}