[fa.info-mac] New VMS Versions of macput, macget, xbin

info-mac@uw-beaver (04/06/85)

From: stew%lhasa@harvard.ARPA

$ COPY SYS$INPUT: 00README.TXT
$ DECK/DOLLAR=">> End <<"
Here is a new VMS version of macget, macput, xbin and other things.
These programs were originally written by Dave Johnson at Brown
University, and were modified for VMS and the Vax-11 C compiler by
Stewart Rubenstein at Harvard University.  It may be used and
distributed but not sold for profit.

The two programs which I have added are DUMPINF and FIXINF.
DUMPINF will list the contents of a .INF file stored on your vax.
FIXINF creates a new .INF file from an existing one by zeroing
the location, flags, lock and all undefined fields.  I have found
that this occasionally helps in downloading a recalcitrant file.
These may both be used with any number of arguments, any of which
may include wildcards.  The fgen() function in FGEN.C is a general
purpose C interface for this purpose.

The low-level i/o routines have been sped up considerably.  MacGet, in
particular, should work much better and faster now.

This file should be unpacked by executing it as a command procedure.  It
will create the files 00README.TXT (this file), DUMPINF.C, FGEN.C,
FIXINF.C, GETPUT.C, MACGET.C, MACGET.HLP, MACGETPUT.H, MACPUT.C,
MACPUT.HLP, MAKE.COM, RDT.C and TTYIO.MAR.  The command procedure
MAKE.COM will compile and link everything.

Please send comments, bug reports, etc to:
   Stew Rubenstein
   rubenstein@harvard.arpa
   {ihnp4, seismo, ut-sally}!harvard!rubenstein
>> End <<
$ COPY SYS$INPUT: DUMPINF.C
$ DECK/DOLLAR=">> End <<"
#include stdio
#include "macgetput.h"

/*  MAXFILE is the maximum number of files which can be matched by a
    wildcard filename.
*/

#define MAXFILE 200

main(argc, argv)
    int argc;
    char **argv;
{
    char buf[DATABYTES];
    FILE *input;
    char *mtchs[MAXFILE];
    int i, fcount;

    while (--argc > 0) {
        fcount = fgen(*++argv, mtchs, MAXFILE, "*.inf");
        for (i = 0;  i < fcount;  i++) {
            input = fopen(mtchs[i], "r");
            if (input == NULL)
                perror(mtchs[i]);
            else {
		printf("\n%s\n", mtchs[i]);
                dump_inf(input);
	    }
            free(mtchs[i]);
        }
    }
}

dump_inf(input)
    FILE *input;
{
    char buf[DATABYTES];

    if (fread(buf, 1, DATABYTES, input) == 0)
        perror("fread");
    if (fclose(input) != 0)
        perror("fclose");
    printf("Name: %.*s\nType: %.4s  Creator: %.4s  Finder flags: %04.4X\n",
        (int)(buf[H_NLENOFF]), buf+H_NAMEOFF, buf+H_TYPEOFF, buf+H_AUTHOFF,
        get2(buf+H_FFLGOFF));
    printf("Location: %d, %d  Folder: %d  Lock: %d\nData len: %d  Rsrc len: %d\n",
        get2(buf+H_LOCOFF), get2(buf+H_LOCOFF+2), get2(buf+H_FOLDOFF),
        get2(buf+H_LOCKOFF), get4(buf+H_DLENOFF), get4(buf+H_RLENOFF));
}

int
get2(bp)
char *bp;
{
        return ((bp[0] << 8) & BYTEMASK) | (bp[1] & BYTEMASK);
}

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;
}
>> End <<
$ COPY SYS$INPUT: FGEN.C
$ DECK/DOLLAR=">> End <<"
/*  fgen.c  --  wildcard expansion for vms

    Stew Rubenstein, Harvard Chemical Labs, March, 1985
*/

/*  This routine takes a pattern and a default string and returns an array
    of pointers to the filenames which match.  The strings are allocated
    with malloc().  The value of the function is the number of matches
    found, or -1 if any error is encountered.
*/

#include <descrip.h>
#include <rmsdef.h>

int
fgen(pat, resarry, maxfiles, dfltname)
    char *pat, *resarry[];
    int maxfiles;
    char *dfltname;
{
    struct dsc$descriptor_s file_spec, result, deflt;
    long context;
    int count, slen, status;
    char result_string[256], *strchr();

    file_spec.dsc$w_length  = strlen(pat);
    file_spec.dsc$b_dtype   = DSC$K_DTYPE_T;
    file_spec.dsc$b_class   = DSC$K_CLASS_S;
    file_spec.dsc$a_pointer = pat;

    result.dsc$w_length  = sizeof result_string;
    result.dsc$b_dtype   = DSC$K_DTYPE_T;
    result.dsc$b_class   = DSC$K_CLASS_S;
    result.dsc$a_pointer = result_string;

    deflt.dsc$w_length  = strlen(dfltname);
    deflt.dsc$b_dtype   = DSC$K_DTYPE_T;
    deflt.dsc$b_class   = DSC$K_CLASS_S;
    deflt.dsc$a_pointer = dfltname;

    count = 0;
    context = 0;
    while (count < maxfiles
	   && (status = LIB$FIND_FILE(&file_spec, &result, &context, &deflt))
		== RMS$_NORMAL) {
	slen = strchr(result_string, ' ') - result_string;
    	resarry[count] = malloc(slen + 1);
	strncpy(resarry[count], result_string, slen);
	resarry[count][slen] = '\0';
	++count;
    }
#ifdef VMS_V4
    lib$find_file_end(&context);	/* Only on V4 systems */
#endif
    if (status == RMS$_FNF) return(0);
    if (status == RMS$_NMF) return(count);
    return(-1);
}
>> End <<
$ COPY SYS$INPUT: FIXINF.C
$ DECK/DOLLAR=">> End <<"
#include stdio
#include "macgetput.h"

/*  MAXFILE is the maximum number of files which can be matched by a
    wildcard filename.
*/

#define MAXFILE 200

main(argc, argv)
    int argc;
    char **argv;
{
    char buf[DATABYTES];
    FILE *input, *output;
    char *mtchs[MAXFILE], *strchr();
    int i, fcount;

    while (--argc > 0) {
        fcount = fgen(*++argv, mtchs, MAXFILE, "*.inf");
        for (i = 0;  i < fcount;  i++) {
            input = fopen(mtchs[i], "r");
            if (input == NULL)
                perror(mtchs[i]);
            else {
		printf("\n%s\n", mtchs[i]);
		*strchr(mtchs[i], ';') = '\0';
		output = fopen(mtchs[i], "w");
		if (output == NULL)
		    perror(mtchs[i]);
		else
		    dump_inf(input, output);
	    }
            free(mtchs[i]);
        }
    }
}

dump_inf(input, output)
    FILE *input, *output;
{
    int i;
    char buf[DATABYTES], outbuf[DATABYTES];

    if (fread(buf, 1, DATABYTES, input) == 0)
        perror("fread");
    printf("Name: %.*s\nType: %.4s  Creator: %.4s  Finder flags: %04.4X\n",
        (int)(buf[H_NLENOFF]), buf+H_NAMEOFF, buf+H_TYPEOFF, buf+H_AUTHOFF,
        get2(buf+H_FFLGOFF));
    printf("Location: %d, %d  Folder: %d  Lock: %d\nData len: %d  Rsrc len: %d\n",
        get2(buf+H_LOCOFF), get2(buf+H_LOCOFF+2), get2(buf+H_FOLDOFF),
        get2(buf+H_LOCKOFF), get4(buf+H_DLENOFF), get4(buf+H_RLENOFF));
    for (i = 0;  i < DATABYTES;  i++)
    	outbuf[i] = 0;
    outbuf[H_NLENOFF] = buf[H_NLENOFF];
    strncpy(outbuf + H_NAMEOFF, buf + H_NAMEOFF, (int) buf[H_NLENOFF]);
    * (int *) (outbuf + H_TYPEOFF) = * (int *) (buf + H_TYPEOFF);
    * (int *) (outbuf + H_AUTHOFF) = * (int *) (buf + H_AUTHOFF);
    * (int *) (outbuf + H_DLENOFF) = * (int *) (buf + H_DLENOFF);
    * (int *) (outbuf + H_RLENOFF) = * (int *) (buf + H_RLENOFF);
    * (int *) (outbuf + H_CTIMOFF) = * (int *) (buf + H_CTIMOFF);
    * (int *) (outbuf + H_MTIMOFF) = * (int *) (buf + H_MTIMOFF);
    fwrite(outbuf, 1, DATABYTES, output);
    if (fclose(input) != 0)
        perror("fclose(input)");
    if (fclose(output) != 0)
        perror("fclose(output)");
}

