#define VERSION "2.10 10-18-83" /*% /bin/env - /bin/ncc -DUSG -O -n % -o rb * * rb.c By Chuck Forsberg * * A program for Unix which can receive * files from computers running YAM or MODEM. * If no filename is given, YAM batch mode is assumed. * * Iff the program is invoked by rbCOMMAND, output is piped to * "COMMAND filename" * * Supports the CRC option or regular checksum. * Received pathnames containing no lowercase letters will be changed to lower * case unless -u option is given. * * Unless the -b (binary) option is given, \r is discarded and * ^Z (which is also discarded) acts as end of file. * * Any slashes in the pathname are changed to underscore. * If the raw pathname ends in .MSG or .TXT, any existing file will * be appended to rather than replaced. Trailing periods are eliminated. * * If the raw pathname ends in any of the extensions in Extensions, * or .?Q* (squeezed file), or if the first sector contains binary-like * data (parity bits or characters in the range 0 to 6 before ^Z is seen), * or if the transmitted file mode has the 0100000 but set, * that file will be received in binary mode anyway. * * * a log of activities is appended to LOGFILE with the -v option * if stdout and stderr refer to different devices, progress is displayed to * stderr. * * rb is derived from yam2.c and sb.c * rb uses Unix System III buffered input to reduce CPU time. * USG UNIX (3.0) ioctl conventions courtesy Jeff Martin * cc -O -DV7 rb.c -o rb vanilla Unix version 7 * cc -O -DUSG rb.c -o rb USG (3.0) Unix * cc -o rb.c Regulus * (Don't try this on Unix, you'll clobber the source!) * Unix is a trademark of Western Electric Company * * Regulus conventions 1-10-83 CAF */ #define LOGFILE "/tmp/rblog" #include #include #include FILE *popen(); #define OK 0 #define FALSE 0 #define TRUE 1 #define ERROR (-1) #define HOWMANY 133 #include "rbsb.c" /* most of the system dependent stuff here */ char *substr(); FILE *fout; char *Extensions[] = { ".A", ".ARC", ".CCC", ".CL", ".CMD", ".COM", ".CRL", ".DAT", ".DIR", ".EXE", ".O", ".OBJ", ".OVL", ".PAG", ".REL", ".SAV", ".SUB", ".SWP", ".SYS", ".TAR", ".UTL", ".a", ".o", ".tar", "" }; /* 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 TIMEOUT (-2) #define ERRORMAX 5 #define RETRYMAX 5 #define WCEOT (-10) #define SECSIZ 128 /* cp/m's Magic Number record size */ #define PATHLEN 257 /* ready for 4.2 bsd ? */ #define KSIZE 1024 /* record size with k option */ #define UNIXFILE 0x8000 /* happens to the the S_IFREG file mask bit for stat */ int Lastrx; int Crcflg; int Firstsec; int Eofseen; /* indicates cpm eof (^Z) has been received */ int totblocks; /* total number of blocks received */ int errors; #define DEFBYTL 2000000000L /* default rx file size */ long Bytesleft; /* number of bytes of incoming file left */ long Modtime; /* Unix style mod time for incoming file */ short Filemode; /* Unix style mode for incoming file */ char Pathname[PATHLEN]; char *Progname; /* the name by which we were called */ int Batch=0; int Wcsmask=0377; int Topipe=0; int MakeLCPathname=TRUE; /* make received pathname lower case */ int Verbose=0; int Quiet=0; /* overrides logic that would otherwise set verbose */ int Rxbinary=FALSE; /* receive all files in bin mode */ int Thisbinary; /* current file is to be received in bin mode */ int Blklen; /* record length of received packets */ char secbuf[KSIZE]; char linbuf[KSIZE]; int Lleft=0; /* number of characters in linbuf */ int Llorig; unsigned short updcrc(); /* Actual effect is to give an error return on read */ alrm() { signal(SIGALRM, alrm); } /* called by signal interrupt or terminate to clean things up */ bibi(n) { canit(); mode(0); fprintf(stderr, "sb: caught signal %d; exiting\r", n); exit(128+n); } main(argc, argv) char *argv[]; { register char *cp; register npats; char *virgin, **patts; int exitcode; setbuf(stderr, NULL); chkinvok(virgin=argv[0]); /* if called as [-]rbCOMMAND set flag */ npats = 0; while (--argc) { cp = *++argv; if (*cp == '-') { while( *++cp) { switch(*cp) { case '7': Wcsmask = 0177; case 'b': Rxbinary=TRUE; break; case 'k': case 'c': Crcflg=TRUE; break; case 'q': Quiet=TRUE; Verbose=0; break; case 'u': MakeLCPathname=FALSE; break; case 'v': ++Verbose; break; default: usage(); } } } else if ( !npats && argc>0) { if (argv[0][0]) { npats=argc; patts=argv; } } } if (npats > 1) usage(); if (Verbose) { if (freopen(LOGFILE, "a", stderr)==NULL) { printf("Can't open log file %s\n",LOGFILE); exit(0200); } setbuf(stderr, NULL); fprintf(stderr, "argv[0]=%s Progname=%s\n", virgin, Progname); } if (fromcu() && !Quiet) { if (Verbose == 0) Verbose = 2; } mode(1); if (signal(SIGINT, bibi) == SIG_IGN) { signal(SIGINT, SIG_IGN); signal(SIGKILL, SIG_IGN); } else { signal(SIGINT, bibi); signal(SIGKILL, bibi); } signal(SIGALRM, alrm); if (wcreceive(npats, patts)==ERROR) { exitcode=0200; canit(); } mode(0); if (exitcode != 0) /* bellow again with all thy might. */ canit(); #ifdef REGULUS else printf("\6\6\6\6\6\n"); /* Regulus doesn't wait ... */ #endif exit(exitcode); } usage() { fprintf(stderr,"%s %s by Chuck Forsberg\n", Progname, VERSION); fprintf(stderr,"Usage: rb [-7buv]\n\tor rb [-bcuv] file\n"); exit(1); } wcreceive(argc, argp) char **argp; { if (Batch || argc==0) { Crcflg=(Wcsmask==0377); fprintf(stderr, "rb: ready "); for (;;) { totblocks=0; if (wcrxpn(secbuf)== ERROR) goto fubar; if (secbuf[0]==0) return OK; if (procheader(secbuf) == ERROR) goto fubar; if (wcrx()==ERROR) goto fubar; } } else { totblocks=0; Bytesleft = DEFBYTL; Filemode = 0; Modtime = 0L; strcpy(Pathname, *argp); if (fopen(*argp, "r") != NULL) { fprintf(stderr, "rb: %s exists\r\n", Pathname); goto fubar; } fprintf(stderr, "\r\nrb: ready to receive %s ", Pathname); if ((fout=fopen(Pathname, "w")) == NULL) return ERROR; if (wcrx()==ERROR) goto fubar; } return OK; fubar: canit(); if (Topipe && fout) { pclose(fout); return ERROR; } if (fout) fclose(fout); return ERROR; } /* * Fetch a pathname from the other end as a C ctyle ASCIZ string. * Length is indeterminate as long as less than Blklen * a null string represents no more files */ wcrxpn(rpn) char *rpn; /* receive a pathname */ { purgeline(); Firstsec=TRUE; sendline(Crcflg?WANTCRC:NAK); if (wcgetsec(rpn, 100) != 0) { log( "Pathname fetch failed\n"); return ERROR; } sendline(ACK); return OK; } /* * Adapted from CMODEM13.C, written by * Jack M. Wierda and Roderick W. Hart */ wcrx() { register int sectnum, sectcurr; register char sendchar; register char *p; int cblklen; /* bytes to dump this block */ time_t timep[2]; Firstsec=TRUE;sectnum=0; Eofseen=FALSE; sendchar=Crcflg?WANTCRC:NAK; for (;;) { /* purgeline(); */ sendline(sendchar); /* send it now, we're ready! */ sectcurr=wcgetsec(secbuf, (sectnum&0177)?50:130); report(sectcurr); if (sectcurr==(sectnum+1 &Wcsmask)) { if (sectnum==0 && !Thisbinary) for (p=secbuf,sectcurr=Blklen; *p != 032 && --sectcurr>=0; ++p) if (*p < 07 || (*p & 0200)) { Thisbinary++; if (Verbose) fprintf(stderr, "Changed to BIN\n"); break; } sectnum++; cblklen = Bytesleft>Blklen ? Blklen:Bytesleft; if (putsec(secbuf, cblklen)==ERROR) return ERROR; if ((Bytesleft-=cblklen) < 0) Bytesleft = 0; sendchar=ACK; } else if (sectcurr==(sectnum&Wcsmask)) { log( "Received dup Sector\n"); sendchar=ACK; } else if (sectcurr==WCEOT) { if (Topipe) { if (pclose(fout)!=ERROR) { sendline(ACK); return OK; } canit(); return ERROR; } sendline(ACK); if (fclose(fout)==ERROR) { if (Verbose) fprintf(stderr, "fclose returned ERROR\n"); return ERROR; } if (Modtime) { timep[0] = time(NULL); timep[1] = Modtime; utime(Pathname, timep); } if (Filemode) chmod(Pathname, (07777 & Filemode)); return OK; } else if (sectcurr==ERROR) return ERROR; else { log( "Sync Error\n"); return ERROR; } } } /* * wcgetsec fetches a Ward Christensen type sector. * Returns sector number encountered or ERROR if valid sector not received, * or CAN CAN received * or WCEOT if eot sector * time is timeout for first char, set to 4 seconds thereafter ***************** NO ACK IS SENT IF SECTOR IS RECEIVED OK ************** * (Caller must do that when he is good and ready to get next sector) */ wcgetsec(rxbuf, maxtime) char *rxbuf; int maxtime; { register checksum, wcj, firstch; register unsigned short oldcrc; register char *p; int sectcurr; for (Lastrx=errors=0; errors=0; ) { if ((firstch=readline(1)) < 0) goto bilge; oldcrc=updcrc(firstch, oldcrc); checksum += (*p++ = firstch); } if ((firstch=readline(1)) < 0) goto bilge; if (Crcflg) { oldcrc=updcrc(firstch, oldcrc); if ((firstch=readline(1)) < 0) goto bilge; oldcrc=updcrc(firstch, oldcrc); if (oldcrc) log("CRC=0%o\n", oldcrc); else { Firstsec=FALSE; return sectcurr; } } else if (((checksum-firstch)&Wcsmask)==0) { Firstsec=FALSE; return sectcurr; } else log( "Checksum Error\n"); } else log("Sector number garbled 0%o 0%o\n", sectcurr, oldcrc); } /* make sure eot really is eot and not just mixmash */ else if (firstch==EOT && readline(1)==TIMEOUT) return WCEOT; else if (firstch==CAN) { if (Lastrx==CAN) { log( "Sender CANcelled\n"); return ERROR; } else { Lastrx=CAN; continue; } } else if (firstch==TIMEOUT) { if (Firstsec) goto humbug; bilge: log( "Timeout\n"); } else log( "Got 0%o sector header\n", firstch); humbug: Lastrx=0; while(readline(1)!=TIMEOUT) ; if (Firstsec) sendline(Crcflg?WANTCRC:NAK); else { maxtime=40; sendline(NAK); } } /* try to stop the bubble machine. */ canit(); return ERROR; } /* * This version of readline is reasoably well suited for * reading many characters. * (except, currently, for the Regulus version!) * * timeout is in tenths of seconds */ #define BOTHER 70 /* don't do alarm stuff if last fetch > BOTHER */ readline(timeout) int timeout; { static char *cdq; /* pointer for removing chars from linbuf */ if (--Lleft>=0) return (*cdq++ & Wcsmask); if (Llorig 3) { fprintf(stderr, "Calling read: ", Llorig); } #endif #ifdef REGULUS /* Verry sorry, Regulus doesn't work right in raw mode! */ Llorig=Lleft=read(0, cdq=linbuf, 1); #else Llorig=Lleft=read(0, cdq=linbuf, KSIZE); #endif #ifdef DEBUG if (Verbose > 3) { fprintf(stderr, "Read returned %d bytes\n", Llorig); } #endif if (Llorig < 1) return TIMEOUT; if (Lleft=0;) { if (crc & 0x8000) { crc <<= 1; crc += (((c<<=1) & 0400) != 0); crc ^= 0x1021; } else { crc <<= 1; crc += (((c<<=1) & 0400) != 0); } } return crc; } /* * process incoming header */ procheader(name) char *name; { register char *openmode, *p, **pp; /* set default parameters */ openmode = "w"; Thisbinary=Rxbinary; Bytesleft = DEFBYTL; Filemode = 0; Modtime = 0L; p = name + 1 + strlen(name); if (*p) { /* file coming from Unix type system */ sscanf(p, "%ld%lo%o", &Bytesleft, &Modtime, &Filemode); if (Filemode & UNIXFILE) ++Thisbinary; if (Verbose) { fprintf(stderr, "Incoming: %s %ld %lo %o\n", name, Bytesleft, Modtime, Filemode); } } else { /* File coming from CP/M type system */ for (p=name; *p; ++p) /* change / to _ */ if ( *p == '/') *p = '_'; if ( *--p == '.') /* zap trailing period */ *p = 0; } /* scan for extensions that signify a binary file */ if (p=substr(name, ".")) { if ( p[2] == 'Q' || p[2] == 'q' ) Thisbinary = TRUE; else for (pp=Extensions; **pp; ++pp) if (strcmp(p, *pp)==0) { Thisbinary=TRUE; break; } } /* scan for files which should be appended */ if ( !Thisbinary && (substr(name, ".TXT") || substr(name, ".txt") || substr(name, ".MSG"))) openmode = "a"; if (MakeLCPathname && !IsAnyLower(name)) uncaps(name); if (Topipe) { sprintf(Pathname, "%s %s", Progname+2, name); if (Verbose) fprintf(stderr, "Topipe: %s %s\n", Pathname, Thisbinary?"BIN":"ASCII"); if ((fout=popen(Pathname, "w")) == NULL) return ERROR; } else { strcpy(Pathname, name); if (Verbose) { fprintf(stderr, "Receiving %s %s %s\n", name, Thisbinary?"BIN":"ASCII", openmode); } if ((fout=fopen(name, openmode)) == NULL) return ERROR; } return OK; } /* make string s lower case */ uncaps(s) register char *s; { for ( ; *s; ++s) if (isupper(*s)) *s = tolower(*s); } /* * IsAnyLower returns TRUE if string s has lower case letters. */ IsAnyLower(s) register char *s; { for ( ; *s; ++s) if (islower(*s)) return TRUE; return FALSE; } /* * putsec writes the n characters of buf to receive file fout. * If not in binary mode, carriage returns, and all characters * starting with CPMEOF are discarded. */ putsec(buf, n) char *buf; register n; { register char *p; ++totblocks; if (Thisbinary) { for (p=buf; --n>=0; ) putc( *p++, fout); } else { if (Eofseen) return OK; for (p=buf; --n>=0; ++p ) { if ( *p == '\r') continue; if (*p == CPMEOF) { Eofseen=TRUE; return OK; } putc(*p ,fout); } } return OK; } sendline(c) { putchar(c); fflush(stdout); Llorig=0; /* do bother next time ... */ } /* * 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; } /*VARARGS1*/ log(s,p,u) char *s, *p, *u; { if (!Verbose) return; fprintf(stderr, "error %d: ", errors); fprintf(stderr, s, p, u); } /* send 10 CAN's to try to get the other end to shut up */ canit() { register n; for (n=10; --n>=0; ) sendline(CAN); } #ifdef REGULUS /* * copies count bytes from s to d * (No structure assignment in Regulus C compiler) */ movmem(s, d, count) register char *s, *d; register count; { while (--count >= 0) *d++ = *s++; } #endif /* * return 1 iff stdout and stderr are different devices * indicating this program operating with a modem on a * different line */ fromcu() { struct stat a, b; fstat(1, &a); fstat(2, &b); return (a.st_rdev != b.st_rdev); } report(sct) int sct; { if (Verbose>1) fprintf(stderr,"%3d%c",sct,sct%10? ' ' : '\r'); } /* * if called as [-][dir/../]vrbCOMMAND set Verbose to 1 * if called as [-][dir/../]rbCOMMAND set the pipe flag */ 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[2] && s[0]=='r' && s[1]=='b') Topipe=TRUE; }