/* * A version of Ward Christensen's file transfer protocol for * Unix System V or 4.2 bsd. * * Emmet P. Gray, ..!ihnp4!uiucuxc!fthood!egray, 16 Aug 85 * * Modified by Sanford Zelkovitz 08/18/86 * Last modification date: 05/20/87 */ #define SV #undef BSD #include #include #include #include #ifdef SV #include #endif #ifdef BSD #include #endif #define MAXERRORS 10 /* max number of times to retry */ #define SECSIZE 128 /* CP/M sector, transmission block */ #define CPMEOF 26 /* End Of File (for CP/M) */ #define SOH 1 /* Start Of Header */ #define EOT 4 /* End Of Transmission */ #define ACK 6 /* ACKnowledge */ #define NAK 21 /* Negative AcKnowledge */ #define CAN 24 /* CANcel */ int synchron; int exit_return; unsigned char crc1, crc2; #ifdef SV struct termio ttyhold; #endif #ifdef BSD struct sgttyb ttyhold; #endif main(argc, argv) int argc; char *argv[]; { int msgstat; char *tty, *ttyname(); struct stat stbuf; exit_return=0; if (argc != 3) { usage(); exit(1); } tty = ttyname(1); stat(tty, &stbuf); msgstat = (stbuf.st_mode & 0777); chmod(tty, 0600); /* mesg n */ #ifdef SV ioctl(0, TCGETA, &ttyhold); /* get current settings */ #endif #ifdef BSD ioctl(0, TIOCGETP, &ttyhold); #endif switch (*argv[1]) { case 'r': recvfile(argv[2]); break; case 's': sendfile(argv[2]); break; default: usage(); } #ifdef SV ioctl(0, TCSETAF, &ttyhold); /* restore settings */ #endif #ifdef BSD ioctl(0, TIOCSETP, &ttyhold); #endif chmod(tty, msgstat); /* restore mesg status */ exit(exit_return); } /* send a file to the remote */ sendfile(tfile) char *tfile; { FILE *fp; unsigned char chr, checksum, block, sector[SECSIZE]; int i, mode, nbytes, errcount, size, speed; long min, sec; static int baud[16] = {0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400}; struct stat sbuf; if (!(fp = fopen(tfile, "r"))) { fprintf(stderr, "xmodem: Can't open '%s' for read\r\n", tfile); exit_return=1; return; } stat(tfile, &sbuf); size = (sbuf.st_size / 128) + 1; #ifdef SV speed = baud[ttyhold.c_cflag & 017]; #endif #ifdef BSD speed = baud[ttyhold.sg_ispeed]; #endif sec = size; sec = sec * 128L * 11L / speed; min = sec / 60L; sec = sec - min * 60L; printf("File open: %d records\r\n", size); printf("Send time: %ld min, %ld sec at %d baud\r\n", min, sec, speed); printf("To cancel: use CTRL-X numerous times\r\n"); printf("Waiting ready signal\r\n"); rawmode(); errcount = 0; mode = 0; block = 1; while (errcount < MAXERRORS) { chr = getchar_t(); if (chr == NAK) /* checksum mode */ break; if (chr == 'C') { /* CRC mode */ mode = 1; break; } errcount++; } if (errcount == MAXERRORS) { sleep(3); fprintf(stderr, "xmodem: Timed out on acknowledge\r\n"); exit_return=1; return; } while (nbytes = fread(sector, sizeof(sector[0]), SECSIZE, fp)) { if (nbytes < SECSIZE) { /* fill short sector */ for (i=nbytes; i < SECSIZE; i++) sector[i] = CPMEOF; } errcount = 0; while (errcount < MAXERRORS) { putchar(SOH); /* the header */ putchar(block); /* the block number */ chr = ~block; putchar(chr); /* it's complement */ checksum = 0; crc1 = 0; crc2 = 0; for (i=0; i < SECSIZE; i++) { putchar(sector[i]); if (mode) update_crc(sector[i]); else checksum += sector[i]; } if (mode) { update_crc(0); update_crc(0); putchar(crc1); putchar(crc2); } else putchar(checksum); rec_loop: chr = getchar_t(); if (chr == CAN) { sleep(3); exit_return=1; fprintf(stderr,"\r\nxmodem: Abort request received\r\n"); return; } if (chr == ACK) break; /* got it! */ if (chr != NAK) goto rec_loop; /* Noise on line? */ errcount++; } if (errcount == MAXERRORS) { error(); exit_return=1; return; } block++; } errcount = 0; exit_return=1; while (errcount < MAXERRORS) { putchar(EOT); if (getchar_t() == ACK) { exit_return=0; break; } errcount++; } return; } /* receive a file from the remote */ recvfile(tfile) char *tfile; { FILE *fp; unsigned char hdr, blk, cblk, tmp, cksum; unsigned char c1, c2, sum, block, sector[SECSIZE]; int i, stop = 0, mode, errcount, resync(); long true_end; char ans[40]; if (!access(tfile, 00)) { while (1) { printf("File already exists \r\n"); return; } } if (!(fp = fopen(tfile, "w"))) { fprintf(stderr, "xmodem: Can't open '%s' for write\r\n", tfile); return; } printf("File open - ready to receive\r\n"); rawmode(); errcount = 0; block = 1; sleep(10); while (errcount < MAXERRORS) { if (errcount < (MAXERRORS / 2)) { putchar(NAK); /* try checksum mode first */ mode = 0; } else { putchar('C'); /* then crc */ mode = 1; } if ((hdr = getchar_t()) == SOH) { ungetc(SOH, stdin); break; } errcount++; } if (errcount == MAXERRORS) { sleep(3); fprintf(stderr, "\r\nxmodem: Timed out on acknowledge\r\n"); return; } errcount = 0; while (errcount < MAXERRORS) { hdr = getchar_t(); if (hdr == CAN) { sleep(3); fprintf(stderr, "\r\nxmodem: Abort request received\r\n"); return; } if (hdr == EOT) /* done! */ break; if (hdr != SOH) { /* read in junk for 6 seconds */ synchron = 0; /* to re-synchronized block */ signal(SIGALRM, resync); alarm(6); while(synchron == 0) hdr = getchar(); goto nak; } blk = getchar_t(); cblk = getchar_t(); crc1 = 0; crc2 = 0; sum = 0; for (i=0; i < SECSIZE; i++) { sector[i] = getchar_t(); if (mode) update_crc(sector[i]); else sum += sector[i]; } if (mode) { c1 = getchar_t(); c2 = getchar_t(); } else cksum = getchar_t(); if (blk != block && blk != (block - 1)) goto nak; tmp = ~blk; if (cblk != tmp) goto nak; if (mode) { update_crc(0); update_crc(0); if (c1 != crc1 || c2 != crc2) goto nak; } else { if (cksum != sum) goto nak; } if (block == blk) { fflush(fp); fwrite(sector, sizeof(sector[0]), SECSIZE, fp); } block = blk + 1; putchar(ACK); /* got it! */ errcount = 0; continue; nak: putchar(NAK); /* do it over */ errcount++; } if (errcount == MAXERRORS) { error(); return; } putchar(ACK); for (i = SECSIZE -1; i >= 0; i--) { /* find true EOF */ if (sector[i] != CPMEOF) { stop = i; break; } } /* * Some CPM systems don't pad the end of the file with ^Z's so the file may * have junk at the end. A conservative approach had to be taken in order * for Unix object code (where ^Z's may be valid data) to transfer properly. */ true_end = ftell(fp) - SECSIZE + stop +1; fclose(fp); truncate(tfile, true_end); return; } /* give minimal usage message */ usage() { fprintf(stderr, "Usage: xmodem [ s | r ] filename\r\n"); fprintf(stderr, " options are 's' for send or 'r' for receive\r\n"); return; } /* exceeded the maximum number of retry's */ error() { putchar(CAN); putchar(CAN); putchar(CAN); putchar(CAN); sleep(3); fprintf(stderr, "\r\nxmodem: Exceeded error limit...aborting\r\n"); return; } /* update the CRC bytes */ update_crc(c) unsigned char c; { int i, temp; unsigned char carry, c_crc1, c_crc2; for (i=0; i < 8; i++) { temp = c * 2; c = temp; /* rotate left */ carry = ((temp > 255) ? 1 : 0); temp = crc2 * 2; crc2 = temp; crc2 |= carry; /* rotate with carry */ c_crc2 = ((temp > 255) ? 1 : 0); temp = crc1 * 2; crc1 = temp; crc1 |= c_crc2; c_crc1 = ((temp > 255) ? 1 : 0); if (c_crc1) { crc2 ^= 0x21; crc1 ^= 0x10; } } return; } /* getchar with a 10 sec time out */ getchar_t() { int force_it(); unsigned char c; signal(SIGALRM, force_it); alarm(10); /* only have 10 sec... */ c = getchar(); alarm(0); return(c); } /* * This code (and the resync() below) is the most machine dependent part * of the program. The action of the signal SIGALRM during a read system * call is not well defined. Some systems return the stack to the point * outside the system call, others inside the call itself. Have fun... */ force_it() { unsigned char c; c = CPMEOF; /* arbitrary default char */ #ifdef SV ungetc(c, stdin); #endif #ifdef BSD ioctl(0, TIOCSTI, &c); #endif return; } /* truncate file to given length */ truncate(path, length) char *path; long length; { FILE *fp, *tempfp; long i; char c, string[80], *tempfile, *mktemp(); if (!(fp = fopen(path, "r"))) { fprintf(stderr, "xmodem: Can't open '%s' for read\r\n", path); return; } tempfile = mktemp("/tmp/trunXXXXXX"); if (!(tempfp = fopen(tempfile, "w"))) { fprintf(stderr, "xmodem: Can't open temporary file\r\n"); return; } for (i=0; i < length; i++) { c = fgetc(fp); fputc(c, tempfp); } fclose(fp); fclose(tempfp); sprintf(string, "mv %s %s", tempfile, path); system(string); return; } /* put the stdin/stdout in the "raw" mode */ rawmode() { #ifdef SV struct termio tbuf; ioctl(0, TCGETA, &tbuf); tbuf.c_cc[4] = 1; /* VMIN */ tbuf.c_cc[5] = 0; /* VTIME */ tbuf.c_iflag = 0; tbuf.c_oflag = 0; tbuf.c_lflag = 0; tbuf.c_cflag &= ~CSIZE; tbuf.c_cflag |= CS8; tbuf.c_cflag &= ~PARENB; ioctl(0, TCSETAF, &tbuf); return; #endif #ifdef BSD struct sgttyb sgbuf; ioctl(0, TIOCGETP, &sgbuf); sgbuf.sg_flags |= RAW; sgbuf.sg_flags &= ~ECHO; ioctl(0, TIOCSETP, &sgbuf); return; #endif } /* after 6 seconds of reading junk data... */ resync() { char c; synchron = 1; /* set the flag */ c = SOH; #ifdef SV ungetc(c, stdin); #endif #ifdef BSD ioctl(0, TIOCSTI, &c); #endif return; }