int
get2(bp)
char *bp;
{
        return ((bp[0] << 8) & BYTEMASK) | (bp[1] & BYTEMASK);
}

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;
}
>> End <<
$ COPY SYS$INPUT: GETPUT.C
$ DECK/DOLLAR=">> End <<"
#include stdio
#include signal
#include "macgetput.h"

extern FILE *dbg_file;
extern int txtmode;
extern char tmpname[];

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

	cksum = 0;
	bp = buf;
	for (i = 0; i < count; i++) {
		timed_in(&c, &timeout);
		if (c < 0)
		{
			if (dbg_file != NULL)
				dbg_buf(buf, i, i, "tgetrec: timeout after %d chars rec=%s\n");
			return TMO;
		}
		cksum += c;
		if (txtmode && c == '\r')
			c = '\n';
		*(bp++) = c;
	}
	if (dbg_file != NULL)
		dbg_buf(buf, count, cksum, "tgetrec: cksum=%d rec=%s\n");
	return cksum;
}

dbg_buf(buf, count, num, label)
    char *buf;
    int count, num;
    char *label;
{
	char outbuf[512], *outptr;
	int i, c;

	outptr = outbuf;
	for (i = 0;  i < count;  i++)
	{
		c = buf[i] & BYTEMASK;
		if (c > 127)
		{
		    *outptr++ = '&';
		    c &= 127;
		}
		if (c < 32 || c == 127)
		{
		    *outptr++ = '#';
		    c ^= 64;
		}
		if (c == '&' || c == '#')
		    *outptr++ = '#';
		*outptr++ = c;
	}
	*outptr++ = '\0';
	fprintf(dbg_file, label, num, outbuf);
}

setup_tty()
{
	int cleanup();

	raw_mode();
	signal(SIGHUP, cleanup);
	signal(SIGINT, cleanup);
	signal(SIGQUIT, cleanup);
	signal(SIGTERM, cleanup);
}

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

tgetc(timeout)
int timeout;
{
	int c;

	timed_in(&c, &timeout);
	if (c == -1)	/* probably hung up or logged off */
	{
		if (dbg_file != NULL) fprintf(dbg_file, "tgetc: timeout\n");
		return EOT;
	}
	else
	{
		if (dbg_file != NULL) fprintf(dbg_file, "tgetc: %d\n", c);
		return c & BYTEMASK;
	}
}

tputrec(buf, count)
char *buf;
int count;
{
	int i, c;
	for (i = 0;  i < count;  i++)
	{
		c = buf[i];
		out(&c);
	}
	flush();
	if (dbg_file != NULL) dbg_buf(buf, count, count, "tputrec(%d): %s\n");
}

tputc(c)
char c;
{
	if (dbg_file != NULL) fprintf(dbg_file, "tputc: %d\n", (int) c);
	out(&c);
	flush();
}
>> End <<
$ COPY SYS$INPUT: MACGET.C
$ DECK/DOLLAR=">> End <<"
#include stdio
#include signal
#include setjmp
#include ctype
#include "macgetput.h"

struct macheader mh;
struct filenames files;
int mode, txtmode;
int pre_beta;	/* -o flag; for compatibility with MacTerminal Version -0.15X */
char tmpname[16];
int lastack;
char buf[DATABYTES];
FILE *dbg_file;

/*
 * macget -- receive file from macintosh using xmodem protocol
 * Dave Johnson, Brown University Computer Science
 * Stew Rubenstein, Harvard University Chemical Labs
 *
 * (c) 1984 Brown University 
 * (c) 1985 President and Fellows of Harvard College
 * may be used but not sold without permission
 *
 * created ddj 5/22/84 
 * revised ddj 6/29/84 -- added [-rdu] options
 * revised ddj 7/16/84 -- protocol changes for MacTerminal Beta Version 0.5X
 * revised ddj 7/31/84 -- pre-4.2 signal bugs fixed in timedout()
 * revised sdr 2/28/85 -- Converted to VMS Vax-11 C.
 */

