[comp.sys.mac.programmer] Macbinput.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 Macbinput which is a MacBinary XMODEM download
program. This is an alteration of "Mbput.c" which was posted on the net
some time ago. The only difference is a name change to "Macbinput" and ifdefs
for System V and Sun 4.2 BSD compilation (and some cleanup.)

A companion posting is being done for my version of "Macbinget" which supports
faster uploading as an option as well as compilation on System V machines.

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

************** Cut Here *****************
/*
 * (originally macput) -- send file to Macintosh using MacBinary 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 macbinput macbinput.c
    (Sun 4.2 BSD) cc -O -DSUNBSD42 -o macbinput macbinput.c
    (System V)    cc -O -DSYSV -o macbinput macbinput.c

   Latest modifications 10/20/88 by Trissel -

 1. General cleanup by removal of unused definitions and headers.
 2. Added #ifdefs to support System V and BSD 4.2 Sun compilation.
 3. Removed ancient Macterminal Beta 0.5X code.
 4. Changed name to less cryptic "macbinput" from "mbput".

	Dave Trissel
	Motorola Inc.
	ut-sally!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
	ddj%brown@csnet-relay.arpa
	Brown University Computer Science

   who did the initial MacTerminal 1.1 transfer protocol.
*/

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

/* Sun BSD 4.2 systems should define the following: */
	/* #define SUNBSD42 */

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>
#ifdef SYSV
#include <termio.h>
#else
#include <sgtty.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>

#ifdef SUNBSD42
/* RAW is no longer being found on latest Sun system (??) (Trissel) */
#define RAW 0x20
#endif

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

#define RETRIES 10
#define ACKTIMO 10

#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;

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;

int recno, crc;
char buf[DATABYTES];

char usage[] =
    "usage: \"macbinput [-rdu] [-t type] [-c creator] [-n name] filename\"\n";

main(ac, av)
char **av;
{
	int n;
	char *filename;

	if (ac == 1) {
		fprintf(stderr, usage);
		exit(1);
	}

	mode = FULL;
	ac--; av++;
	while (ac) {
		if (av[0][0] == '-') {
			switch (av[0][1]) {
			case 'r':
				mode = RSRC;
				strncpy(mh.m_type, "????", 4);
				strncpy(mh.m_author, "????", 4);
				break;
			case 'u':
				mode = TEXT;
				strncpy(mh.m_type, "TEXT", 4);
				strncpy(mh.m_author, "EDIT", 4);
				break;
			case 'd':
				mode = DATA;
				strncpy(mh.m_type, "????", 4);
				strncpy(mh.m_author, "????", 4);
				break;
			case 'n':
				if (ac > 1) {
					ac--; av++;
					n = strlen(av[0]);
					if (n > NAMEBYTES) n = NAMEBYTES;
					strncpy(mh.m_name, av[0], n);
					mh.m_name[n] = '\0';
					break;
				}
				else goto bad_usage;
			case 't':
				if (ac > 1) {
					ac--; av++;
					strncpy(mh.m_type, av[0], 4);
					break;
				}
				else goto bad_usage;
			case 'c':
				if (ac > 1) {
					ac--; av++;
					strncpy(mh.m_author, av[0], 4);
					break;
				}
				else goto bad_usage;
			default:
bad_usage:
				fprintf(stderr, usage);
				exit(1);
			}
		}
		else {
			filename = av[0];
		}
		ac--; av++;
	}

	setup_tty();
	find_files(filename, mode);
	if (mode != FULL)
		forge_info();

	if (send_sync()) {
		recno = 1;
		txtmode = 0;
		send_file(files.f_info, 1);

		if (mode != FULL)
			unlink(files.f_info);

		if (mode == TEXT) txtmode++;
		send_file(files.f_data, 1);

		txtmode = 0;
		send_file(files.f_rsrc, 0);
	}
	reset_tty();
}

