/***************************************************************************** * SEAlink - Sliding window file transfer protocol * * @(#) sealink.c 2.9 89/03/02 * UNIX SVR2 and BSD versions by Scott Reynolds * uucp: clmqt!scott * * additional SysV modifications by Sanford Zelkovitz, without whose * help this couldn't have been accomplished * * Based on: * MS-DOS Version 1.20, created on 08/05/87 at 17:51:40 * (C)COPYRIGHT 1986, 87 by System Enhancement Associates ALL RIGHTS RESERVED * By: Thom Henderson * * Mr. Henderson had no hand in this UNIX port; please don't bother * him with questions about this program! * * Description: * * The intent of SEAlink is to provide a file transfer protocol that * does not suffer from propagation delays, such as are introduced * by satellite relays or packet switched networks. ****************************************************************************/ /* * The following flags are for compiling on different systems. */ /* #define SYSV /* Compile for SYS V i/o calls */ /* #define BSD /* Compile for BSD i/o calls */ /* * Define NO_MEM if your system doesn't have a * working memset() function */ /* #define NO_MEM /* memset() doesn't work */ /* * Define NO_NAP if there is no nap() function. * * If you use nap() be aware that it causes a MUCH greater load * on the processor. * * With NO_NAP defined the XModem compatibility is reduced; if * this is not a concern, define this for greater efficiency. */ #define NO_NAP /* nap() doesn't work */ /* * Define CRCTABLE to use the fast table lookup CRC calculation; * a slower calculation-based method can be compiled to reduce the * amount of process memory required by commenting this out. */ #define CRCTABLE /* use CRC lookup table */ /* * The section of code that is compiled when NAKEOT is defined is in the * original MS-DOS version 1.16 code. Its purpose is to send a NAK when * an EOT is received during rcvfile(), apparently to confirm that this is * indeed the end of file. However, in certain (apparently non-standard) * versions of the protocol, it is possible that the program will report an * error when in fact there isn't one. Comment this out at your discretion. */ #define NAKEOT /* define to NAK EOT's */ #include #include #include #include #include #include #include #include #include #ifdef SYSV /* use System V I/O control */ #include #endif /* SYSV */ #ifdef BSD /* use BSD I/O control */ #include #endif /* BSD */ #ifndef NO_MEM #include #endif /* NO_MEM */ /* Various system constants */ #define WINDOW 6 /* maximum size of window */ #define S_NAK 0 /* NAK condition for sendack() */ #define S_ACK 1 /* ACK condition for sendack() */ #define NONE 0 /* neither send nor receive */ #define SEND 1 /* send mode */ #define RECV 2 /* receive mode */ #define TENYEAR (time_t) 315532800L /* GMT offset for 1970 <-> 1980 */ /* SEAlink block zero data structure */ struct zeros { long flen; /* file length */ time_t fstamp; /* file date/time stamp */ char fnam[17]; /* original file name */ char prog[15]; /* sending program name */ char noacks; /* true if ACKing not required */ char fill[87]; /* reserved for future use */ }; /* ASCII mnemonic values */ #define ACK 0x06 #define NAK 0x15 #define SOH 0x01 #define EOT 0x04 #define CAN 0x18 static int outblk; /* number of next block to send */ static int ackblk; /* number of last block ACKed */ static int blksnt; /* number of last block sent */ static int slide; /* true if sliding window */ static int ackst; /* ACK/NAK state */ static int numnak; /* number of sequential NAKs */ static int chktec; /* 1 = CRC, 0 = checksum */ static int toterr; /* total number of errors */ static int ackrep; /* true when ACK, NAK reported */ static int ackseen; /* count of sliding ACKs seen */ static int xferdone = 0; /* done with transfer (recvr) */ static int debug = 0; /* debugging flag */ static int sld_flag = 1; /* sliding windows allowed */ static int ackless = 1; /* true if ACKs not required */ static char outb[133]; /* block to use for output */ /* the program name MUST be 14 characters plus a '\0' terminator */ #ifdef SYSV char progname[15] = "SEAlink/SYSV "; #endif /* SYSV */ #ifdef BSD char progname[15] = "SEAlink/BSD "; #endif /* BSD */ /* the debug filename is a local preference */ char *dbugfile = "/tmp/sealink.log"; #ifdef NO_NAP /* * Need this to do a (very) rough approximation of nap(). * Used by alarm() in com_getc() */ jmp_buf tohere; #endif /* CRC computation logic */ #ifdef CRCTABLE unsigned short crc_tab[256] = /* CRC lookup table */ { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; /* * crc_update performs CRC calculation using crc_tab[]. * Note: Don't need to "flush" with zeroes with this formula. */ #define crc_update(CRC, C) ((CRC << 8) ^ crc_tab[(CRC >> 8)^C]) #define crc_finish(CRC) (CRC) #else /* otherwise, don't use CRC table */ unsigned short crc_update(crc, c) /* calculate a CRC value */ register unsigned crc; register int c; { int count; for (count = 8; --count >= 0;) { if (crc & 0x8000) { crc <<= 1; crc += (((c <<= 1) & 0400) != 0); crc ^ = 0x1021; } else { crc <<= 1; crc += (((c <<= 1) & 0400) != 0); } } return crc; } /* finish CRC calculation by "flushing" with zeroes */ #define crc_finish(C) crc_update(crc_update(C, 0), 0) #endif unsigned alarm(); void sendabort(); void shipblk(); #ifdef NO_MEM char *memset(); #endif /* NO_MEM */ main(argc, argv) int argc; char *argv[]; { int c; /* used to get options */ int mode = NONE; /* SEND, RECV files */ int noerr; /* no error in transmission */ char *fn; /* current filename to send/recv */ int getopt(); unsigned sleep(); int xmtfile(); char *rcvfile(); extern int opterr; /* used by getopt() */ extern int optind; /* " */ extern char *optarg; /* " */ #ifdef SYSV struct termio oldtty, tty; #endif /* SYSV */ #ifdef BSD struct sgttyb oldtty, tty; #endif /* BSD */ mode = 0; fn = NULL; opterr = 0; while ((c = getopt(argc, argv, "dfors:")) != EOF) { switch (c) { case 'd': debug = 1; /* use debug file */ break; case 'f': sld_flag = 0; /* no sliding window */ break; case 'o': ackless = 0; /* no overdrive mode */ break; case 's': mode = SEND; fn = optarg; break; case 'r': mode = RECV; break; default: mode = NONE; break; } } switch (mode) { case RECV: fputs("sealink: ready to receive\n", stderr); break; case SEND: fputs("sealink: ready to send\n", stderr); break; default: if (debug) printf("%s 2.9 sealink.c 89/03/02\n\n", progname); printf(" SEAlink sliding window file transfer protocol\n"); printf("v1.20 (C) 1986, 1987 System Enhancement Associates\n"); printf(" ALL RIGHTS RESERVED written by Thom Henderson\n"); printf(" UNIX version written by Scott Reynolds\n\n"); printf("Usage: sealink -[dfo]s filename...\n"); printf(" sealink -[dfo]r [filename...]\n"); printf("Options:\n"); printf(" -d Debug output to temporary file\n"); printf(" -f Force no sliding window\n"); printf(" -o Shut down overdrive mode\n"); printf(" -s filename... Send the file(s) specified\n"); printf(" -r [filename...] Receive file(s)\n"); exit(1); } if (debug) { time_t tim, time(); char *ctime(); printf("Sending debug output to %s\n", dbugfile); freopen(dbugfile, "a", stderr); /* open log file */ setbuf(stderr, NULL); (void) time(&tim); fputs(ctime(&tim), stderr); } fflush(stdout); /* flush output before anything else */ #ifdef SYSV (void) ioctl(0, TCGETA, &oldtty); /* get terminal parameters */ if (debug) fputs("tty parameters read\n", stderr); tty = oldtty; /* copy them, then set new */ tty.c_iflag = IGNBRK; /* No input filter; ignore break */ tty.c_oflag = 0; /* Use transparent output */ tty.c_lflag &= ~(ECHO|ICANON|ISIG); /* disable echo, signals */ tty.c_cc[VMIN] = 0; /* AT&T Sys V: return immediately */ tty.c_cc[VTIME] = 0; /* if no characters can be read */ tty.c_cflag &= ~PARENB; /* Leave baud rate, disable parity */ tty.c_cflag &= ~CSIZE; /* reset data bits */ tty.c_cflag |= CS8; /* set 8 bit data */ (void) ioctl(0, TCSETAW, &tty); /* go after setting terminal */ #endif /* SYSV */ #ifdef BSD (void) ioctl(0, TIOCGETP, &oldtty); /* get terminal parameters */ if (debug) fputs("tty parameters read\n", stderr); tty = oldtty; /* copy them, then set new */ tty.sg_flags = RAW; /* raw mode (8 bit, no processing */ (void) ioctl(0, TIOCSETP, &tty); /* go after setting terminal */ #endif /* BSD */ if (debug) fputs("tty parameters set\n", stderr); if (mode == SEND) { do { if (noerr = xmtfile(fn)) sleep(2); /* wait a few before next */ if (optind < argc) fn = argv[optind++]; else fn = NULL; } while (noerr && fn != NULL); if (noerr) /* no errors, send end marker */ (void) xmtfile(""); } else { do { if (optind < argc) /* if filename given, use it */ fn = argv[optind++]; else /* otherwise get one from remote */ fn = ""; if (noerr = (rcvfile(fn) != NULL)) sleep(2); /* wait a few before next */ } while (noerr); /* go until done/err */ noerr = xferdone; /* set no error if done */ } #ifdef SYSV (void) ioctl(0, TCSBRK, 1); /* Wait for output to drain */ (void) ioctl(0, TCFLSH, 2); /* Flush input queue */ (void) ioctl(0, TCSETAW, &oldtty); /* Restore original modes */ (void) ioctl(0, TCXONC, 1); /* Restart output */ #endif /* SYSV */ #ifdef BSD (void) ioctl(0, TIOCSETP, &oldtty); #endif /* BSD */ if (debug) { fputs("tty parameters restored\n", stderr); (void) fclose(stderr); } exit(!noerr); /* and return error status */ /*NOTREACHED*/ } /* * chkout() returns non-zero if stdout and stderr are sending to * different files/devices, zero if the same */ int chkout() { struct stat so, se; (void)fstat(1, &so); (void)fstat(2, &se); return (so.st_rdev != se.st_rdev); } #ifdef NO_NAP /* * alarmint() is called when an alarm signal is caught. */ int alarmint() { longjmp(tohere, EOF); /* return EOF to indicate timeout */ } #endif /* NO_NAP */ /* * com_getc(timeout) reads a character from file descriptor 0 * timeout is in tenths of seconds * EOF returned if no character was available to read * * If timeout is 0, this routine will return immediately regardless of * the status of the read. If timeout > 0, there will be a minimum of * one to two seconds before returning if nap() does not work. */ int com_getc(timeout) register int timeout; { static char byt[2]; /* buffer to read characters into */ #ifdef BSD if (!timeout) { /* if no timeout then no alarms */ long len; /* number of buffered characters */ (void) ioctl(0, FIONREAD, &len); /* check buffer */ /* read character if available */ if (len > 0L && read(0, byt, 1) == 1) return (byt[0] & 0377); /* return the character */ return EOF; /* error or none available */ } #endif /* BSD */ #ifdef SYSV if (!timeout) { /* if no timeout then no alarms */ if (read(0, byt, 1) == 1) /* if character was read, */ return (byt[0] & 0377); /* return the character */ return EOF; /* error or none available */ } #endif /* SYSV */ #ifdef NO_NAP /* There's a timeout value, so now we get to use alarm() */ timeout = ((timeout-1)/10)+1; /* round to seconds */ if (timeout == 1) /* minimum of 2 seconds for alarm, */ timeout++; /* since 1 may not be any delay */ if (setjmp(tohere)) { /* if the alarm went off */ if (debug) /* timeout message if debugging */ fputs("Read: timeout\n", stderr); return EOF; /* return EOF (longjmp call) */ } signal(SIGALRM, alarmint); /* set up alarm signal catching */ alarm((unsigned)timeout); /* set alarm time */ while (read(0, byt, 1) != 1) /* Go until we read a character */ ; /* (or the alarm goes off!) */ alarm(0); /* reset alarm */ signal(SIGALRM, SIG_DFL); /* and turn off signal catching */ return (byt[0] & 0377); /* return the character */ #else /* NO_NAP undefined -- nap() works */ do { if (read(0, byt, 1) == 1) /* did we read a char? */ return (byt[0] & 0377); /* yes, return it */ (void) nap(100L); /* sleep for a little while */ } while (--timeout); /* loop until time runs out */ if (debug) /* timeout message if debugging */ fputs("Read: timeout\n", stderr); return EOF; #endif /* NO_NAP */ } /* File transmitter logic */ int xmtfile(name) /* transmit a file */ char *name; /* name of file to send */ { FILE *f, *fopen(); /* file to send */ int endblk; /* block number of EOT */ struct stat fst; /* data about file */ struct zeros zero; /* block zero data */ char *basename; /* base filename */ if (name && *name) { /* if sending a file */ if ((char *)(f = fopen(name, "r")) == NULL) { fprintf(stderr, "Can't read %s\n", name); return 0; } memset((char *)&zero, 0, sizeof(zero)); /* clear data block */ tzset(); stat(name, &fst); /* get file information */ zero.flen = (long)fst.st_size; zero.fstamp = fst.st_mtime - timezone; /* adjust f/TZ */ if (daylight) /* if daylight savings, */ zero.fstamp -= 3600L; /* subtract an hour */ if (zero.fstamp < 0L) zero.fstamp = (time_t) 0; if ((basename = strrchr(name, '/')) == NULL) { strcpy(zero.fnam, name); } else { basename++; strcpy(zero.fnam, basename); } if (debug) fprintf(stderr, "basename: %s\n", zero.fnam); strcpy(zero.prog, progname); zero.noacks = ackless; endblk = (int)((zero.flen+127L)/128L)+1; } else { endblk = 0; /* fake for no file */ if (debug) fputs("send transfer complete\n", stderr); } outblk = 1; /* set starting state */ ackblk = (-1); blksnt = slide = ackst = numnak = toterr = ackrep = ackseen = 0; chktec = 2; /* undetermined */ while (ackblk < endblk) { /* while not all there yet */ if (outblk <= ackblk + ((slide && sld_flag)? WINDOW : 1)) { if (outblk < endblk) { if (outblk > 0) sendblk(f, outblk); else shipblk((unsigned char *)&zero, 0); if (ackless && slide && sld_flag) ackblk = outblk; } else if (outblk == endblk) { outb[0] = EOT; write(1, outb, 1); if (debug) fputs("sent EOT\n", stderr); } outblk++; } ackchk(); if (numnak>10) goto abort; } if (endblk) (void) fclose(f); if (debug && toterr>2) fprintf(stderr, "%d errors/%d blocks\n", toterr, blksnt); return 1; /* exit with good status */ abort: if (endblk) (void) fclose(f); if (debug) { fputs("TRANSMIT ABORTED\n", stderr); if (toterr) fprintf(stderr, "%d errors/%d blocks\n", toterr, blksnt); } sendabort(); return 0; /* exit with bad status */ } /* * The various ACK/NAK states are: * 0: Ground state, ACK or NAK expected. * 1: ACK received * 2: NAK received * 3: ACK, block# received * 4: NAK, block# received * 5: Returning to ground state */ static ackchk() /* check for ACK or NAK */ { int c; /* one byte of data */ static int rawblk; /* raw block number */ ackrep = 0; /* nothing reported yet */ while ((c = com_getc(0)) != EOF) { if (c == CAN) { /* CANcel received? */ if ((c = com_getc(20)) == CAN) { /* get two */ numnak = 11; /* OK, let's abort! */ ackst = 0; if (debug) fputs("received cancel\n", stderr); } return; /* break out of here */ } if (ackst == 3 || ackst == 4) { /* Windowed ACK/NAK */ slide = 0; /* assume this will fail */ /* see if we believe the number */ if (rawblk == (c^0xff)) { rawblk = outblk - ((outblk-rawblk)&0xff); if (rawblk >= 0 && rawblk <= outblk && rawblk > outblk-128) { /* we have sliding window! */ if (ackst == 3) { ackblk = ackblk > rawblk ? ackblk : rawblk; slide = 1; if (ackless && ++ackseen>10) { ackless = 0; if (debug) fputs("- Overdrive disengaged\n", stderr); } } else { outblk = rawblk<0? 0 : rawblk; slide = numnak<4; } if (debug) fprintf(stderr, "%s %d == \n", ackst == 3?"ACK":"NAK", rawblk); ackrep = 1; /* we reported something */ } } ackst = 5; /* return to ground state */ } if (ackst == 1 || ackst == 2) { rawblk = c; ackst += 2; } if (!slide || ackst == 0) { if (c == ACK) { if (!slide) { ackblk++; if (debug) fprintf(stderr, "ACK %d --\n", ackblk); ackrep = 1; /* reported an ACK */ } ackst = 1; numnak = 0; } else if (c == 'C' || c == NAK) { /* if method not determined yet */ if (chktec>1) /* then do what rcver wants */ chktec = (c == 'C'); #ifdef SYSV (void) ioctl(0, TCFLSH, 1); /* purge output */ #endif /* SYSV */ #ifdef BSD /* for now this code is commented out. It causes more complications to have * it installed -- my wish list would include for a BSD ioctl() to purge only * the output, but at the moment things will just have to stay this way. */ /* (void) ioctl(0, TIOCFLUSH, 0); /* purge i/o */ #endif /* BSD */ if (!slide) { outblk = ackblk+1; if (debug) fprintf(stderr, "NAK %d --\n", ackblk+1); ackrep = 1; /* reported a NAK */ } ackst = 2; numnak++; if (blksnt) toterr++; } } if (ackst == 5) ackst = 0; } } static sendblk(f, blknum) /* send one block */ FILE *f; /* file to read from */ int blknum; /* block to send */ { long blkloc; /* address of start of block */ unsigned char buf[128]; /* one block of data */ if (blknum != blksnt+1) { /* if jumping */ blkloc = (long)(blknum-1) * 128L; fseek(f, blkloc, 0); /* move where to */ } blksnt = blknum; memset(buf, 26, 128); /* fill buffer with control Zs */ fread(buf, 1, 128, f); /* read in some data */ shipblk(buf, blknum); /* pump it out to the receiver */ } static void shipblk(blk, blknum) /* physically ship a block */ unsigned char *blk; /* data to be shipped */ int blknum; /* number of block */ { register unsigned short crc = 0; /* CRC check value */ register int n; /* index */ unsigned char *b = blk; /* data pointer */ outb[0] = SOH; /* block header */ outb[1] = blknum; /* block number */ outb[2] = blknum^0xff; /* block number check value */ for(n = 0;n < 128;n++) { /* ship the data */ if (chktec) crc = crc_update(crc, *b); else crc += *b; outb[n+3] = (*b++); } crc = crc_finish(crc); if (chktec) { /* send proper check value */ outb[131] = crc>>8; outb[132] = crc&0xff; write(1, outb, 133); } else { outb[131] = crc&0xff; write(1, outb, 132); } if (debug) fprintf(stderr, "sent block %d\n", blknum); return; } /* File receiver logic */ char *rcvfile(name) /* receive file */ char *name; /* name of file */ { int c; /* received character */ int tries; /* retry counter */ int blknum; /* desired block number */ int inblk; /* this block number */ FILE *f; /* file to receive to */ char buf[128]; /* data buffer */ char tmpname[100]; /* name of temporary file */ static char outname[100]; /* name of final file */ struct zeros zero; /* file header data storage */ int endblk; /* block number of EOT if known */ long left; /* bytes left to output */ int getblock(); /* block receiver, status */ int fcopy(); /* copy source file to dest */ void setstamp(); /* set date/time stamp of file */ int cnvrt; /* flag -- convert filename? */ char *onp; /* use to convert filename to l/c */ if (name && *name) { /* figure out a name to use */ strcpy(outname, name); /* user supplied one */ cnvrt = 0; /* no conversion should be done */ } else { *outname = '\0'; /* get name from transmitter */ cnvrt = 1; /* convert to local is necessary */ } strcpy(tmpname, ".sl.rcv.XXXXXX"); /* template for mktemp() */ mktemp(tmpname); /* use a unique temp filename */ if (debug) fprintf(stderr, "tmpname: %s\n", tmpname); if (*outname && (f = fopen(outname, "r"))) { /* open output file */ (void) fclose(f); if (!(f = fopen(outname, "r+"))) { if (debug) fprintf(stderr, "Cannot write %s\n", tmpname); sendabort(); return NULL; } else { (void) fclose(f); } } if (!(f = fopen(tmpname, "w"))) { /* open temporary file */ if (debug) fprintf(stderr, "Cannot create %s\n", tmpname); sendabort(); return NULL; } blknum = *outname ? 1 : 0; /* first block we must get */ tries = -10; /* kludge for first time around */ chktec = 1; /* try for CRC error checking */ toterr = 0; /* no errors yet */ endblk = 0; /* we don't know the size yet */ ackless = 0; /* we don't know about this yet */ memset((char *)&zero, 0, sizeof(zero)); /* or much of anything else */ /* if (com_scan() == EOF) /+ kludge for adaptive Modem7 +/ goto nextblock; */ nakblock: /* we got a bad block */ if (blknum>1) toterr++; if (++tries>10) goto abort; if (tries == 0) /* if CRC isn't going */ chktec = 0; /* then give checksum a try */ sendack(S_NAK, blknum); /* send the NAK */ if (ackless && toterr > 20) { /* if ackless mode isn't working */ ackless = 0; /* then shut it off */ if (debug) fputs("- Overdrive disengaged\n", stderr); } goto nextblock; ackblock: /* we got a good block */ nextblock: /* start of "get a block" */ while ((c = com_getc(30)) != EOF) { if (c == CAN) if ((c = com_getc(30)) == CAN) { sendabort(); return NULL; } else break; if (c == EOT) { if (!endblk || endblk == blknum) goto endrcv; } else if (c == SOH) { if ((inblk = com_getc(5)) == EOF) goto nakblock; if (com_getc(5) == (inblk^0xff)) { if (debug) fprintf(stderr, "received #%d\n", inblk); goto blockstart; /* we found a start */ } } } goto nakblock; blockstart: /* start of block detected */ c = blknum&0xff; if (inblk == 0 && blknum <= 1) { /* if this is the header */ if (!getblock((char *)&zero)) { sendack(S_ACK, inblk); /* ack the header */ if (!*name) /* given name takes precedence */ strcpy(outname, zero.fnam); if (left = zero.flen) /* length to transfer */ endblk = (int)((left+127L)/128L)+1; if (ackless != zero.noacks && debug) fprintf(stderr, "+ Overdrive %sengaged\n", zero.noacks?"":"dis"); ackless = zero.noacks; blknum = 1; /* now we want first data block */ goto ackblock; } else { goto nakblock; /* bad header block */ } } else if (inblk == c) { /* if this is the one we want */ if (!getblock(buf)) { /* else if we get it okay */ if (!ackless) /* if we're sending ACKs */ sendack(S_ACK, inblk); /* then ACK the data */ /* * if file size is not known or more than 128 bytes * to go, write one block (128 bytes) to the file */ if (!endblk || left >= 128L) { if (fwrite(buf, 1, 128, f) != 128) { if (debug) fputs("- WRITE ERROR\n", stderr); goto abort; } left -= 128L; /* 128 less to do... */ /* * we know the size of the file and there are less than * 128 bytes to write out; only do the necessary amount */ } else if (left > 0) { if (fwrite(buf, 1, (unsigned)left, f) != (int)left) { if (debug) fputs("- WRITE ERROR\n", stderr); goto abort; } left = 0; /* and then there were none */ } tries = 0; /* reset try count */ blknum++; /* we want the next block */ goto ackblock; } else { goto nakblock; /* ask for a resend */ } } else if (inblk < c || inblk > (c + 100)) { /* if we have it, */ (void) getblock(buf); /* ignore it */ sendack(S_ACK, inblk); /* but ack it */ goto ackblock; } else goto nextblock; /* else if running ahead */ endrcv: #ifdef NAKEOT sendack(S_NAK, blknum); /* NAK the EOT, make sure */ if (com_getc(20) != EOT) /* we're all done */ goto nakblock; #endif /* NAKEOT */ sendack(S_ACK, blknum); /* ACK it and clean up */ if (debug) fputs("received EOT\n", stderr); if (blknum>1) { /* if we really got anything */ if (debug && toterr>2) fprintf(stderr, "%d errors/%d blocks\n", toterr, blknum-1); (void) fclose(f); (void) unlink(outname); /* rename temp to proper name */ for (onp = outname;cnvrt && *onp;onp++) /* find out if there's lower */ if (islower(*onp)) /* case letters in filename */ cnvrt = 0; /* there are, don't convert */ if (cnvrt) /* if there aren't, make all */ for (onp = outname;*onp;onp++) /* into lowercase */ *onp = tolower(*onp); if (link(tmpname, outname) == 0 || fcopy(tmpname, outname) == 0) (void) unlink(tmpname); else if (debug) fputs("can't rename or copy file\n", stderr); if (zero.fstamp) /* set stamp, if known */ setstamp(outname, zero.fstamp); if (debug) fprintf(stderr, "received file: %s\n", outname); return outname; /* signal what file we got */ } else { /* else no real file */ (void) fclose(f); (void) unlink(tmpname); /* discard empty file */ if (debug) fputs("received end\n", stderr); xferdone = 1; /* signal end of transfer */ return NULL; } abort: if (debug) { fputs("RECEIVE ABORTED\n", stderr); if (toterr) fprintf(stderr, "%d errors/%d blocks\n", toterr, blknum-1); } (void) fclose(f); xferdone = 1; /* signal end of transfer */ sendabort(); return NULL; } int fcopy(file1, file2) /* copy 'file1' to 'file2' */ char *file1; /* source filename */ char *file2; /* destination filename */ { FILE *f, *t; /* files from and to */ long size; /* bytes to copy */ char *buf; /* buffer pointer */ char *malloc(); /* buffer allocator */ unsigned int bufl; /* buffer length */ unsigned int cpy; /* bytes being copied */ struct stat fst; /* data about file */ if (stat(file1, &fst)) /* get file information */ return EOF; size = (long)fst.st_size; if ((char *)(f = fopen(file1, "r")) == NULL) return EOF; if ((char *)(t = fopen(file2, "w")) == NULL) { (void) fclose(f); return EOF; } bufl = 32760; if (bufl > size) bufl = size; /* don't waste space */ while (bufl >= 128 && (buf = malloc(bufl)) == NULL) bufl >>= 1; /* keep trying until it's hopeless */ if (buf == NULL) { /* if we can't get a buffer, clean up */ (void) fclose(f); (void) fclose(t); (void) unlink(file2); return EOF; /* return an error indication */ } while (size > 0) { cpy = fread(buf, sizeof(char), bufl < size ? bufl : (unsigned short) size, f); if (fwrite(buf, sizeof(char), cpy, t) != cpy) break; size -= cpy; } free(buf); (void) fclose(f); (void) fclose(t); return (size > 0); } static void setstamp(f_name, f_time) /* set a file's date/time stamp */ char *f_name; /* file to set stamp on */ long f_time; /* desired date/time */ { void tzset(); /* library time zone function */ time_t times[2], time(); times[0] = time((time_t *) 0); tzset(); times[1] = f_time + TENYEAR + timezone; /* convert time */ if (daylight) /* if daylight savings, */ times[1] += 3600L; /* add an hour */ utime(f_name, times); } static int sendack(acknak, blknum) /* send an ACK or a NAK */ register int acknak; /* 1 = ACK, 0 = NAK */ register int blknum; /* block number */ { if (debug) fprintf(stderr, "%s %d\n", acknak?"ACK":"NAK", blknum); if (acknak) /* send the right signal */ outb[0] = ACK; else if (chktec) outb[0] = 'C'; else outb[0] = NAK; outb[1] = blknum; /* block number */ outb[2] = blknum^0xff; /* block number check */ write(1, outb, 3); } static int getblock(buf) /* read a block of data */ char *buf; /* data buffer */ { register unsigned short ourcrc = 0; /* remote CRC check value */ unsigned short hiscrc; /* remote CRC check value */ register int c; /* one byte of data */ int n; /* index */ int timeout = ackless ? 200 : 5; /* short block timeout */ for(n = 0; n<128; n++) { if ((c = com_getc(timeout)) == EOF) { if (debug) fputs("block received -- short\n", stderr); return 1; } if (chktec) ourcrc = crc_update(ourcrc, (unsigned char) c); else ourcrc += c; *buf++ = (unsigned char) c; } if (chktec) { ourcrc = crc_finish(ourcrc); c = com_getc(timeout); hiscrc = (c << 8) | com_getc(timeout); } else { ourcrc &= 0xff; hiscrc = com_getc(timeout) & 0xff; } if (debug) { if (ourcrc == hiscrc) fputs("block received -- good\n", stderr); else { fprintf(stderr, "block received -- bad %s\n", chktec?"CRC":"checksum"); fprintf(stderr, "his = 0x%x ours = 0x%x\n", hiscrc, ourcrc); } } if (ourcrc == hiscrc) return 0; /* block is good */ else return 1; /* error in checksum or CRC */ } void sendabort() { #ifdef SYSV (void) ioctl(0, TCFLSH, 1); /* purge output */ #endif /* SYSV */ #ifdef BSD (void) ioctl(0, TIOCFLUSH, 0); /* purge all i/o */ #endif /* BSD */ strcpy(outb, "\030\030\030\030\030\030\030\030\b\b\b\b\b\b\b\b"); strcat(outb, "\030\030\030\030\b\b\b\b"); /* set up cancel seq */ write(1, outb, (unsigned)strlen(outb)); /* write it out */ } #ifdef NO_MEM /* * This routine replicates the function found in the * System V memory.h module for systems without a proper * or with no implementation of this function available. */ char *memset(m, n, c) char *m; int n; char c; { int i; for (i = 0;i < n;i++) *(m+i) = c; return m; } #endif /* NO_MEM */