[comp.sys.mac.programmer] Macbinget.c XMODEM source

davet@oakhill.UUCP (David Trissel) (10/21/88)

[Sorry for posting here but our mailer refuses to send to comp.sources.mac]

What follows is my version of the MacBinary XMODEM upload utility which was
originally posted as "Mbget". I call it "Macbinget". This version has #ifdefs 
for System V compilation and the latest Sun 4.2 BSD (which for some reason 
won't compile the original "Mbget.c" any more.)

An important new feature is a -g option for self-correcting modems (or any
error free communications link) which speeds uploading by about 40%. [Your
milage may vary.] Users without error-correcting modems can use the -g
option if they want to gamble speed for upload failure.  (Any garbled data
causes the entire file transfer to abort.)

A companion posting is being done for my version of "Macbinput".

 -- Dave Trissel  Motorola Semiconductor Austin, Texas
                   ut-sally!cs.utexas.edu!oakhill!davet

**************** Cut Here *************************
/*
 * (originally Macget) -- receive file from macintosh using xmodem protocol
 * Dave Johnson, Brown University Computer Science
 *
 * (c) 1984 Brown University 
 * may be used but not sold without permission
 *
 */

/* To compile:

                      cc -O -o macbinget macbinget.c
   (For Sun BSD 4.2)  cc -O -DSUNBSD42 -o macbinget macbinget.c
   (For System V)     cc -O -DSYSV -o macbinget macbinget.c

      Latest modification 10/21/88 by Trissel -
 1. Fast transfer (-g) option for error-correcting modems or any
    error-free uplink. This option assumes all packets will be
    received valid and pre-ACKs them which amounts to about a 42% speedup. 
    Option can be used by anyone but the entire transfer will fail if any 
    packets are garbled.  (In other words: you're gambling that there will 
    be no communication errors for speed improvement.)
 2. General cleanup by removal of unused definitions and headers.
 3. Removed ancient Macterminal Beta 0.5X code. 
 4. Changed name to less cryptic "macbinget" from "mbget".
 5. Added #ifdefs to support System V and BSD 4.2 Sun compilation.

      Dave Trissel
      Motorola Inc.
      ut-sally!cs.utexas.edu!oakhill!davet

   This code is fundamentally from two earlier programmers:

	Jon Hueras
	Symantec/THINK Technologies
	singer@endor.harvard.edu

   who added 2-Byte CRC capability to code from:

	Dave Johnson, Brown University Computer Science
	ddj%brown@csnet-relay.arpa
	Brown University Computer Science

*/


/* if you have System V then define the following: */
	/* #define SYSV */

/* if you have Sun BSD 4.2 then define the following: */
	/* #define SUNBSD42 */

#ifdef SUNBSD42
/* RAW/ANYP no longer being found on latest Sun systems? (10/88) */
#define RAW 0x20
#define ANYP 0
#endif

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#ifdef SYSV
#include <termio.h>
#else
#include <sgtty.h>
#endif

#ifdef SYSV
#define NO_RENAME
#endif

#ifdef NO_RENAME
#define rename(old, new)	link(old, new); unlink(old)
#endif

#define RECORDBYTES 132
#define DATABYTES 128
#define NAMEBYTES 63

#define RETRIES 10
#define SOHTIMO 10
#define LINTIMO 20
#define CHRTIMO 2

#define MAXRECNO 0xff
#define BYTEMASK 0xff

#define TMO -1
#define DUP '\000'
#define SOH '\001'
#define EOT '\004'
#define ACK '\006'
#define NAK '\025'
#define CAN '\030'
#define EEF '\032'
#define ESC '\033'

#define H_NLENOFF 1
#define H_NAMEOFF 2
/* 65 <-> 80 is the FInfo structure */
#define H_TYPEOFF 65
#define H_AUTHOFF 69

#define H_LOCKOFF 81
#define H_DLENOFF 83
#define H_RLENOFF 87
#define H_CTIMOFF 91
#define H_MTIMOFF 95

#define H_OLD_DLENOFF 81
#define H_OLD_RLENOFF 85

#define TEXT 0
#define DATA 1
#define RSRC 2
#define FULL 3

int mode, txtmode, preackopt;

struct macheader {
	char m_name[NAMEBYTES+1];
	char m_type[4];
	char m_author[4];
	long m_datalen;
	long m_rsrclen;
	long m_createtime;
	long m_modifytime;
} mh;

struct filenames {
	char f_info[256];
	char f_data[256];
	char f_rsrc[256];
} files;

char tmpname[16];

int lastack, crc;
char buf[DATABYTES];

char usage[] = "usage: \"macbinget [-rdug] [filename]\"\n";

main(ac, av)
char **av;
{
	char *name;

	mode = FULL;
	name = "";
	preackopt = 0;
	ac--; av++;
	while (ac) {
		if (av[0][0] == '-') {
			switch (av[0][1]) {
			case 'g':
				preackopt++;
				break;
			case 'r':
				mode = RSRC;
				break;
			case 'd':
				mode = DATA;
				break;
			case 'u':
				mode = TEXT;
				break;
			default:
				fprintf(stderr, usage);
				exit(1);
			}
		}
		else {
			name = av[0];
		}
		ac--; av++;
	}

	setup_tty();
	if (get_sync()) {
		lastack = 0;
		txtmode = 0;
		recv_hdr(name);
		if (mode == TEXT) txtmode++;
		recv_file(files.f_data, mh.m_datalen, 1);
		txtmode = 0;
		recv_file(files.f_rsrc, mh.m_rsrclen, 0);
	}
	reset_tty();
}

recv_hdr(name)
char *name;
{
	long get4();
	int n;
	FILE *fp;
	char *np;

	strcpy(tmpname, "#machdrXXXXXX");
	mktemp(tmpname);
	recv_file(tmpname, (long)DATABYTES, 1);

	fp = fopen(tmpname, "r");
	if (fp == NULL) {
		perror("temp file");
		cleanup(-1);
	}
	fread(buf, 1, DATABYTES, fp);
	fclose(fp);

	if (name && *name) {
		n = strlen(name);
		if (n > NAMEBYTES) n = NAMEBYTES;
		strncpy(mh.m_name, name, n);
		mh.m_name[n] = '\0';
	}
	else {
		n = buf[H_NLENOFF] & BYTEMASK;
		if (n > NAMEBYTES) n = NAMEBYTES;
		strncpy(mh.m_name, buf + H_NAMEOFF, n);
		mh.m_name[n] = '\0';
	}
	for (np = mh.m_name; *np; np++)
		if (*np == ' ') *np = '_';

	if (mode == FULL) {
		sprintf(files.f_info, "%s.info", mh.m_name);
		rename(tmpname, files.f_info);
		tmpname[0] = '\0';
		sprintf(files.f_data, "%s.data", mh.m_name);
		sprintf(files.f_rsrc, "%s.rsrc", mh.m_name);
	}
	else {
		unlink(tmpname);
		tmpname[0] = '\0';
		switch (mode) {
		case RSRC:
			sprintf(files.f_data, "/dev/null");
			sprintf(files.f_rsrc, "%s.rsrc", mh.m_name);
			break;

		case DATA:
			sprintf(files.f_data, "%s.data", mh.m_name);
			sprintf(files.f_rsrc, "/dev/null");
			break;

		case TEXT:
			sprintf(files.f_data, "%s", mh.m_name);
			sprintf(files.f_rsrc, "/dev/null");
			break;
		}
	}

	strncpy(mh.m_type, buf + H_TYPEOFF, 4);
	strncpy(mh.m_author, buf + H_AUTHOFF, 4);
	mh.m_datalen = get4(buf + H_DLENOFF);
	mh.m_rsrclen = get4(buf + H_RLENOFF);
	mh.m_createtime = get4(buf + H_CTIMOFF);
	mh.m_modifytime = get4(buf + H_MTIMOFF);
}

recv_file(fname, bytes, more)
char *fname;
long bytes;
int more;
{
	register int status, n;
	FILE *outf;
	int naks = 0;

	outf = fopen(fname, "w");
	if (outf == NULL) {
		perror(fname);
		cleanup(-1);
	}
	for (;;) {
		if (!bytes) {
			if (!more)
				tputc(ACK);
			fclose(outf);
			return;
		}
		if (preackopt)
			tputc(ACK);
		status = rec_read(buf, DATABYTES);
		switch (status) {
		case EOT:
			if (!preackopt)
				tputc(ACK);
			fclose(outf);
			if (more) {
				purge(SOHTIMO);
				cleanup(-1);
			}
			return;
		case ACK:
			if (!preackopt)
				tputc(ACK);
			naks = 0;
			n = (bytes > DATABYTES) ? DATABYTES : bytes;
			bytes -= n;
			fwrite(buf, n, 1, outf);
			break;
		case DUP:
			if (!preackopt)
				tputc(ACK);
			naks = 0;
			break;
		case NAK:
			purge(CHRTIMO);
			if (!preackopt)
			   if (naks++ < RETRIES) {
				tputc(NAK);
				break;
			}
			/* fall through */
		case CAN:
			tputc(CAN);
			fclose(outf);
			/* unlink fname? */
			cleanup(-1);
			/* NOTREACHED */
		}
	}
}

get_sync()
{
	int c, i;
	
	for (;;) {
		if ((c = tgetc(15)) == TMO)
			break;
		if (c != ESC)
			continue;
		if ((c = tgetc(1)) == TMO)
			continue;
		if (c == 'b')
			break;
	}
	
	for (i = 0; i < 3; i++) {
		tputc('C');
		if ((c = tgetc(SOHTIMO)) != SOH)
			continue;
		tungetc(c);
		crc++;
		return 1;
	}
	
	for (i = 0; i < 3; i++) {
		tputc(NAK);
		if ((c = tgetc(SOHTIMO)) != SOH)
			continue;
		tungetc(c);
		return 1;
	}
	
	tputc(CAN);
	return 0;
}

rec_read(buf, recsize)
char buf[];
int recsize;
{
	int c, rec, rec_bar, cksum;
	long tgetrec();

	c = tgetc(SOHTIMO);
	switch (c) {
	case TMO:
	default:
		return NAK;
	case EOT:
		return EOT;
	case CAN:
		return CAN;
	case SOH:
		/* read header */
		rec = tgetc(CHRTIMO);
		if (rec == TMO)
			return NAK;
		rec_bar = tgetc(CHRTIMO);
		if (rec_bar == TMO)
			return NAK;

		/* check header */
		if (rec != MAXRECNO - rec_bar) return NAK;

		/* fill buffer */
		if ((cksum = tgetrec(buf, recsize, LINTIMO)) == TMO)
			return NAK;

		/* get checksum */
		c = tgetc(CHRTIMO);
		if (c == TMO)
			return NAK;
		if (crc) {
			if (c != ((cksum >> 8) & BYTEMASK))
				return NAK;
			c = tgetc(CHRTIMO);
			if (c == TMO)
				return NAK;
		}
		if (c != (cksum & BYTEMASK))
			return NAK;

		/* check record number */
		if (rec == lastack)
			return DUP;
		if (rec != ((lastack + 1) & MAXRECNO))
			return CAN;
		else {
			lastack = rec;
			return ACK;
		}
	}
	/* NOTREACHED */
}

purge(timeout)
int timeout;
{
	int c;

	do {
		c = tgetc(timeout);
	} while (c != TMO);
}

static int ttyfd;
static FILE *ttyf;
jmp_buf timobuf;

long tgetrec(buf, count, timeout)
char *buf;
int count, timeout;
{
	char *bp;
	int i, cksum;

	if (setjmp(timobuf))
		return TMO;
	
	alarm(timeout);
	i = fread(buf, 1, count, ttyf);
	alarm(0);
	if (i != count)
		return TMO;
	
	if (crc)
		cksum = calcrc(buf, count);
	
	if (!crc)
		cksum = 0;
	bp = buf;
	for (i = 0; i < count; bp++, i++) {
		if (!crc)
			cksum += *bp;
		if (txtmode && *bp == '\r')
			*bp = '\n';
	}
	
	return cksum & 0xFFFF;
}

tgetc(timeout)
int timeout;
{
	int c;

	if (setjmp(timobuf))
		return TMO;

	alarm(timeout);
	c = getc(ttyf);
	alarm(0);

	if (c == -1)	/* probably hung up or logged off */
		return EOT;
	else
		return c & BYTEMASK;
}

tungetc(c)
char c;
{
	ungetc(c, ttyf);
}

tputc(c)
char c;
{
	write(ttyfd, &c, 1);
}

timedout()
{
	signal(SIGALRM, timedout);	/* for pre-4.2 systems */
	longjmp(timobuf, 1);
}

#ifdef SYSV
static struct termio otty, ntty;
#else
static struct sgttyb otty, ntty;
#endif

/* should turn messages off */

setup_tty()
{
	int cleanup();
	int timedout();

	ttyf = stdin;
	ttyfd = fileno(stdout);
#ifdef SYSV
	ioctl(ttyfd, TCGETA, &otty);		/* get termio info */
#else
	ioctl(ttyfd, TIOCGETP, &otty);
#endif
	signal(SIGHUP, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGQUIT, cleanup);
	signal(SIGTERM, cleanup);
	signal(SIGALRM, timedout);
#ifdef SYSV
	ntty.c_iflag = BRKINT;				/* only interrupt on break */
	ntty.c_oflag = 0;					/* no output processing */
	ntty.c_cflag |= CS8;				/* 8 bit characters */
	ntty.c_lflag = 0;					/* no echoing */
	ntty.c_cc[VEOF] = 1;				/* "MIN" minimum chars before input */
	ntty.c_cc[VEOL] = 1;				/* "TIME" maximum .1 secs before feed */
	ioctl(ttyfd, TCSETAF, &ntty);		/* set mode and flush input */
#else
	ntty = otty;
	ntty.sg_flags = RAW|ANYP;
	ioctl(ttyfd, TIOCSETP, &ntty);
#endif
}

reset_tty()
{
#ifdef SYSV
	ioctl(ttyfd, TCSETAF, &otty);	/* reset after output drains */
#else
	sleep(5);	/* should wait for output to drain */
	ioctl(ttyfd, TIOCSETP, &otty);
#endif
}

cleanup(sig)
int sig;
{
	if (tmpname[0] != '\0')
		unlink(tmpname);
	reset_tty();
	exit(sig);
}

long
get4(bp)
char *bp;
{
	register int i;
	long value = 0;

	for (i = 0; i < 4; i++) {
		value <<= 8;
		value |= (*bp & BYTEMASK);
		bp++;
	}
	return value;
}

int calcrc(ptr,	count)
char *ptr;
int count;
	{
		int	crc, i;

		crc	= 0;
		while (--count >= 0) {
		 crc ^= ((int) *ptr++) << 8;
		 for (i = 0; i < 8; ++i)
				 if (crc & 0x8000)
			 crc = crc <<	1 ^ 0x1021;
				 else
			 crc <<= 1;
		 }
		return (crc	& 0xFFFF);
	}