ftp.nice.ch/pub/next/unix/communication/pcomm.NIHS.bs.tar.gz#/pcomm/Source/x_rcv.c

This is x_rcv.c in view mode; [Download] [Up]

/*
 * Receive a list of files using a version of Ward Christensen's file
 * transfer protocol.  A non-zero return code means the user must acknowledge
 * an error condition (a user generated abort returns a 0).  Write errors
 * are considered fatal.
 */

#include <stdio.h>
#include <curses.h>
#include "config.h"
#include "dial_dir.h"
#include "misc.h"
#include "xmodem.h"

unsigned char buf[1029];
char file_name[15];
long file_length;

static int err_method, tot_err, block_size;

int
rcv_xmodem(win, list, type, fast)
WINDOW *win;
char *list;
int type, fast;
{
	extern char *protocol[];
	FILE *fp, *my_fopen();
	int i, default_err, is_batch, max_block, code, file_count, got_hdr;
	int hours, mins, secs, len;
	static int send_first();
	long block, recv, partial;
	float percent, performance;
	unsigned char blk;
	unsigned int sleep();
	char *file, *name, *strcpy(), *strrchr(), *strtok();
	void cancel_xfer();
					/* which protocol? */
	switch (type) {
		case XMODEM:
			default_err = CRC_CHECKSUM;
			is_batch = 0;
			max_block = 128;
			break;
		case XMODEM_1k:
			default_err = CRC_CHECKSUM;
			is_batch = 0;
			max_block = 1024;
			break;
		case MODEM7:
			default_err = CHECKSUM;
			is_batch = 1;
			max_block = 128;
			break;
		case YMODEM:
			default_err = CRC;
			is_batch = 1;
			max_block = 1024;
			performance = 1.09;
			break;
		case YMODEM_G:
			default_err = NONE;
			is_batch = 1;
			max_block = 1024;
			performance = 1.02;
			break;
		default:
			return(1);
	}

	tot_err = 0;
	file_count = 0;
	mvwaddstr(win, 2, 24, protocol[type]);
	mvwaddstr(win, 11, 24, "0  ");

	while (1) {
		file_count++;
		file_length = 0L;
					/* user supplied name */
		if (!is_batch) {
			if (file_count > 1)
				break;

			file = strtok(list, " \t");
					/* dissect the file name */
			if ((name = strrchr(file, '/')))
				strcpy(file_name, ++name);
			else
				strcpy(file_name, file);
		}
					/* get the modem7 file name */
		if (type == MODEM7) {
			if (code = rcv_modem7(win, default_err))
				return(code +1);

			file = file_name;
		}
					/* get the block 0 */
		if (type == YMODEM || type == YMODEM_G) {
			if (code = send_first(win, max_block, default_err))
				return(code +1);

			if (code = rcv_ymodem(win))
				return(code +1);

					/* at the end? */
			if (buf[3] == '\0') {
				beep();
				wrefresh(win);
				putc_line(ACK);
				sleep(1);
				return(0);
			}
			file = file_name;
		}
					/* any trouble? */
		if (file_name[0] == '\0')
			continue;

		clear_line(win, 3, 24, TRUE);
		waddstr(win, file_name);
					/* if file length is known */
		if (file_length != 0L) {
			mvwprintw(win, 4, 24, "%-10ld", file_length);

			secs = (file_length * 10.0 / dir->baud[dir->d_cur]) * performance;
			hours = secs / 3600;
			mins = (secs % 3600) / 60;
			secs = (secs % 3600) % 60;

			mvwprintw(win, 6, 24, "%d:%02d:%02d", hours, mins, secs);
		}
					/* some starting numbers */
		mvwaddstr(win, 7, 24, "0    ");
		if (file_length != 0L && fast)
			mvwaddstr(win, 8, 24, "0%  ");
		if (fast)
			mvwaddstr(win, 9, 24, "0          ");
		mvwaddstr(win, 10, 24, "0 ");
		clear_line(win, 12, 24, TRUE);
		waddstr(win, "NONE");
		wrefresh(win);

		/*
		 * If the user supplied the name, write permission is checked
		 * by the get_names() routine in xfer_menu().  If modem7
		 * or ymodem supplied name, the name is unique and the write
		 * permission on the directory is checked by the change_name()
		 * routines.  However, this is required for systems with
		 * SETUID_BROKE set.
		 */
					/* open the file */
		if (!(fp = my_fopen(file, "w"))) {
			beep();
			clear_line(win, 12, 24, TRUE);
			wattrstr(win, A_BOLD, "CAN'T OPEN FILE");
			wrefresh(win);
			cancel_xfer(DOWN_LOAD);
			return(1);
		}
					/* ACK the block 0 */
		if (type == YMODEM || type == YMODEM_G)
			putc_line(ACK);

		if (code = send_first(win, max_block, default_err)) {
			fclose(fp);
			return(code +1);
		}
					/* here we go... */
		clear_line(win, 12, 24, TRUE);
		waddstr(win, "NONE");
		wrefresh(win);
		blk = 1;
		block = 1L;
		recv = 0L;
		got_hdr = 1;
		while (1) {
			code = rcv_block(win, got_hdr, max_block, blk);

			if (code < 0) {
				fclose(fp);
				return(code +1);
			}
			got_hdr = 0;
					/* are we done? */
			if (buf[0] == EOT) {
				if (!is_batch) {
					beep();
					wrefresh(win);
					sleep(1);
				}
				break;
			}
					/* if not a duplicate block */
			if (!code) {
				if (file_length != 0L) {
					partial = file_length - recv;
					if (partial > (long) block_size)
						len = block_size;
					else
						len = partial;
				}
				else
					len = block_size;

				if (fwrite((char *) &buf[3], sizeof(char), len, fp) != len) {
					beep();
					clear_line(win, 12, 24, TRUE);
					wattrstr(win, A_BOLD, "WRITE ERROR");
					wrefresh(win);
					cancel_xfer(DOWN_LOAD);
					fclose(fp);
					/* fatal */
					return(1);
				}
				mvwprintw(win, 7, 24, "%-5ld", block);
				recv = recv + (unsigned int) len;
				if (fast)
					mvwprintw(win, 9, 24, "%-10ld", recv);
				blk++;
				block++;
			}
			/*
			 * If the length is known, give the same status
			 * report as uploading
			 */
			if (file_length != 0L && fast) {
				percent = recv * 100.0 / file_length;
				if (percent > 100.0)
					percent = 100.0;
				mvwprintw(win, 8, 24, "%0.1f%%", percent);
			}
			wrefresh(win);
			putc_line(ACK);
		}
		if (file_length != 0L && fast) {
			mvwaddstr(win, 8, 24, "100%  ");
			wrefresh(win);
		}
		/*
		 * If the file length is not known, search backwards from
		 * the end of the file until you find a character that is
		 * not the ^Z padding character.
		 */
		if (file_length == 0L) {
			for (i=block_size+2; i>2; i--) {
				if (buf[i] != CTRLZ)
					break;
			}
			file_length = recv - (unsigned int) block_size + (unsigned int) i -2L;
			fclose(fp);
			if (fix_length(file, file_length)) {
				beep();
				clear_line(win, 12, 24, TRUE);
				wattrstr(win, A_BOLD, "TRUNCATE ERROR");
				wrefresh(win);
				sleep(1);
			}
		}
		else
			fclose(fp);
					/* ACK the EOT */
		putc_line(ACK);
	}
	return(0);
}