char usage[] = "usage: \"macget [-o] [-rdu] [filename]\"\n";

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

	mode = FULL;
	name = "";
	dbg_file = NULL;
	ac--; av++;
	while (ac) {
		if (av[0][0] == '-') {
			switch (av[0][1]) {
			case 'x':
				dbg_file = fopen("debug.dat", "w");
				break;
			case 'r':
				mode = RSRC;
				break;
			case 'd':
				mode = DATA;
				break;
			case 'u':
				mode = TEXT;
				break;
			case 'o':
				pre_beta++;
				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();
}

make_name(dest, type, name)
/*
 *  Construct a valid VMS file name.
 */
	char *dest, *type, *name;
{
	int i;
	char *dp, *np;

	for (dp = dest, np = name;  dp - dest < 9 && *np != '\0';  np++)
		if (isalpha(*np))
			*dp++ = *np;
	*dp++ = '.';
	for (np = type, i = 0;  i < 3 && *np != '\0';  np++)
		if (isalpha(*np))
		{
			*dp++ = *np;
			++i;
		}
	*dp = '\0';
}

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

	strcpy(tmpname, TMPNAME);
	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) {
		make_name(files.f_info, "info", mh.m_name);
		rename(tmpname, files.f_info);
		tmpname[0] = '\0';
		make_name(files.f_data, "data", mh.m_name);
		make_name(files.f_rsrc, "rsrc", mh.m_name);
	}
	else {
		delete(tmpname);
		tmpname[0] = '\0';
		switch (mode) {
		case RSRC:
			strcpy(files.f_data, "_NL:");
			make_name(files.f_rsrc, "rsrc", mh.m_name);
			break;

		case DATA:
			make_name(files.f_data, "data", mh.m_name);
			strcpy(files.f_rsrc, "_NL:");
			break;

		case TEXT:
			make_name(files.f_data, "text", mh.m_name);
			strcpy(files.f_rsrc, "_NL:");
			break;
		}
	}

	strncpy(mh.m_type, buf + H_TYPEOFF, 4);
	strncpy(mh.m_author, buf + H_AUTHOFF, 4);
	if (pre_beta) {
		mh.m_datalen = get4(buf + H_OLD_DLENOFF);
		mh.m_rsrclen = get4(buf + H_OLD_RLENOFF);
	}
	else {
		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;

	lastack = 0;
	outf = fopen(fname, "w");
	if (outf == NULL) {
		perror(fname);
		cleanup(-1);
	}
	for (;;) {
		status = rec_read(buf, DATABYTES);
		switch (status) {
		case EOT:
			if (!pre_beta)
				tputc(ACK);
			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:
	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 */
		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 */
}

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;
}
>> End <<
$ COPY SYS$INPUT: MACGET.HLP
$ DECK/DOLLAR=">> End <<"
     macget - receive file from	macintosh via xmodem/macterminal

SYNOPSIS
     macget [ -rdu ] [ -o ] [file]

DESCRIPTION
     Macget receives a file from a Macintosh running MacTerminal.
     The File Transfer settings	should specify the "XModem"
     transfer method and a "MacTerminal" remote	system.	 This
     program is	designed for use with the 0.5 Beta and newer ver-
     sions of MacTerminal, but includes	a compatibility	option
     for the older -0.15X Almost-Alpha version.

     The optional file parameter specifies the name to use when
     creating the unix files, otherwise	the Mac	file name is used
     (with spaces converted to underscores).

     If	none of	the -rdu flags are specified, macget receives
     three files from the Mac: file.inf, file.dat, and
     file.rsr.	 This mode is useful for storing Mac files so
     they can be restored later	using macput.

     The -r flag specifies resource mode.  Only	file.rsr will be
     created, from the Mac file's resource fork.

     The -d flag specifies data	mode.  Only file.dat will be
     created, containing the data fork of the Mac file.

     The -u flag requests unix mode, in	which carriage returns
     are converted into	unix newline characters, and the unix
     file file.text is created.  Note -- this mode should also
     be used for VMS text files.

     The -o flag specifies "old" (version -0.15X) MacTerminal
     compatibility mode.  You must manually disable XON/XOFF flow
     control in	this version to	perform	file transfer; this is
     done automatically	in the newer versions.

AUTHOR
     Dave Johnson, Brown 7/25/84
>> End <<
$ COPY SYS$INPUT: MACGETPUT.H
$ DECK/DOLLAR=">> End <<"
#define TMPNAME "tmp:macXXXXXX"

/* Mac time of 00:00:00 GMT, Jan 1, 1970 */
#define TIMEDIFF 0x7c25b080

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

#define RETRIES 10
#define SOHTIMO 10
#define LINTIMO 20
#define CHRTIMO 1
#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_FFLGOFF 73
#define H_LOCOFF  75
#define H_FOLDOFF 79
#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

/*  In VMS version 4, we will be able to do something better. */

#ifdef vax11c
# define rename(old, new)
# define unlink(file) delete(file)
#else
# ifdef NO_RENAME
#  define rename(old, new)	link(old, new); unlink(old)
# endif
#endif

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

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

>> End <<
$ COPY SYS$INPUT: MACPUT.C
$ DECK/DOLLAR=">> End <<"
#include stdio
#include signal
#include setjmp
#include types
#include time
#include timeb
#include "macgetput.h"

int mode, txtmode;
int pre_beta;	/* -o flag; for compatibility with MacTerminal Version -0.15X */

struct macheader mh;
struct filenames files;
int recno;
char buf[DATABYTES];
FILE *dbg_file;

/*
 * macput -- send file to macintosh using xmodem protocol
 * Dave Johnson, Brown University Computer Science
 * Stew Rubenstein, Harvard University Chemical Labs
 *
 * (c) 1984 Brown University 
 * (c) 1985 President and Fellows of Harvard College
 * may be used but not sold without permission
 *
 * created ddj 6/17/84 
 * revised ddj 7/16/84 -- protocol changes for MacTerminal Beta Version 0.5X
 * revised ddj 7/31/84 -- pre-4.2 signal bugs fixed in timedout()
 * revised ddj 7/31/84 -- fixed timeout problem in initial handshake
 * revised sdr 2/28/85 -- Converted to VMS Vax-11 C
 */
char usage[] =
    "usage: \"macput [-o] [-rdu] [-t type] [-a author] [-n name] filename\"\n";

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

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

	mode = FULL;
	dbg_file = NULL;
	ac--; av++;
	while (ac) {
		if (av[0][0] == '-') {
			switch (av[0][1]) {
			case 'x':
				dbg_file = fopen("debug.dat", "w");
				break;
			case 'r':
				mode = RSRC;
				strncpy(mh.m_type, "APPL", 4);
				strncpy(mh.m_author, "CCOM", 4);
				break;
			case 'u':
				mode = TEXT;
				strncpy(mh.m_type, "TEXT", 4);
				strncpy(mh.m_author, "MACA", 4);
				break;
			case 'd':
				mode = DATA;
				strncpy(mh.m_type, "TEXT", 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 'a':
				if (ac > 1) {
					ac--; av++;
					strncpy(mh.m_author, av[0], 4);
					break;
				}
				else goto bad_usage;
			case 'o':
				pre_beta++;
				break;
			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() == ACK) {
		txtmode = 0;
		send_file(files.f_info, 1);
		if (mode != FULL)
			delete(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, fd, status;
	struct tm *tp;
	struct timeb tbuf;
	time_t mtime;

	sprintf(files.f_data, "%s.dat", filename);
	sprintf(files.f_rsrc, "%s.rsr", filename);

	if (mode == FULL) {
		sprintf(files.f_info, "%s.inf", filename);
		if ((fd = open(files.f_info, 0)) < 0) {
			perror(files.f_info);
			cleanup(-1);
		}
		close(fd);
		return;
	}
	else {
		strcpy(files.f_info, TMPNAME);
		mktemp(files.f_info);
	}

	if (mode == RSRC) {
		strcpy(files.f_data, "_NL:");
		if ((fd = open(files.f_rsrc, 0)) < 0) {
			strcpy(files.f_rsrc, filename);
			if ((fd = open(files.f_rsrc, 0)) < 0) {
				perror(files.f_rsrc);
				cleanup(-1);
			}
		}
		mh.m_datalen = 0;
		mh.m_rsrclen = lseek(fd, 0, 2);
		close(fd);
		status = revdate(files.f_rsrc, (char *) 0, &mtime);
	}
	else {
		strcpy(files.f_rsrc, "_NL:");
		if ((fd = open(files.f_data, 0)) < 0) {
			sprintf(files.f_data, "%s.txt", filename);
			if ((fd = open(files.f_data, 0)) < 0) {
				strcpy(files.f_data, filename);
				if ((fd = open(files.f_data, 0)) < 0) {
					perror(files.f_data);
					cleanup(-1);
				}
			}
		}
		mh.m_datalen = lseek(fd, 0, 2);
		mh.m_rsrclen = 0;
		close(fd);
		status = revdate(files.f_data, (char *) 0, &mtime);
	}

	if (!pre_beta && status) {
		ftime(&tbuf);
		tp = localtime(&tbuf.time);
		tdiff = TIMEDIFF - tbuf.timezone * 60;
		if (tp->tm_isdst)
			tdiff += 60 * 60;
		mh.m_createtime = mtime + tdiff;
		mh.m_modifytime = mtime + tdiff;
	}

	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);
	if (pre_beta) {
		put4(buf + H_OLD_DLENOFF, mh.m_datalen);
		put4(buf + H_OLD_RLENOFF, mh.m_rsrclen);
	}
	else {
		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, i;

	for (i = 0; i < 3; i++) {
		tputc(ESC);
		tputc('a');
		while ((c = tgetc(ACKTIMO)) != TMO) {
			switch (c) {
			case CAN:
			case EOT:
			case ACK:
				return c;
			default:
				continue;
			}
		}
		fprintf(dbg_file == NULL ? stderr : dbg_file,
			"starting handshake timeout\r\n");
	}
	fprintf(dbg_file == NULL ? stderr : dbg_file, "giving up\r\n");
	return CAN;
}

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

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

send_rec(buf, recsize)
char buf[];
int recsize;
{
	int i, cksum;
	char *bp;

	cksum = 0;
	bp = buf;
	for (i = 0; i < recsize; i++, bp++) {
		if (txtmode && *bp == '\n')
			*bp = '\r';
		cksum += *bp;
	}

	tputc(SOH);
	tputc((char)recno);
	tputc((char)(MAXRECNO - recno));
	tputrec(buf, recsize);
	tputc((char)cksum);
}

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;
	}
}
>> End <<
$ COPY SYS$INPUT: MACPUT.HLP
$ DECK/DOLLAR=">> End <<"
macput - send file to macintosh via modem7/macterminal

SYNOPSIS
     macput file
     macput [ -rdu ] file [ -t type ] [ -a author ] [ -n name ]

DESCRIPTION
     Macput sends a file to a Macintosh running MacTerminal.  The File
     Transfer settings should specify the "XModem" transfer method and a
     "MacTerminal" remote system. This program is designed for use with
     the 0.5 Beta and newer versions of MacTerminal, but includes a
     compatibility option for the older -0.15X Almost-Alpha version.

     If none of the -rdu flags are specified, macput sends three files
     to the mac: file.inf, file.dat, and file.rsr.  This is useful for
     returning files to the mac which were stored using macget.

     The -r flag specifies resource mode.  Either file.rsr or file will
     be sent to the Mac, along with a forged .inf file and an empty .dat
     file.  The file sent becomes the resource fork of the Mac file.

     The -d flag specifies data mode.  Either file.dat, file.text or
     file will be sent to the Mac, along with a forged .inf file and an
     empty .rsr file.  The file sent becomes the data fork of the Mac
     file.

     The -u flag requests unix mode, which is the same as data mode mode
     except unix newline characters are converted into carriage returns.
     This mode should also be used for transferring VMS text files.

     The -o flag specifies "old" (version -0.15X) MacTerminal
     compatibility mode.  You must manually disable XON/XOFF flow
     control in this version to perform file transfer; this is done
     automatically in the newer versions.

     The remaining options serve to override the default file type,
     author, and file name to be used on the Mac.  The default type and
     author for resource mode are "APPL" and "CCOM".  data mode defaults
     are "TEXT", "????", and unix mode defaults are "TEXT" and "MACA".

BUGS
     Doesn't work over flow controlled communication lines, or when
     using rlogin.

     Doesn't set the bundle bit on resource files, to incorporate any
     icons into the Desk Top.  Use setfile to set the bundle bit.

FEATURES
     Properly initializes the Creation Date.

AUTHOR
     Dave Johnson, Brown 7/25/84
>> End <<
$ COPY SYS$INPUT: MAKE.COM
$ DECK/DOLLAR=">> End <<"
$ cc macput, macget, rdt, getput
$ macro ttyio
$ link macput, rdt, ttyio, getput
$ link macget, ttyio, getput
>> End <<
$ COPY SYS$INPUT: RDT.C
$ DECK/DOLLAR=">> End <<"
#module revdate

/*  REVDATE(FILE, DFILE, RDT) returns the revision time and date for the
    specified vms file.  FILE is the vms file specification string, DFILE is
    the default filename string (may be (char *) 0) and RDT is returned with
    the time in unix format (seconds since 00:00:00, January 1, 1970).
    The value of the function is 1 if successful, 0 if any errors encountered.

(c) Stew Rubenstein, Harvard Chemical Labs, February, 1985
May be used but not sold for profit without written permission from the author.
*/

#include rms
#include types

typedef struct { unsigned a : 32;  unsigned b : 32; } quad;

int
revdate(file, dfile, rdt)
    char *file, *dfile;
    time_t *rdt;
{
    struct FAB fab;
    struct RAB rab;
    struct XABDAT xab;
    int status;
    long int hsecs, hnano;

    fab = cc$rms_fab;
    rab = cc$rms_rab;
    xab = cc$rms_xabdat;

    rab.rab$l_fab = &fab;
    fab.fab$l_xab = &xab;
    fab.fab$l_fna = file;
    fab.fab$b_fns = strlen(file);
    if (dfile != 0)
    {
	fab.fab$l_dna = dfile;
	fab.fab$b_dns = strlen(dfile);
    }
    status = sys$open(&fab);
/*    vms_to_unix_time(&xab.xab$q_rdt, rdt); */
    lib$ediv(&1000000000, &xab.xab$q_rdt, &hsecs, &hnano);
    hsecs = hsecs - 35067168; /* Number of hundreds of secs from */
    	    	    	      /* Nov 17, 1858 to Jan 1, 1970     */
    *rdt = hsecs * 100 + hnano / 10000000;
    if (status == RMS$_NORMAL) status = sys$close(&fab);
    return status == RMS$_NORMAL;
}
>> End <<
$ COPY SYS$INPUT: TTYIO.MAR
$ DECK/DOLLAR=">> End <<"
	.TITLE TTYIO -- Subroutines for simple terminal I/O

;  This file supplies seven subroutines for terminal I/O.  They are:
;
;	IN(character.wl.r)
;		Read one character.
;	OUT(character.rl.r)
;		Write one character (buffered).
;	TIMED_IN(character.wl.r, seconds.rl.r)
;		Read one character with timeout.  If none are available within
;		SECONDS seconds, then the value returned is -1.
;	PURGE()
;		Clean out any characters waiting in the input buffer.
;	FLUSH()
;		Actually write characters which have been buffered with OUT.
;	RAW_MODE()
;		Set the terminal to PASSALL mode.
;	RESET_TTY()
;		Reset the terminal to what it was before calling RAW_MODE.

	.PSECT	$LOCAL, PIC, WRT, NOEXE, CON, LONG, NOSHR

DEBUGGING = 1
INBUF_SIZE = 128
OUTBUF_SIZE = 256

TTMODE: .BLKQ	1
OUTPTR:	.BLKL	1
INPTR:	.BLKL	1
TTIOSB:	.BLKL	2
TRMMSK:	.BLKL	2
INCNT:	.BLKW	1
TTYCHN:	.BLKW	1
BUFF:	.BLKB	10
INBUF:	.BLKB	INBUF_SIZE
OUTBUF:	.BLKB	OUTBUF_SIZE
.IF NE, DEBUGGING
msg1:	.ASCIZ	/IN: CHAR = %d/<10>
MSG2:	.ASCIZ	/TEST_INPUT: INCNT = %d, CHAR = %d/<10>
MSG3:	.ASCIZ	/TEST_INPUT: SYSBUF = %d, CHAR = %d/<10>
MSG4:	.ASCIZ	/TIMED_IN: CHAR = %d/<10>
MSG5:	.ASCIZ	/OUT: CHAR = %d/<10>
MSG6:	.ASCIZ	/PURGE/<10>
MSG7:	.ASCIZ	/FLUSH/<10>

	.PSECT	DBG_FILE, LONG, WRT, NOEXE, OVR, PIC, GBL, SHR
DBG_FILE: .BLKL	1
.ENDC
	.PSECT	$CODE NOWRT, EXE, SHR, CON, PIC

.ENTRY	IN, ^M<R2>
;     SUBROUTINE IN(CHAR)
;++
;	IN reads a single character from the terminal and returns it in CHAR.
;--
	BSBB	TEST_INPUT
	TSTL	R0
	BLSS	1$
	MOVL	R0, @4(AP)
	RET
1$:	CALLS	#0, FLUSH
	$QIOW_S	CHAN=TTYCHN, -
		FUNC=#IO$_READVBLK!IO$M_NOECHO!IO$M_NOFILTR, -
		P1=@4(AP), -
		P2=#1, -
		P4=#TRMMSK
.IF NE,DEBUGGING
	TSTL	DBG_FILE
	BEQL	80$
	MOVZBL	@4(AP), -(SP)
	PUSHAB	MSG1
	PUSHL	DBG_FILE
	CALLS	#3, FPRINTF
80$:
.ENDC
	RET

;	The local subroutine, TEST_INPUT, checks to see if any input has
;	already arrived, either in the local input buffer, or in the system
;	typeahead buffer.  It returns the resulting character in R0.  If there
;	is no character ready, it returns -1.

TEST_INPUT:
	TSTW	INCNT		; Anything already read in?
	BEQL	1$		; If not, go check system typeahead buffer
.IF NE,DEBUGGING
	TSTL	DBG_FILE
	BEQL	80$
	MOVZBL	@INPTR, -(SP)
	CVTWL	INCNT, -(SP)
	PUSHAB	MSG2
	PUSHL	DBG_FILE
	CALLS	#4, FPRINTF
80$:
.ENDC
	MOVL	INPTR, R0	; Get address of this character
	INCL	INPTR		; Point to next
	DECW	INCNT		; Update count of remaining chars
	MOVZBL	(R0)+, R0	; Return this character
	RSB
1$:	TSTW	TTYCHN		; Have we initialized?
	BNEQ	2$		; If so, skip GETTRM call
	BSBW	GETTRM		; Get the tty channel, etc.
2$:				; Snarf up anything in the system input buffer
	$QIOW_S	CHAN=TTYCHN, -
		IOSB=TTIOSB, -
		FUNC=#IO$_READVBLK!IO$M_NOECHO!IO$M_TIMED, -
		P1=INBUF, -
		P2=#INBUF_SIZE, -
		P3=#0, -
		P4=#TRMMSK
	TSTW	TTIOSB+2	; Were there any?
	BEQL	3$		; If not, return with flags set
	MOVAL	INBUF+1, INPTR	; Yes, reset pointer to next character
	SUBW3	#1, TTIOSB+2, INCNT ; Save the count of characters remaining
.IF NE,DEBUGGING
	TSTL	DBG_FILE
	BEQL	81$
	MOVZBL	INBUF, -(SP)
	CVTWL	TTIOSB+2, -(SP)
	PUSHAB	MSG3
	PUSHL	DBG_FILE
	CALLS	#4, FPRINTF
81$:
.ENDC
	MOVZBL	INBUF, R0	; Return the first character
	RSB
3$:	MCOML	#0, R0
	RSB

.ENTRY	TIMED_IN, ^M<R2>
;     SUBROUTINE TIMED_IN(CHAR, TIME)
;++
;	TIMED_IN reads a single character from the grpahics terminal.
;	If no such character is available within TIME seconds, -1 is
;	returned.
;--
	BSBW	TEST_INPUT
	TSTL	R0
	BLSS	2$
	MOVL	R0, @4(AP)
	RET
2$:	TSTL	@8(AP)
	BEQL	3$
	CALLS	#0, FLUSH
	CLRL	@4(AP)
	$QIOW_S	CHAN=TTYCHN, -
		IOSB=TTIOSB, -
		FUNC=#IO$_READVBLK!IO$M_NOECHO!IO$M_TIMED, -
		P1=@4(AP), -
		P2=#1, -
		P3=@8(AP), -
		P4=#TRMMSK
	CMPW	TTIOSB, #SS$_TIMEOUT
	BNEQ	1$
3$:	MCOML	#0, @4(AP)
1$:
.IF NE,DEBUGGING
	TSTL	DBG_FILE
	BEQL	80$
	PUSHL	@4(AP)
	PUSHAB	MSG4
	PUSHL	DBG_FILE
	CALLS	#3, FPRINTF
80$:
.ENDC
	RET

.ENTRY	PURGE, ^M<R2>
;     SUBROUTINE PURGE
;++
;	PURGE clears the input buffer of the graphics terminal.
;--
	TSTW	TTYCHN
	BNEQ	1$
	BSBW	GETTRM
1$:	CLRW	INCNT
	$QIOW_S	CHAN=TTYCHN, -
		FUNC=#IO$_READVBLK!IO$M_NOECHO!IO$M_TIMED!IO$M_PURGE, -
		P1=BUFF, -
		P2=#1, -
		P3=#0
.IF NE,DEBUGGING
	TSTL	DBG_FILE
	BEQL	80$
	PUSHAB	MSG6
	PUSHL	DBG_FILE
	CALLS	#2, FPRINTF
80$:
.ENDC
	RET

.ENTRY	OUT,0
;     SUBROUTINE OUT(CHAR)
;++
;	OUT writes a single character, CHAR, to the terminal.
;	Actually, the characters are buffered locally, and then sent
;	out by a call to FLUSH, IN or GETLN.  OUT is used for character
;	output in graphics mode.
;--
	MOVB	@4(AP), @OUTPTR
	INCL	OUTPTR
	MOVAL	OUTBUF+OUTBUF_SIZE, R0
	CMPL	OUTPTR, R0
	BNEQ	1$
	CALLS	#0, FLUSH
1$:
.IF NE,DEBUGGING
	TSTL	DBG_FILE
	BEQL	80$
	MOVZBL	@4(AP), -(SP)
	PUSHAB	MSG5
	PUSHL	DBG_FILE
	CALLS	#3, FPRINTF
80$:
.ENDC
	RET

.ENTRY	FLUSH,^M<R2>
;     SUBROUTINE FLUSH
;++
;	FLUSH writes out the characters accumulated with OUT to the terminal.
;--
	MOVAL	OUTBUF, R0
	SUBL3	R0, OUTPTR, R2
	BEQL	1$
	TSTW	TTYCHN
	BNEQ	2$
	BSBW	GETTRM
2$:	$QIOW_S	CHAN=TTYCHN, -
		FUNC=#IO$_WRITEVBLK!IO$M_NOFORMAT, -
		P1=OUTBUF, -
		P2=R2
	MOVAL	OUTBUF, OUTPTR
1$:
.IF NE,DEBUGGING
	TSTL	DBG_FILE
	BEQL	80$
	PUSHAB	MSG7
	PUSHL	DBG_FILE
	CALLS	#2, FPRINTF
80$:
.ENDC
	RET

GETTRM:
	PUSHL	#^A/TT/
	PUSHL	SP
	PUSHL	#2
	MOVL	SP, R2
	$ASSIGN_S DEVNAM=(R2), CHAN=TTYCHN
	BLBS	R0, 1$
	$EXIT_S	R0
1$:	ADDL	#12, SP
	MOVAL	OUTBUF, OUTPTR
	CALLS	#0, PURGE
	RSB

.ENTRY	RAW_MODE, ^M<R2>
;     SUBROUTINE RAW_MODE
;++
;	Puts the terminal in passall mode.
;--
	TSTW	TTYCHN
	BNEQ	1$
	BSBW	GETTRM
1$:	$QIOW_S	CHAN=TTYCHN, -
		FUNC=#IO$_SENSEMODE, -
		P1=TTMODE
	BISL3	#TT$M_EIGHTBIT!TT$M_PASSALL, TTMODE+4, -(SP)
	MOVL	TTMODE, -(SP)
	MOVL	SP, R2
	$QIOW_S	CHAN=TTYCHN, -
		FUNC=#IO$_SETMODE, -
		P1=(R2)
	RET

.ENTRY	RESET_TTY, 0
;     SUBROUTINE RESET_TTY
;++
;	Resets the terminal to the modes in effect before the call to RAW_MODE.
;--
	$QIOW_S	CHAN=TTYCHN, -
		FUNC=#IO$_SETMODE, -
		P1=TTMODE
	RET

	.END
>> End <<
$ COPY SYS$INPUT: XBIN.C
$ DECK/DOLLAR=">> End <<"
#ifndef lint
static char version[] = "xbin.c Version 2.1 03/26/85";
#endif lint

#include <stdio.h>

#ifdef vax11c
#define FNAMELEN 256
#define search_last strrchr
#else

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>

#ifdef MAXNAMLEN	/* 4.2 BSD */
#define FNAMELEN MAXNAMLEN
#else
#define FNAMELEN DIRSIZ
#endif

#ifdef BSD
#include <sys/time.h>
#include <sys/timeb.h>
#define search_last rindex
#else
#include <time.h>
extern long timezone;
#define search_last strrchr
#endif

#endif

extern char *search_last();

/* Mac time of 00:00:00 GMT, Jan 1, 1970 */
#define TIMEDIFF 0x7c25b080

#define DATABYTES 128

#define BYTEMASK 0xff
#define BYTEBIT 0x100
#define WORDMASK 0xffff
#define WORDBIT 0x10000

#define NAMEBYTES 63
#define H_NLENOFF 1
#define H_NAMEOFF 2

/* 65 <-> 80 is the FInfo structure */
#define H_TYPEOFF 65
#define H_AUTHOFF 69
#define H_FLAGOFF 73

#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 F_BUNDLE 0x2000
#define F_LOCKED 0x8000

struct macheader {
	char m_name[NAMEBYTES+1];
	char m_type[4];
	char m_author[4];
	short m_flags;
	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 pre_beta;	/* options */
int listmode;
int verbose;

int compressed;	/* state variables */
int qformat;
FILE *ifp;

/*
 * xbin -- unpack BinHex format file into suitable
 * format for downloading with macput
 * Dave Johnson, Brown University Computer Science
 *
 * VMS support by Stew Rubenstein, Harvard University Chemical Labs
 *
 * checksum code by Darin Adler, TMQ Software
 *
 * (c) 1984 Brown University
 * may be used but not sold without permission
 *
 * created ddj 12/16/84
 * revised ddj 03/10/85 -- version 4.0 compatibility, other minor mods
 * revised ddj 03/11/85 -- strip LOCKED bit from m_flags
 * revised ahm 03/12/85 -- System V compatibility
 * revised dba 03/16/85 -- 4.0 EOF fixed, 4.0 checksum added
 * revised ddj 03/17/85 -- extend new features to older formats: -l, stdin
 * revised ddj 03/24/85 -- check for filename truncation, allow multiple files
 * revised ddj 03/26/85 -- fixed USG botches, many problems w/multiple files
 * revised sdr 03/29/85 -- vms version under #ifdef vax11c switches
 */
char usage[] = "usage: \"xbin [-v] [-l] [-o] [-n name] [-] filename\"\n";

main(ac, av)
char **av;
{
	char *filename, *macname;

	filename = ""; macname = "";
	ac--; av++;
	while (ac) {
		if (av[0][0] == '-') {
			switch (av[0][1]) {
			case '\0':
				filename = "-";
				break;
			case 'v':
				verbose++;
				break;
			case 'l':
				listmode++;
				break;
			case 'o':
				pre_beta++;
				break;
			case 'n':
				if (ac > 1) {
					ac--; av++;
					macname = av[0];
					filename = "";
					break;
				}
				else
					goto bad_usage;
			default:
				goto bad_usage;
			}
		}
		else
			filename = av[0];
		if (filename[0] != '\0') {
			setup_files(filename, macname);
			if (listmode) {
				print_header();
			}
			else {
				process_forks();
				/* now that we know the size of the forks */
				forge_info();
			}
			if (ifp != stdin)
				fclose(ifp);
			macname = "";
			ifp = NULL;		/* reset state */
			qformat = 0;
			compressed = 0;
		}
		ac--; av++;
	}
	if (*filename == '\0') {
bad_usage:
		fprintf(stderr, usage);
		exit(1);
	}
}

static char *extensions[] = {
	".hqx",
	".hcx",
	".hex",
	"",
	NULL
};

setup_files(filename, macname)
char *filename;		/* input file name -- extension optional */
char *macname;		/* name to use on the mac side of things */
{
	char namebuf[256], *np;
	char **ep;
	int n;
#ifndef vax11c
	struct stat stbuf;
#endif
	long curtime;

	if (filename[0] == '-') {
		ifp = stdin;
		filename = "stdin";
	}
	else {
		/* find input file and open it */
		for (ep = extensions; *ep != NULL; ep++) {
			sprintf(namebuf, "%s%s", filename, *ep);
#ifdef vax11c
			if ((n = open(namebuf, 0)) >= 0) {
				close(n);
				break;
			}
#else
			if (stat(namebuf, &stbuf) == 0)
				break;
#endif
		}
		if (*ep == NULL) {
			perror(namebuf);
			exit(-1);
		}
		ifp = fopen(namebuf, "r");
		if (ifp == NULL) {
			perror(namebuf);
			exit(-1);
		}
	}
	if (ifp == stdin) {
		curtime = time(0);
		mh.m_createtime = curtime;
	}
	else {
#ifdef vax11c
		revdate(namebuf, "", &mh.m_createtime);
#else
		mh.m_createtime = stbuf.st_mtime;
#endif
	}
	mh.m_modifytime = mh.m_createtime;
	if (listmode || verbose) {
		fprintf(stderr, "%s %s%s",
			listmode ? "\nListing" : "Converting",
			namebuf, listmode ? ":\n" : " ");
	}

	qformat = find_header(); /* eat mailer header &cetera, intuit format */

	if (qformat)
		do_q_header(macname);
	else
		do_o_header(macname, filename);

	/* make sure host file name doesn't get truncated beyond recognition */
	n = strlen(mh.m_name);
	if (n > FNAMELEN - 2)
		n = FNAMELEN - 2;
	strncpy(namebuf, mh.m_name, n);
	namebuf[n] = '\0';

	/* get rid of troublesome characters */
	for (np = namebuf; *np; np++)
		if (*np == ' ' || *np == '/')
			*np = '_';

#ifdef vax11c
	sprintf(files.f_data, "%s.dat", namebuf);
	sprintf(files.f_rsrc, "%s.rsr", namebuf);
	sprintf(files.f_info, "%s.inf", namebuf);
	if (verbose)
		fprintf(stderr, "==> %s.{inf,dat,rsr}\n", namebuf);
#else
	sprintf(files.f_data, "%s.data", namebuf);
	sprintf(files.f_rsrc, "%s.rsrc", namebuf);
	sprintf(files.f_info, "%s.info", namebuf);
	if (verbose)
		fprintf(stderr, "==> %s.{info,data,rsrc}\n", namebuf);
#endif
}

/* print out header information in human-readable format */
print_header()
{
	char *ctime();

	printf("macname: %s\n", mh.m_name);
	printf("filetype: %.4s, ", mh.m_type);
	printf("author: %.4s, ", mh.m_author);
	printf("flags: 0x%x\n", mh.m_flags);
	if (qformat) {
		printf("data length: %d, ", mh.m_datalen);
		printf("rsrc length: %d\n", mh.m_rsrclen);
	}
	if (!pre_beta) {
		printf("create time: %s", ctime(&mh.m_createtime));
	}
}

process_forks()
{
	if (qformat) {
		/* read data and resource forks of .hqx file */
		do_q_fork(files.f_data, mh.m_datalen);
		do_q_fork(files.f_rsrc, mh.m_rsrclen);
	}
	else
		do_o_forks();
}

/* write out .info file from information in the mh structure */
forge_info()
{
	static char buf[DATABYTES];
	char *np;
	FILE *fp;
	int n, tdiff;
#ifndef vax11c
	struct tm *tp;
#ifdef BSD
	struct timeb tbuf;
#else
	long bs;
#endif
#endif

	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);
	put2(buf + H_FLAGOFF, mh.m_flags & ~F_LOCKED);
	if (pre_beta) {
		put4(buf + H_OLD_DLENOFF, mh.m_datalen);
		put4(buf + H_OLD_RLENOFF, mh.m_rsrclen);
	}
	else {
		put4(buf + H_DLENOFF, mh.m_datalen);
		put4(buf + H_RLENOFF, mh.m_rsrclen);

		/* convert unix file time to mac time format */
#ifdef vax11c
		tdiff = TIMEDIFF;
#else
#ifdef BSD
		ftime(&tbuf);
		tp = localtime(&tbuf.time);
		tdiff = TIMEDIFF - tbuf.timezone * 60;
		if (tp->tm_isdst)
			tdiff += 60 * 60;
#else
		/* I hope this is right! -andy */
		time(&bs);
		tp = localtime(&bs);
		tdiff = TIMEDIFF - timezone;
		if (tp->tm_isdst)
			tdiff += 60 * 60;
#endif
#endif
		put4(buf + H_CTIMOFF, mh.m_createtime + tdiff);
		put4(buf + H_MTIMOFF, mh.m_modifytime + tdiff);
	}
	fp = fopen(files.f_info, "w");
	if (fp == NULL) {
		perror("info file");
		exit(-1);
	}
	fwrite(buf, 1, DATABYTES, fp);
	fclose(fp);
}

/* eat characters until header detected, return which format */
find_header()
{
	int c, n, at_bol;
	char ibuf[BUFSIZ];

	/* look for "(This file ...)" line */
	while (fgets(ibuf, BUFSIZ, ifp) != NULL) {
		if (strncmp(ibuf, "(This file", 10) == 0)
			break;
	}
	at_bol = 1;
	while ((c = getc(ifp)) != EOF) {
		switch (c) {
		case '\n':
		case '\r':
			at_bol = 1;
			break;
		case ':':
			if (at_bol)	/* q format */
				return 1;
			break;
		case '#':
			if (at_bol) {	/* old format */
				ungetc(c, ifp);
				return 0;
			}
			break;
		default:
			at_bol = 0;
			break;
		}
	}

	fprintf(stderr, "unexpected EOF\n");
	exit(2);
}

static unsigned int crc;

short get2q();
long get4q();

/* read header of .hqx file */
do_q_header(macname)
char *macname;
{
	char namebuf[256];		/* big enough for both att & bsd */
	int n;
	int calc_crc, file_crc;

	crc = 0;			/* compute a crc for the header */
	q_init();			/* reset static variables */

	n = getq();			/* namelength */
	n++;				/* must read trailing null also */
	getqbuf(namebuf, n);		/* read name */
	if (macname[0] == '\0')
		macname = namebuf;

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

	getqbuf(mh.m_type, 4);
	getqbuf(mh.m_author, 4);
	mh.m_flags = get2q();
	mh.m_datalen = get4q();
	mh.m_rsrclen = get4q();

	comp_q_crc(0);
	comp_q_crc(0);
	calc_crc = crc;
	file_crc = get2q();
	verify_crc(calc_crc, file_crc);
}

do_q_fork(fname, len)
char *fname;
register int len;
{
	FILE *outf;
	register int c, i;
	int calc_crc, file_crc;

	outf = fopen(fname, "w");
	if (outf == NULL) {
		perror(fname);
		exit(-1);
	}

	crc = 0;	/* compute a crc for a fork */

	if (len)
		for (i = 0; i < len; i++) {
			if ((c = getq()) == EOF) {
				fprintf(stderr, "unexpected EOF\n");
				exit(2);
			}
			putc(c, outf);
		}

	comp_q_crc(0);
	comp_q_crc(0);
	calc_crc = crc;
	file_crc = get2q();
	verify_crc(calc_crc, file_crc);
	fclose(outf);
}

/* verify_crc(); -- check if crc's check out */
verify_crc(calc_crc, file_crc)
unsigned int calc_crc, file_crc;
{
	calc_crc &= WORDMASK;
	file_crc &= WORDMASK;

	if (calc_crc != file_crc) {
		fprintf(stderr, "CRC error\n---------\n");
		fprintf(stderr, "CRC in file:\t0x%x\n", file_crc);
		fprintf(stderr, "calculated CRC:\t0x%x\n", calc_crc);
		exit(3);
	}
}

static int eof;
static char obuf[3];
static char *op, *oend;

/* initialize static variables for q format input */
q_init()
{
	eof = 0;
	op = obuf;
	oend = obuf + sizeof obuf;
}

/* get2q(); q format -- read 2 bytes from input, return short */
short
get2q()
{
	register int c;
	short value = 0;

	c = getq();
	value = (c & BYTEMASK) << 8;
	c = getq();
	value |= (c & BYTEMASK);

	return value;
}

/* get4q(); q format -- read 4 bytes from input, return long */
long
get4q()
{
	register int c, i;
	long value = 0;

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

/* getqbuf(); q format -- read n characters from input into buf */
/*		All or nothing -- no partial buffer allowed */
getqbuf(buf, n)
register char *buf;
register int n;
{
	register int c, i;

	for (i = 0; i < n; i++) {
		if ((c = getq()) == EOF)
			return EOF;
		*buf++ = c;
	}
	return 0;
}

#define RUNCHAR 0x90

/* q format -- return one byte per call, keeping track of run codes */
getq()
{
	register int c;

	if ((c = getq_nocrc()) == EOF)
		return EOF;
	comp_q_crc(c);
	return c;
}

getq_nocrc()
{
	static int rep, lastc;
	int c;

	if (rep) {
		rep--;
		return lastc;
	}
	if ((c = getq_raw()) == EOF) {
		return EOF;
		}
	if (c == RUNCHAR) {
		if ((rep = getq_raw()) == EOF)
			return EOF;
		if (rep == 0) {
			return RUNCHAR;
		}
		else {
			/* already returned one, about to return another */
			rep -= 2;
			return lastc;
		}
	}
	else {
		lastc = c;
		return c;
	}
}

/* q format -- return next 8 bits from file without interpreting run codes */
getq_raw()
{
	char ibuf[4];
	register char *ip = ibuf, *iend = ibuf + sizeof ibuf;
	int c;

	if (op == obuf) {
		for (ip = ibuf; ip < iend; ip++) {
			if ((c = get6bits()) == EOF)
				if (ip <= &ibuf[1])
					return EOF;
				else if (ip == &ibuf[2])
					eof = 1;
				else
					eof = 2;
			*ip = c;
		}
		obuf[0] = (ibuf[0] << 2 | ibuf[1] >> 4);
		obuf[1] = (ibuf[1] << 4 | ibuf[2] >> 2);
		obuf[2] = (ibuf[2] << 6 | ibuf[3]);
	}
	if ((eof) & (op >= &obuf[eof]))
		return EOF;
	c = *op++;
	if (op >= oend)
		op = obuf;
	return (c & BYTEMASK);
}

char tr[] = "!\"#$%&'()*+,-012345689@ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr";

/* q format -- decode one byte into 6 bit binary */
get6bits()
{
	register int c;
	register char *where;

	while (1) {
		c = getc(ifp);
		switch (c) {
		case '\n':
		case '\r':
			continue;
		case ':':
		case EOF:
			return EOF;
		default:
			where = tr;
			while (*where != '\0' && *where != c)
				where++;
			if (*where == c)
				return (where - tr);
			else {
				fprintf(stderr, "bad char\n");
				return EOF;
			}
		}
	}
}

#define CRCCONSTANT 0x1021

comp_q_crc(c)
register unsigned int c;
{
	register int i;
	register unsigned int temp = crc;

	for (i=0; i<8; i++) {
		c <<= 1;
		if ((temp <<= 1) & WORDBIT)
			temp = (temp & WORDMASK) ^ CRCCONSTANT;
		temp ^= (c >> 8);
		c &= BYTEMASK;
	}
	crc = temp;
}

/* old format -- process .hex and .hcx files */
do_o_header(macname, filename)
char *macname, *filename;
{
	char namebuf[256];		/* big enough for both att & bsd */
	char ibuf[BUFSIZ];
	int n;

	/* set up name for output files */
	if (macname[0] == '\0') {
		strcpy(namebuf, filename);

		/* strip directories */
		macname = search_last(namebuf, '/');
		if (macname == NULL)
			macname = namebuf;
		else
			macname++;

		/* strip extension */
		n = strlen(macname);
		if (n > 4) {
		    n -= 4;
		    if (macname[n] == '.' && macname[n+1] == 'h'
					    && macname[n+3] == 'x')
			    macname[n] = '\0';
		}
	}
	n = strlen(macname);
	if (n > NAMEBYTES)
		n = NAMEBYTES;
	strncpy(mh.m_name, macname, n);
	mh.m_name[n] = '\0';

	/* read "#TYPEAUTH$flag"  line */
	if (fgets(ibuf, BUFSIZ, ifp) == NULL) {
		fprintf(stderr, "unexpected EOF\n");
		exit(2);
	}
	n = strlen(ibuf);
	if (n >= 7 && ibuf[0] == '#' && ibuf[n-6] == '$') {
		if (n >= 11)
			strncpy(mh.m_type, &ibuf[1], 4);
		if (n >= 15)
			strncpy(mh.m_author, &ibuf[5], 4);
		sscanf(&ibuf[n-5], "%4hx", &mh.m_flags);
	}
}

do_o_forks()
{
	char ibuf[BUFSIZ];
	int forks = 0, found_crc = 0;
	int calc_crc, file_crc;
	int n;

	crc = 0;	/* calculate a crc for both forks */

	/* create empty files ahead of time */
	close(creat(files.f_data, 0666));
	close(creat(files.f_rsrc, 0666));

	while (!found_crc && fgets(ibuf, BUFSIZ, ifp) != NULL) {
		if (forks == 0 && strncmp(ibuf, "***COMPRESSED", 13) == 0) {
			compressed++;
			continue;
		}
		if (strncmp(ibuf, "***DATA", 7) == 0) {
			mh.m_datalen = make_file(files.f_data, compressed);
			forks++;
			continue;
		}
		if (strncmp(ibuf, "***RESOURCE", 11) == 0) {
			mh.m_rsrclen = make_file(files.f_rsrc, compressed);
			forks++;
			continue;
		}
		if (compressed && strncmp(ibuf, "***CRC:", 7) == 0) {
			found_crc++;
			calc_crc = crc;
			sscanf(&ibuf[7], "%x", &file_crc);
			break;
		}
		if (!compressed && strncmp(ibuf, "***CHECKSUM:", 12) == 0) {
			found_crc++;
			calc_crc = crc & BYTEMASK;
			sscanf(&ibuf[12], "%x", &file_crc);
			file_crc &= BYTEMASK;
			break;
		}
	}

	if (found_crc)
		verify_crc(calc_crc, file_crc);
	else {
		fprintf(stderr, "missing CRC\n");
		exit(3);
	}
}

make_file(fname, compressed)
char *fname;
int compressed;
{
	register int n;
	char ibuf[BUFSIZ];
	FILE *outf;
	int nbytes = 0;

	outf = fopen(fname, "w");
	if (outf == NULL) {
		perror(fname);
		exit(-1);
	}

	while (fgets(ibuf, BUFSIZ, ifp) != NULL) {
		if (strncmp(ibuf, "***END", 6) == 0)
			break;
		if (compressed)
			nbytes += comp_to_bin(ibuf, outf);
		else
			nbytes += hex_to_bin(ibuf, outf);
	}

	fclose(outf);
	return nbytes;
}

comp_c_crc(c)
unsigned char c;
{
	crc = (crc + c) & WORDMASK;
	crc = ((crc << 3) & WORDMASK) | (crc >> 13);
}

comp_e_crc(c)
unsigned char c;
{
	crc += c;
}

#define SIXB(c) (((c)-0x20) & 0x3f)

comp_to_bin(ibuf, outf)
char ibuf[];
FILE *outf;
{
	char obuf[BUFSIZ];
	register char *ip = ibuf;
	register char *op = obuf;
	register int n, outcount;
	int numread, incount;

	numread = strlen(ibuf);
	ip[numread-1] = ' ';		/* zap out the newline */
	outcount = (SIXB(ip[0]) << 2) | (SIXB(ip[1]) >> 4);
	incount = ((outcount / 3) + 1) * 4;
	for (n = numread; n < incount; n++)	/* restore lost spaces */
		ibuf[n] = ' ';

	n = 0;
	while (n <= outcount) {
		*op++ = SIXB(ip[0]) << 2 | SIXB(ip[1]) >> 4;
		*op++ = SIXB(ip[1]) << 4 | SIXB(ip[2]) >> 2;
		*op++ = SIXB(ip[2]) << 6 | SIXB(ip[3]);
		ip += 4;
		n += 3;
	}

	for (n=1; n <= outcount; n++)
		comp_c_crc(obuf[n]);

	fwrite(obuf+1, 1, outcount, outf);
	return outcount;
}

hex_to_bin(ibuf, outf)
char ibuf[];
FILE *outf;
{
	register char *ip = ibuf;
	register int n, outcount;
	int c;

	n = strlen(ibuf) - 1;
	outcount = n / 2;
	for (n = 0; n < outcount; n++) {
		c = hexit(*ip++);
		comp_e_crc(c = (c << 4) | hexit(*ip++));
		fputc(c, outf);
	}
	return outcount;
}

hexit(c)
int c;
{
	if ('0' <= c && c <= '9')
		return c - '0';
	if ('A' <= c && c <= 'F')
		return c - 'A' + 10;

	fprintf(stderr, "illegal hex digit: %c", c);
	exit(4);
}

put2(bp, value)
char *bp;
short value;
{
	*bp++ = (value >> 8) & BYTEMASK;
	*bp++ = value & BYTEMASK;
}

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;
	}
}
>> End <<
$ COPY SYS$INPUT: XBIN.HLP
$ DECK/DOLLAR=">> End <<"
NAME
     xbin - convert mailable format BinHex file into binary
     before downloading to MacTerminal

SYNOPSIS
     xbin [ -o ] [ -v ] [ -l ] [[ -n name ] file] ...

DESCRIPTION
     Xbin converts a file created by BinHex (usually named with
     one of the extensions ".hex", ".hcx", or ".hqx") into three
     host-system files suitable for downloading to a Macintosh
     via macput.  This program is designed for use with the 1.1
     Release version of MacTerminal, but includes a compatibility
     option for the old -0.15X Almost-Alpha version.

     The -l (list) option reads the header information and prints
     out all the useful information there, without creating any
     converted output files.

     The -v (verbose) option prints a line for each file to be
     converted, indicating the input and output file names.

     The -n name option allows the user to specify the name to
     use when creating the host files and the eventual name to
     use on the mac.  This option must precede the input file
     name it is to affect.

     If this option is not used, the names will be derived from
     either the input file name (.hex or .hcx files), or the name
     encoded in the header information (.hqx files).  Spaces and
     slashes will be converted to underscores, and the .h?x
     extension will be deleted, if one is included in the input
     file name.

     A file name of "-" indicates that the input should be taken
     from SYS$INPUT.  If no mac file name is specified, the default
     name (for .hex or .hcx files) is "stdin".

     Mail or news headers and signatures need not be manually
     stripped -- xbin will ignore pretty much anything it doesn't
     need.

     xbin creates three host-system files from each input file:
     name.inf, name.dat, and name.rsr.

     The -o flag specifies "old" (version -0.15X) MacTerminal
     compatibility mode.

BUGS
     The "LOCKED" bit in the flags cannot be set by xbin.  This
     is due to a bug in MacTerminal, which sets the flags when
     the file is created, rather than after it has been
     transfered, resulting in it not being able to write the
     file.

     Input files must contain a line starting with "(This file"
     to detect the beginning of the BinHex information.

SEE ALSO
     macput, macget

AUTHOR
     Dave Johnson, Brown 12/16/84;
     CRC handling code by Darin Adler, TMQ Software 3/16/85
     VMS support by Stew Rubenstein, Harvard University 3/29/85
>> End <<
$ COPY SYS$INPUT: XBIN.TXT
$ DECK/DOLLAR=">> End <<"
This version of "xbin" can handle all three BinHex formats
(so far).  Thanks to Darin Adler at TMQ Software for providing
the code to compute and check the CRC values for all three formats.

Other new features include "list" and "verbose" modes, the
ability to convert several binhex files at one time, the ability
to read standard input, somewhat better error handling, and a
manual page.

Any extraneous mail or news headers are ignored, but xbin relies
on finding a line which starts with "(This file" to know when
the header ends and the good stuff begins.  You can add one
of these by hand if it's been lost.

To compile it on USG systems, type:
	cc -o xbin xbin.c

or on Berkeley systems:
	cc -o xbin xbin.c -DBSD

As usual, please report any problems, suggestions, or
improvements to me.

	Dave Johnson
	Brown University Computer Science
	ddj%brown@csnet-relay.ARPA
	{ihnp4,decvax,allegra,ulysses,linus}!brunix!ddj
>> End <<