[ont.micro.mac] macget -- mac -> unix file transfer

info-mac@utcsrgv.UUCP (info-mac) (07/01/84)

Date:     29 Jun 84 (Fri) 20:40:01 EDT
From: Dave Johnson <uw-beaver!ddj%brown.csnet@csnet-relay.arpa>
To: info-mac@sumex-aim.arpa
Subject:  macget -- mac -> unix file transfer 

I've received several requests for "macget" since I posted
"macput" to this list, and since info-mac is now being gatewayed
to fa.info-mac on usenet, I expect many more requests, hence this
posting.

Macget is set up to receive files sent by MacTerminal using a variant
of the MODEM7 protocol.  It is known to work on suns and vaxes running
4.2, but the "rename" call can be easily simulated under 4.1 (exercise
left for the reader).

Following is the source and the manual page; because of paranoia, 
everything is shifted over one tabstop.  Enjoy!
========
macget.c
========
	#include <stdio.h>
	#include <signal.h>
	#include <setjmp.h>
	#include <sys/ioctl.h>

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

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

	#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
	#define H_TYPEOFF 65
	#define H_AUTHOFF 69
	#define H_DLENOFF 81
	#define H_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];
		int m_datalen;
		int m_rsrclen;
	} mh;

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

	int lastack;
	char buf[DATABYTES];

	/*
	 * macget -- receive file from macintosh using xmodem protocol
	 *
	 * (c) 1984 Brown University Computer Science 
	 * may be used but not sold without permission
	 * written by Dave Johnson 5/22/84
	 * revised ddj 6/29/84
	 */
	char usage[] = "usage: \"macget [-rdu] [filename]\"\n";

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

		mode = FULL;
		name = "";
		ac--; av++;
		while (ac) {
			if (av[0][0] == '-') {
				switch (av[0][1]) {
				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 (send_sync() == ACK) {
			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;
	{
		int n;
		FILE *fp;
		char tmpname[16];
		char *np;

		strcpy(tmpname, "#machdrXXXXXX");
		mktemp(tmpname);
		recv_file(tmpname, 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);
			sprintf(files.f_data, "%s.data", mh.m_name);
			sprintf(files.f_rsrc, "%s.rsrc", mh.m_name);
		}
		else {
			unlink(tmpname);
			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.text", 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);
	}

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

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

	send_sync()
	{
		int c;

		for (;;) {
			c = tgetc(60);
			switch (c) {
			case ESC:
				break;
			case CAN:
			case EOT:
			case TMO:
				return c;
			default:
				continue;
			}
			c = tgetc(1);
			if (c != 'a')
				continue;
			tputc(ACK);
			return ACK;
		}
	}

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

		c = tgetc(SOHTIMO);
		switch (c) {
		case TMO:
			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 */
			cksum = tgetrec(buf, recsize, LINTIMO);
			if (cksum == TMO)
				return NAK;

			/* get checksum */
			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;

	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;
		
		cksum = 0;
		bp = buf;
		for (i = 0; i < count; i++) {
			cksum += *bp++;
			if (txtmode && *bp == '\r')
				*bp = '\n';
		}
		return cksum;
	}

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

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

	timedout()
	{
		longjmp(timobuf, 1);
	}

	static struct sgttyb otty, ntty;
	/* should turn messages off */

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

		ttyf = stdin;
		ttyfd = fileno(stdin);
		ioctl(ttyfd, TIOCGETP, &otty);
		signal(SIGHUP, cleanup);
		signal(SIGINT, cleanup);
		signal(SIGQUIT, cleanup);
		signal(SIGTERM, cleanup);
		signal(SIGALRM, timedout);
		ntty = otty;
		ntty.sg_flags = RAW|ANYP;
		ioctl(ttyfd, TIOCSETP, &ntty);
	}

	reset_tty()
	{
		sleep(2);	/* should wait for output to drain */
		ioctl(ttyfd, TIOCSETP, &otty);
	}

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

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

		for (i = 0; i < 4; i++) {
			value <<= 8;
			value |= (*bp & BYTEMASK);
			bp++;
		}
		return value;
	}
========
macget.l
========
	.TH MACGET local "29 June 1984"
	.UC 4
	.SH NAME
	macget \- receive file from macintosh via modem7 / macterminal
	.SH SYNOPSIS
	.B macput
	[
	.B \-rdu
	] [file]
	.SH DESCRIPTION
	.I Macget
	receives a file from a Macintosh running MacTerminal.
	MacTerminal should be set to use the modem7 protocol,
	and should have flow control disabled.
	.PP
	The optional 
	.I file
	parameter specifies the name to use when creating the unix files,
	otherwise the Mac file name is used (with spaces converted to underscores).
	.PP
	If none of the
	.B \-rdu
	flags are specified, 
	.I macget 
	receives three files from the Mac:
	.IB file .info ,
	.IB file .data ,
	and
	.IB file .rsrc .
	This option is useful for storing Mac files so they can
	be restored later using
	.IR macput .
	.PP
	The
	.B \-r
	flag specifies 
	.I resource
	mode.
	Only 
	.IB file .rsrc
	will be created, from the Mac file's resource fork.
	.PP
	The
	.B \-d
	flag specifies 
	.I data
	mode.
	Only
	.IB file .data
	will be created, containing the data fork of the Mac file.
	.PP
	The 
	.B \-u
	flag requests 
	.I unix
	mode, in which carriage returns are converted into
	unix newline characters, and the unix file
	.IB file .text
	is created.
	.SH SEE ALSO
	macput(local)
	.SH BUGS
	Doesn't work over flow controlled communication lines,
	or when using rlogin.
	.PP
	The current version of MacTerminal (-0.15X) has trouble sending
	a handful of specific files, with no recourse but to reboot the Mac.
	This problem occurs even when two Mac's are connected directly
	to each other, so maybe this will be fixed in the next version
	of MacTerminal.
	.PP
	MacTerminal does not provide a valid creation time in the header
	it sends out, nor does it check the validity of the time in the 
	header it receives, if indeed the time is encoded in the header.
	Therefore creation times of files returned to the Mac will be bogus.
	.SH AUTHOR
	Dave Johnson, Brown 6/29/84
===========
end of file
===========