/*
 * Send the first character to start the transmission and set the error
 * checking method.  Returns the standard error codes or 0 on success.
 * The variables err_method and block_size are global.
 */

static int
send_first(win, max_block, default_err)
WINDOW *win;
int max_block, default_err;
{
	int i, err_count;
	unsigned int sleep();
	void cancel_xfer();
					/* default error method */
	err_method = default_err;
	if (default_err == CRC_CHECKSUM)
		err_method = CRC;
					/* send the first char */
	err_count = 0;
	while (err_count < MAX_ERRORS*2) {
		mvwprintw(win, 10, 24, "%-2d", err_count);

					/* check for keyboard abort */
		if (wgetch(win) == ESC) {
			beep();
			clear_line(win, 12, 24, TRUE);
			waddstr(win, "ABORTED");
			wrefresh(win);
			cancel_xfer(DOWN_LOAD);
			sleep(3);
			return(ABORT);
		}
					/* switch to checksum? */
		if (default_err == CRC_CHECKSUM && err_count > MAX_ERRORS/2)
			err_method = CHECKSUM;

					/* send error method code */
		clear_line(win, 5, 24, TRUE);
		switch (err_method) {
			case CHECKSUM:
				waddstr(win, "CHECKSUM");
				putc_line(NAK);
				break;
			case CRC:
				waddstr(win, "CRC");
				putc_line('C');
				break;
			case NONE:
				waddstr(win, "NONE");
				putc_line('G');
				break;
		}
		/*
		 * We've cut the delay time in half, so we double
		 * the allowable errors
		 */
		if ((i = getc_line(5)) == -1) {
			err_count++;
			clear_line(win, 12, 24, TRUE);
			waddstr(win, "NO RESPONSE");
			wrefresh(win);
			continue;
		}
		buf[0] = i;
#ifdef DEBUG
		fprintf(stderr, "send_first: got header %02x, %03o, %d\n", buf[0], buf[0], buf[0]);
#endif /* DEBUG */

		switch (buf[0]) {
			case SOH:	/* small block follows */
				block_size = 128;
				return(0);
			case STX:	/* large block follows */
				if (max_block == 1024) {
					block_size = 1024;
					return(0);
				}
				/* fall thru */
			default:
				err_count++;
				clear_line(win, 12, 24, TRUE);
				waddstr(win, "BAD HEADER");
				wrefresh(win);
					/* read some garbage... */
				while(fread_line(buf, 1028, 1) != -1)
					;
				putc_line(NAK);
				break;
		}
	}
	beep();
	clear_line(win, 12, 24, TRUE);
	wattrstr(win, A_BOLD, "TIMED OUT");
	wrefresh(win);
	return(ERROR);
}