find_files(filename, mode)
char *filename;
{
	int n, tdiff;
	struct stat stbuf;

	sprintf(files.f_data, "%s.data", filename);
	sprintf(files.f_rsrc, "%s.rsrc", filename);

	if (mode == FULL) {
		sprintf(files.f_info, "%s.info", filename);
		if (stat(files.f_info, &stbuf) != 0) {
			perror(files.f_info);
			cleanup(-1);
		}
		return;
	}
	else {
		strcpy(files.f_info, "#machdrXXXXXX");
		mktemp(files.f_info);
	}

	if (mode == RSRC) {
		strcpy(files.f_data, "/dev/null");
		if (stat(files.f_rsrc, &stbuf) != 0) {
			strcpy(files.f_rsrc, filename);
			if (stat(files.f_rsrc, &stbuf) != 0) {
				perror(files.f_rsrc);
				cleanup(-1);
			}
		}
		mh.m_datalen = 0;
		mh.m_rsrclen = stbuf.st_size;
	}
	else {
		strcpy(files.f_rsrc, "/dev/null");
		if (stat(files.f_data, &stbuf) != 0) {
			sprintf(files.f_data, "%s.text", filename);
			if (stat(files.f_data, &stbuf) != 0) {
				strcpy(files.f_data, filename);
				if (stat(files.f_data, &stbuf) != 0) {
					perror(files.f_data);
					cleanup(-1);
				}
			}
		}
		mh.m_datalen = stbuf.st_size;
		mh.m_rsrclen = 0;
	}

	if (mh.m_name[0] == '\0') {
		n = strlen(filename);
		if (n > NAMEBYTES) n = NAMEBYTES;
		strncpy(mh.m_name, filename, n);
		mh.m_name[n] = '\0';
	}
}

forge_info()
{
	int n;
	char *np;
	FILE *fp;

	for (np = mh.m_name; *np; np++)
		if (*np == '_') *np = ' ';

	buf[H_NLENOFF] = n = np - mh.m_name;
	strncpy(buf + H_NAMEOFF, mh.m_name, n);
	strncpy(buf + H_TYPEOFF, mh.m_type, 4);
	strncpy(buf + H_AUTHOFF, mh.m_author, 4);
	put4(buf + H_DLENOFF, mh.m_datalen);
	put4(buf + H_RLENOFF, mh.m_rsrclen);
	put4(buf + H_CTIMOFF, mh.m_createtime);
	put4(buf + H_MTIMOFF, mh.m_modifytime);
	fp = fopen(files.f_info, "w");
	if (fp == NULL) {
		perror("temp file");
		cleanup(-1);
	}
	fwrite(buf, 1, DATABYTES, fp);
	fclose(fp);
}

send_sync()
	{
		int c;
		
		tputc(ESC);
		tputc('b');

		for (;;) {

			if ((c = tgetc(ACKTIMO)) == TMO)
			  {
				return(0);
				}

			if (c == NAK)
			  {
				return(1);
				}

			if (c == 'C') {
				crc++;
				return(1);
			}
		}
	}

send_file(fname, more)
char *fname;
int more;
{
	register int status, i, n;
	FILE *inf;

	inf = fopen(fname, "r");
	if (inf == NULL) {
		perror(fname);
		cleanup(-1);
	}
	for (;;) {
		n = fread(buf, 1, DATABYTES, inf);
		if (n > 0) {
			for (i = 0; i < RETRIES; i++) {
				send_rec(buf, DATABYTES);
				while ((status = tgetc(ACKTIMO)) != ACK && status != NAK && status != CAN);
				if (status != NAK)
					break;
			} 
			if (status != ACK) {
				if (status != CAN)
					while ((status = tgetc(ACKTIMO)) != CAN);
				fclose(inf);
				cleanup(-1);
				/* NOTREACHED */
			}
		}
		if (n < DATABYTES) {
			if (!more) {
				tputc(EOT);
				tgetc(ACKTIMO);
			}
			return;
		}
		recno++;
		recno &= MAXRECNO;
	}
}

send_rec(buf, recsize)
char buf[];
int recsize;
{
	int i, cksum;
	char *bp;
 
	if (txtmode || !crc) {
		cksum = 0;
		bp = buf;
		for (i = 0; i < recsize; i++, bp++) {
			if (txtmode && *bp == '\n')
				*bp = '\r';
			cksum += *bp;
		}
	}

	if (crc)
		cksum = calcrc(buf, recsize);

	tputc(SOH);
	tputc((char) recno);
	tputc((char) (MAXRECNO - recno));
	tputrec(buf, recsize);
	
	if (crc) {
		tputc((char) (cksum >> 8));
		tputc((char) cksum);
	} else
		tputc((char) cksum);
}

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

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;
}

tputrec(buf, count)
char *buf;
int count;
{
	write(ttyfd, buf, count);
}

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);
	ntty = otty;
#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.sg_flags = RAW;
	ioctl(ttyfd, TIOCSETP, &ntty);
#endif
}

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

cleanup(sig)
int sig;
{
	reset_tty();
	exit(sig);
}

put4(bp, value)
char *bp;
long value;
{
	register int i, c;

	for (i = 0; i < 4; i++) {
		c = (value >> 24) & BYTEMASK;
		value <<= 8;
		*bp++ = c;
	}
}

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);
	}