/*
 * Receive a block of info from the host.  Returns a 0 on success, a 1 on
 * a duplicate block or the standard error codes.  The variables
 * err_method and block_size are global.
 */

int
rcv_block(win, got_hdr, max_block, blk)
WINDOW *win;
int got_hdr, max_block;
unsigned char blk;
{
	int i, err_count, bad_block, out_of_sync;
	unsigned short crc, calc_crc();
	unsigned int packet, sleep();
	unsigned char blk_compliment, calc_sum(), crc_1, crc_2;
	void cancel_xfer();

	err_count = 0;
	while (err_count < MAX_ERRORS) {
		mvwprintw(win, 10, 24, "%-2d", err_count);
		mvwprintw(win, 11, 24, "%-3d", tot_err);

					/* scan the keyboard for abort */
		if (wgetch(win) == ESC) {
			beep();
			clear_line(win, 12, 24, TRUE);
			waddstr(win, "ABORTED");
			wrefresh(win);
			cancel_xfer(DOWN_LOAD);
			sleep(3);
			return(ABORT);
		}
					/* have we already got a hdr? */
		if (!got_hdr) {
			if ((i = getc_line(10)) == -1) {
				err_count++;
				tot_err++;
				clear_line(win, 12, 24, TRUE);
				waddstr(win, "NO RESPONSE");
				wrefresh(win);
				continue;
			}
			buf[0] = i;
#ifdef DEBUG
			fprintf(stderr, "rcv_block: got header %02x, %03o, %d\n", buf[0], buf[0], buf[0]);
#endif /* DEBUG */
					/* what'd we get? */
			switch (buf[0]) {
				case EOT:	/* we're done! */
					clear_line(win, 12, 24, TRUE);
					waddstr(win, "TRANSFER COMPLETE");
					wrefresh(win);
					sleep(1);
					return(0);
				case SOH:	/* small block follows */
					block_size = 128;
					break;
				case STX:	/* large block follows */
					if (max_block == 1024) {
						block_size = 1024;
						break;
					}
					/* fall thru... */
				default:
					err_count++;
					tot_err++;
					clear_line(win, 12, 24, TRUE);
					waddstr(win, "BAD HEADER");
					wrefresh(win);

					/* read some garbage... */
					while(fread_line(buf, 1028, 1) != -1)
						;
					putc_line(NAK);
					continue;
			}
		}
					/* read the rest of the packet */
		packet = block_size + 2 + (err_method == CHECKSUM ? 1 : 2);
		if (fread_line(&buf[1], packet, 10) == -1) {
			clear_line(win, 12, 24, TRUE);
			waddstr(win, "TIMED OUT");
			wrefresh(win);
			putc_line(NAK);
			err_count++;
			tot_err++;
			continue;
		}

		/*
		 * Validation of the packet includes checking the
		 * block number, its complement, and the crc/checksum.
		 */
		out_of_sync = 0;
		blk_compliment = ~blk;
		if (buf[1] != blk || buf[2] != blk_compliment)
			out_of_sync++;

		bad_block = 0;
		switch (err_method) {
			case CHECKSUM:
#ifdef DEBUG
				fprintf(stderr, "blk=%d, checksum=%d\n", blk, calc_sum(&buf[3], block_size));
#endif /* DEBUG */
				if (buf[block_size +3] != calc_sum(&buf[3], block_size))
					bad_block++;
				break;
			case CRC:
				crc = calc_crc(&buf[3], block_size);
				crc_1 = crc >> 8;
				crc_2 = crc;
#ifdef DEBUG
				fprintf(stderr, "blk=%d, crc1=%d, crc2=%d\n", blk, crc_1, crc_2);
#endif /* DEBUG */
				if (buf[block_size +3] != crc_1 || buf[block_size +4] != crc_2)
					bad_block++;
				break;
			case NONE:
				return(0);
		}
					/* handle errors */
		if (bad_block) {
			clear_line(win, 12, 24, TRUE);
			if (err_method == CRC)
				waddstr(win, "CRC FAILED");
			else
				waddstr(win, "CHECKSUM FAILED");
			wrefresh(win);
			putc_line(NAK);
			err_count++;
			tot_err++;
			continue;
		}
					/* not really an error */
		if (out_of_sync) {
			/*
			 * If a perfect packet is off by 1 block number,
			 * (a lost ACK could cause this) then treat it as
			 * a good block but don't write it to disk.
			 */
			if (buf[1] == (unsigned char) blk-1)
				return(1);

			clear_line(win, 12, 24, TRUE);
			waddstr(win, "OUT OF SYNC");
			wrefresh(win);
			putc_line(NAK);
			err_count++;
			tot_err++;
			continue;
		}
		return(0);
	}
	beep();
	clear_line(win, 12, 24, TRUE);
	waddstr(win, "TOO MANY ERRORS");
	wrefresh(win);
	cancel_xfer(DOWN_LOAD);
	return(ERROR);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.