[comp.sys.amiga.hardware] A3070 Tape Drive programming

crash@ckctpa.UUCP (Frank J. Edwards) (06/20/91)

Where should this have been posted to?  alt.sources?  c.s.a.programmer?
I'm listening ...

In article <1991Jun19.100002.26682@NCoast.ORG> lesle@NCoast.ORG (David A. Lesle) writes:
>Is there any way to program the A3070 tape drive to read tapes
>of lower density than 150M?  I have several tape cartridges with
>tar files from a Sun system circa 1989 that I would like to read.
>
>I remember that the Sun had several different devices depending
>on which density you wanted to read, but I can't find anything
>like that in the UNIX documentation.  I'd like to do this under
>either AmigaDOS or UNIX so any help would be appreciated.

Well, here are a couple of general purpose programs to manipulate the
tape drive directly using SCSIDirect, ie. talking to the scsi.device .

Don't complain about the coding style or I'll never again write another
piece of code!  ;-)  Seriously, though, I've since added more options than
what is given here, but they're still useful.  As soon as I pull out
a more recent backup I'll post the newer ones if anyone's interested.

>------------------------------------------------------------------------------
>David Lesle                uucp: ncoast!lesle
>CLEVELAND AREA-AMIGA USERS' GROUP (CA-AUG)     (216) 642-3344 (BBS)
>------------------------------------------------------------------------------
[Hey! say hello to Brandon@NCoast, huh?  He won't remember me but I do him!]


Here's how I tend to use these things:

1.System2.0:> tctl rewind		; rewind the tape
1.System2.0:> tctl reqblk		; request current block address
1
1.System2.0:> tctl fsf 1		; forward space to next (1) filemark
1.System2.0:> tctl fsb -200		; move backward 200 blocks
1.System2.0:> tctl mselect 1 60		; buffered,    60MB mode
1.System2.0:> tctl mselect 0 120	; unbuffered, 120MB mode
1.System2.0:> tctl mselect 1 150	; buffered,   150MB mode
					; normally the drive auto-syncs on read
1.System2.0:> tctl msense		; what is the current mode?
1.System2.0:> tctl seek 94300		; seek to...
1.System2.0:> tctl reqblk
94300
1.System2.0:> 

There are other options as well; play around a little...  This next
one is handy when you've accidentally blown away your BTN tape-handler
(nice program, huh?) and you need something to read your tapes.  Now that
I've started using BRU for everything, this isn't as necessary...

1.System2.0:> run scsird -o pipe:tar	; send tape data to PIPE:
[CLI 4]
1.System2.0:> tar -xvf pipe:tar		; extract files

I have similar programs for Amix, but everytime you re-open the tape
device (/dev/rmt/4*) the drive gets re-initialized (unfortunate, but
necessary) so you can make these changes or position the tape, but when
you again access the tape the only thing that remains is the tape position.

------ cut here ------ cut here ------ cut here ------ cut here ------
#!/bin/sh
# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# This archive contains:
#	Makefile	scsird.c	tctl.c
#

echo x - Makefile
sed -e 's/^X//' >Makefile <<'@EOF'
X#  Copyright (c) 1991 by Frank J. Edwards
X#  All Rights Reserved.
X#
XCC = cc
XCFLAGS = -bs -wl -DFJE
XLD = ln
XLDFLAGS = -g -w
XLIBS = -lc
X
Xtctl: tctl.o getopt.o
X	ln -o $@ tctl.o getopt.o $(LDFLAGS) $(LIBS)
X
Xscsird: scsird.o getopt.o
X	ln -o $@ scsird.o getopt.o $(LDFLAGS) $(LIBS)
@EOF

chmod 666 Makefile

echo x - scsird.c
sed -e 's/^X//' >scsird.c <<'@EOF'
X/* Copyright (c) 1991 by Frank J. Edwards
X * All Rights Reserved.
X */
X#include <stdio.h>
X#include <fcntl.h>
X#include <string.h>
X#include <devices/scsidisk.h>
X#include <proto/exec.h>
X
Xstruct reqsense {
X	UBYTE	bits[3];
X	UBYTE	rlen[4];
X	UBYTE	addlen;
X	UBYTE	srcptr, destptr;
X	UBYTE	recerrs[4];
X} rs;
X#define REQ_SIZE	(UBYTE) sizeof(struct reqsense)
X#define CLASS		0x70
X#define ERRCODE		0x0F
X#define SEGMENT		0xFF
X#define FILEMARK	0x80
X#define EOM			0x40
X#define SENSEKEY	0x0F
X#define LONG(x)		((x[0]<<24) | (x[1]<<16) | (x[2]<<8) | (x[3]))
X
Xtypedef UBYTE Block[512];
Xchar *sensekey(int sense);
Xint DoSCSI(struct IOStdReq *ior);
X
Xextern int optind;
Xextern char *optarg;
Xchar *prog;
X
XUBYTE readcmd[]   = {  8, 1, 0, 0, 0, 0 };
XUBYTE rewindcmd[] = {  1, 1, 0, 0, 0, 0 };
Xstruct SCSICmd sc;
Xstruct reqsense sensedata;
Xstruct MsgPort *port;
Xstruct IOStdReq *ior;
X
Xvoid cleanup(void)
X{
X	if (ior->io_Device)
X		CloseDevice((struct IORequest *) ior), ior->io_Device = 0;
X	if (ior)
X		DeleteStdIO(ior), ior = 0;
X	if (port)
X		DeletePort(port), port = 0;
X}
X
Xvoid usage(int ier, char *fmt, ...)
X{
X	va_list args;
X
X	if (fmt) {
X		va_start(args, fmt);
X		vfprintf(stderr, fmt, args);
X		va_end(args);
X	}
X	fprintf(stderr, "Usage:  %s [-b #] [-r] [-t] -o outfile\n", prog);
X	exit( ier );
X}
X
Xmain(int argc, char **argv)
X{
X	char *fname;
X	int err, out, ch, rewind, reten;
X	Block *buffer;
X	ULONG nblks = 100, bufsiz;
X
X	if ((prog = strrchr(*argv,'/')) || (prog = strrchr(*argv,':')))
X		prog++;
X	else
X		prog = *argv;
X	fname = NULL;
X	rewind = FALSE;
X	reten = FALSE;
X
X	while ((ch = getopt(argc, argv, "b:rto:")) != EOF) {
X		switch (ch) {
X		case 'b' :
X			nblks = atol(optarg);
X			break;
X		case 'r' :
X			rewind = TRUE;
X			break;
X		case 't' :
X			fprintf(stderr, "Retensioning is unimplemented.\n");
X			/* reten = TRUE; */
X			break;
X		case 'o' :
X			if (fname)
X				usage(1, "Specified output file twice!\n");
X			fname = optarg;
X			break;
X		default  :
X			usage(1, "Illegal option -%c\n", ch);
X		}
X	}
X	buffer = (Block *) malloc(bufsiz = sizeof(Block) * nblks);
X	if (!buffer) {
X		fputs("Couldn't allocate buffer!\n", stderr);
X		exit( 10 );
X	}
X	init_io( &port, &ior );
X	ior->io_Command = HD_SCSICMD;
X	ior->io_Data    = (APTR) &sc;
X	ior->io_Length  = sizeof(sc);
X
X	sc.scsi_SenseData   = (UBYTE *) &sensedata;
X	sc.scsi_SenseLength = (UWORD)   sizeof(sensedata);
X	sc.scsi_Data        = (UWORD *) buffer;
X	sc.scsi_Length      = (ULONG)   bufsiz;
X
X	if (rewind) {
X		sc.scsi_Command     = (UBYTE *) rewindcmd;
X		sc.scsi_CmdLength   = (UWORD)   sizeof(rewindcmd);
X		sc.scsi_Flags       = (UBYTE)   SCSIF_READ | SCSIF_AUTOSENSE;
X		if (err = DoSCSI(ior))
X			exit( 20 );
X	}
X#if 0
X	if (reten) {
X		sc.scsi_Command     = (UBYTE *) retencmd;
X		sc.scsi_CmdLength   = (UWORD)   sizeof(retencmd);
X		sc.scsi_Flags       = (UBYTE)   SCSIF_READ | SCSIF_AUTOSENSE;
X		if (err = DoSCSI(ior))
X			exit( 20 );
X	}
X#endif
X	sc.scsi_Command     = (UBYTE *) readcmd;
X	sc.scsi_CmdLength   = (UWORD)   sizeof(readcmd);
X	sc.scsi_Flags       = (UBYTE)   SCSIF_READ | SCSIF_AUTOSENSE;
X
X	readcmd[2] = (bufsiz >> 25) & 0xFF;		/* ((siz/512) >> 16) */
X	readcmd[3] = (bufsiz >> 17) & 0xFF;		/* ((siz/512) >>  8) */
X	readcmd[4] = (bufsiz >>  9) & 0xFF;
X
X	if (!fname)
X		usage(10, "Must specify an output filename!\n");
X	out = open(fname, O_WRONLY | O_CREAT | O_TRUNC);
X	ior->io_Error = 0;
X	while (!ior->io_Error) {
X		if (err = DoSCSI(ior)) {
X			fprintf(stderr, "Error %d during DoSCSI()!\n", err);
X			break;
X		} else if (sc.scsi_Actual) {
X			if (sc.scsi_Actual != bufsiz)
X				fprintf(stderr, "Short block (%ld bytes)\n", sc.scsi_Actual);
X			if (write(out, buffer, sc.scsi_Actual) != sc.scsi_Actual)
X				perror(fname);
X		} else
X			fprintf(stderr, "No data transferred.\n");
X	}
X	close(out);
X	if (rewind) {
X		sc.scsi_Command     = (UBYTE *) rewindcmd;
X		sc.scsi_CmdLength   = (UWORD)   sizeof(rewindcmd);
X		sc.scsi_Flags       = (UBYTE)   SCSIF_READ | SCSIF_AUTOSENSE;
X		(void) DoSCSI(ior);
X	}
X	return( err );
X}
X
Xinit_io(struct MsgPort **Port, struct IOStdReq **Ior)
X{
X	int err;
X
X	atexit(cleanup);
X	if (! (*Port = CreatePort(0L, 0L)) ) {
X		fputs("Couldn't CreatePort()!\n", stderr);
X		exit( 10 );
X	}
X	if (! (*Ior = CreateStdIO(*Port)) ) {
X		fputs("Couldn't CreateStdIO()!\n", stderr);
X		exit( 10 );
X	}
X	err = 2;
X	if (err = OpenDevice("scsi.device", err, (struct IORequest *) *Ior, 0)) {
X		fprintf(stderr, "Error %d from OpenDevice()!\n", err);
X		exit( 10 );
X	}
X	return( 0 );
X}
X
Xchar *sensekey(int sense)
X{
X	static char *SenseKey[] = {
X		"FM/EOM or no status available",
X		"Recovered from error; command successful",
X		"Not Ready",
X		"Medium Error",
X		"Hardware Error",
X		"Illegal Request (illegal parameter in CDB)",
X		"Unit Attention (cartridge changed or Viper reset)",
X		"Data Protect (cartridge is write-portected)",
X		"Blank Check (no-data condition found on tape)",
X		"Vendor Unique Code -- not used on Viper",
X		"-- undocumented error --",
X		"Aborted Command (may retry)",
X		"-- undocumented error --",
X		"Volume Overflow (internal buffer may contain data)",
X	};
X#define KEY_SIZE	sizeof(SenseKey)
X	return( SenseKey[sense] );
X}
X
Xint DoSCSI(struct IOStdReq *ior)
X{
X	int err;
X
X	while (err = DoIO( (struct IORequest *)ior )) {
X		struct SCSICmd *scsicmd = (struct SCSICmd *) ior->io_Data;
X		struct reqsense *rs;
X
X		if (err!=HFERR_BadStatus || !(scsicmd->scsi_Flags&SCSIF_AUTOSENSE)) {
X			fprintf(stderr, "Got error %d from DoIO()!\n", err);
X			return( err );
X		}
X		rs = (struct reqsense *) scsicmd->scsi_SenseData;
X		if (err = rs->bits[2]) {
X			if (err & FILEMARK) {
X				fputs("Filemark.\n", stderr);
X				return( err );
X			} else if (err & EOM) {
X				fputs("End-of-Media.\n", stderr);
X				return( err );
X			} else {
X				err &= SENSEKEY;
X				fprintf(stderr, "sense key = %d (%s)\n", err, sensekey(err));
X				if (err == 1)			/* Recovered Error */
X					break;
X				else if (err != 6)		/* Unit Attention */
X					return( err );
X			}
X		}
X	}
X	return( 0 );
X}
@EOF

chmod 666 scsird.c

echo x - tctl.c
sed -e 's/^X//' >tctl.c <<'@EOF'
X/* Copyright (c) 1991 by Frank J. Edwards
X * All Rights Reserved.
X */
X#include <stdio.h>
X#include <fcntl.h>
X#include <string.h>
X#include <stdarg.h>
X#include <exec/types.h>
X#include <exec/io.h>
X#include <devices/scsidisk.h>
X#include <clib/exec_protos.h>
X
Xextern struct MsgPort *CreatePort(char *, long);
Xextern struct IOStdReq *CreateStdIO(struct MsgPort *);
X
Xstruct reqsense {
X	UBYTE	bits[3];
X	UBYTE	rlen[4];
X	UBYTE	addlen;
X	UBYTE	srcptr, destptr;
X	UBYTE	recerrs[4];
X} rs;
X#define REQ_SIZE	(UBYTE) sizeof(struct reqsense)
X#define CLASS		0x70
X#define ERRCODE		0x0F
X#define SEGMENT		0xFF
X#define FILEMARK	0x80
X#define EOM			0x40
X#define SENSEKEY	0x0F
X#define LONG(x)		((x[0]<<24) | (x[1]<<16) | (x[2]<<8) | (x[3]))
X
Xchar *sensekey(int sense);
Xint DoSCSI(struct IOStdReq *ior);
X
Xextern int optind;
Xextern char *optarg;
Xchar *prog;
X
XUBYTE trewind[]  = {  1, 0, 0, 0, 0, 0 };
XUBYTE treqblk[]  = {  2, 0, 0, 0, 0, 0 };	/* blockaddr returned in [2..4] */
XUBYTE tseekblk[] = { 12, 0, 0, 0, 0, 0 };	/* blockaddr into [2..4] */
XUBYTE twritefm[] = { 16, 0, 0, 0, 0, 0 };	/* count into [2..4] */
XUBYTE tspace[]   = { 17, 0, 0, 0, 0, 0 };	/* (tspace[1] & 03) == SPACE_TYPE */
XUBYTE tverify[]  = { 19, 1, 0, 0, 0, 0 };	/* length into [2..4] */
XUBYTE terase[]   = { 25, 1, 0, 0, 0, 0 };
X
Xstruct SCSICmd sc;
Xstruct reqsense sensedata;
Xstruct MsgPort *port;
Xstruct IOStdReq *ior;
X
Xvoid cleanup(void)
X{
X	if (ior->io_Device)
X		CloseDevice((struct IORequest *) ior), ior->io_Device = 0;
X	if (ior)
X		DeleteStdIO(ior), ior = 0;
X	if (port)
X		DeletePort(port), port = 0;
X}
X
Xchar *cmdlist[] = {
X#define REWIND 0
X	"rewind",		/* Rewind the tape */
X/*	"reset",		/* Reset the controller */
X/*	"sense",		/* Print out request sense data */
X#define REQBLK 1
X#define SEEK 2
X#define FSB 3
X#define FSF 4
X#define FSSF 5
X#define EOD 6
X	"reqblk",		/* Print out current block position */
X	"seek",			/* Seek to given block */
X	"fsb",			/* Forward Space Blocks */
X	"fsf",			/* Forward Space Filemarks */
X	"fssf",			/* Forward Space Sequential Filemarks */
X	"eod",			/* Forward to EOD */
X#define ERASE 7
X#define WRITEFM 8
X	"erase",		/* Erase the tape */
X	"writefm",		/* Write filemarks on tape */
X#define VERIFY 9
X	"verify",		/* Run CRC verification on # blocks */
X	NULL };
Xchar **cmdp;
X
Xvoid usage(int ier, char *fmt, ...)
X{
X	if (fmt) {
X		va_list args;
X
X		va_start(args, fmt);
X		vfprintf(stderr, fmt, args);
X		va_end(args);
X	}
X	fprintf(stderr, "Usage:  %s [-s #] { command }\n", prog);
X	fputs("  -s specifies the SCSI ID of the tape drive, and\n", stderr);
X	fputs("  the rest are:  rewind, eod, reqblk, verify, and\n", stderr);
X	fputs("  erase don't take parameters;  seek, fsb, fsf, fssf,\n", stderr);
X	fputs("  and writefm do.\n", stderr);
X
X	exit( ier );
X}
X
Xvoid encode_size(register UBYTE *cmd, register int offset, register long pos)
X{
X	cmd[offset++] = (pos >> 16) & 0xFF;
X	cmd[offset++] = (pos >>  8) & 0xFF;
X	cmd[offset]   = (   pos   ) & 0xFF;
X}
X
Xmain(int argc, char **argv)
X{
X	int err, cmd, scsi_id = 2;		/* My tape drive is at "2" */
X	long value;
X
X	if ((prog = strrchr(*argv,'/')) || (prog = strrchr(*argv,':')))
X		prog++;
X	else
X		prog = *argv;
X
X	if (argc > 1 && !strcmp(argv[1], "-s")) {
X		if (argc > 2)
X			scsi_id = atoi(argv[2]);
X		argc -= 2;
X		argv += 2;
X	}
X	if (argc < 2)
X		usage(5, NULL);
X
X	init_io( scsi_id, &port, &ior );
X	ior->io_Command = HD_SCSICMD;
X	ior->io_Data    = (APTR) &sc;
X	ior->io_Length  = sizeof(sc);
X
X	sc.scsi_SenseData   = (UBYTE *) &sensedata;
X	sc.scsi_SenseLength = (UWORD)   sizeof(sensedata);
X	sc.scsi_Data        = (UWORD *) 0;
X	sc.scsi_Length      = (ULONG)   0;
X
X	while (argc-- > 1) {
X		++argv;
X		for (cmdp = cmdlist; *cmdp; cmdp++)
X			if (!strcmp(*cmdp, *argv))
X				break;
X		if (!*cmdp)
X			usage(5, "Unknown option `%s'\n", *argv);
X		cmd = cmdp - cmdlist;
X		if (cmd == FSB || cmd == FSF || cmd == FSSF ||
X										cmd == SEEK || cmd == WRITEFM) {
X			if (argc-- > 1)
X				value = atol(*++argv);
X			else
X				usage(5, "Required value for `%s' missing\n", *argv);
X		}
X		if (err = doit(cmd, value))
X			break;
X	}
X	return( err );
X}
X
Xint doit(int cmd, long value)
X{
X	int err;
X	unsigned char reqblkd[3];
X
X	sc.scsi_Flags = (UBYTE) SCSIF_READ | SCSIF_AUTOSENSE;
X	switch (cmd) {
X	case REWIND  :
X		sc.scsi_Command   = (UBYTE *) trewind;
X		sc.scsi_CmdLength = (ULONG)   sizeof(trewind);
X		break;
X#if 0
X	case RESET   :
X		sc.scsi_Command   = (UBYTE *) treset;
X		sc.scsi_CmdLength = (ULONG)   sizeof(treset);
X		break;
X	case SENSE   :
X		sc.scsi_Command   = (UBYTE *) treqsen;
X		sc.scsi_CmdLength = (ULONG)   sizeof(treqsen);
X		break;
X#endif
X	case REQBLK  :
X		sc.scsi_Command   = (UBYTE *) treqblk;
X		sc.scsi_CmdLength = (ULONG)   sizeof(treqblk);
X		sc.scsi_Data      = (UWORD *) reqblkd;
X		sc.scsi_Length    = (ULONG)   3;
X		break;
X	case SEEK    :
X		sc.scsi_Command   = (UBYTE *) tseekblk;
X		sc.scsi_CmdLength = (ULONG)   sizeof(tseekblk);
X		encode_size(tseekblk, 2, value);
X		break;
X	case FSB     :
X		sc.scsi_Command   = (UBYTE *) tspace;
X		sc.scsi_CmdLength = (ULONG)   sizeof(tspace);
X		tspace[1] = 0;
X		encode_size(tspace, 2, value);
X		break;
X	case FSF     :
X		sc.scsi_Command   = (UBYTE *) tspace;
X		sc.scsi_CmdLength = (ULONG)   sizeof(tspace);
X		tspace[1] = 1;
X		encode_size(tspace, 2, value);
X		break;
X	case FSSF    :
X		sc.scsi_Command   = (UBYTE *) tspace;
X		sc.scsi_CmdLength = (ULONG)   sizeof(tspace);
X		tspace[1] = 2;
X		encode_size(tspace, 2, value);
X		break;
X	case EOD     :
X		sc.scsi_Command   = (UBYTE *) tspace;
X		sc.scsi_CmdLength = (ULONG)   sizeof(tspace);
X		tspace[1] = 3;
X		break;
X	case ERASE   :
X		sc.scsi_Command   = (UBYTE *) terase;
X		sc.scsi_CmdLength = (ULONG)   sizeof(terase);
X		break;
X	case WRITEFM :
X		sc.scsi_Command   = (UBYTE *) twritefm;
X		sc.scsi_CmdLength = (ULONG)   sizeof(twritefm);
X		encode_size(twritefm, 2, value);
X		break;
X	case VERIFY  :
X		sc.scsi_Command   = (UBYTE *) tverify;
X		sc.scsi_CmdLength = (ULONG)   sizeof(tverify);
X		encode_size(tverify, 2, value);
X		break;
X	}
X	ior->io_Error = 0;
X	if (err = DoSCSI(ior))
X		fprintf(stderr, "Error %d during DoSCSI()!\n", err);
X	if (cmd == REQBLK)
X		printf("%ld\n", (reqblkd[0] << 16) | (reqblkd[1] << 8) | reqblkd[2]);
X	return( err ? err : ior->io_Error );
X}
X
Xinit_io(int scsi_id, struct MsgPort **Port, struct IOStdReq **Ior)
X{
X	int err;
X
X	atexit(cleanup);
X	if (! (*Port = CreatePort(0L, 0L)) ) {
X		fputs("Couldn't CreatePort()!\n", stderr);
X		exit( 10 );
X	}
X	if (! (*Ior = CreateStdIO(*Port)) ) {
X		fputs("Couldn't CreateStdIO()!\n", stderr);
X		exit( 10 );
X	}
X	if (err = OpenDevice("scsi.device", scsi_id, (struct IORequest *) *Ior, 0)) {
X		fprintf(stderr, "Error %d from OpenDevice()!\n", err);
X		exit( 10 );
X	}
X	return( 0 );
X}
X
Xchar *sensekey(int sense)
X{
X	static char *SenseKey[] = {
X		"FM/EOM or no status available",
X		"Recovered from error; command successful",
X		"Not Ready",
X		"Medium Error",
X		"Hardware Error",
X		"Illegal Request (illegal parameter in CDB)",
X		"Unit Attention (cartridge changed or Viper reset)",
X		"Data Protect (cartridge is write-portected)",
X		"Blank Check (no-data condition found on tape)",
X		"Vendor Unique Code -- not used on Viper",
X		"-- undocumented error --",
X		"Aborted Command (may retry)",
X		"-- undocumented error --",
X		"Volume Overflow (internal buffer may contain data)",
X	};
X#define KEY_SIZE	sizeof(SenseKey)
X	return( SenseKey[sense] );
X}
X
Xint DoSCSI(struct IOStdReq *ior)
X{
X	register int err, retry = 4;
X
X	while ((err = DoIO((struct IORequest *)ior)) && (retry-- > 0)) {
X		struct SCSICmd *scsicmd = (struct SCSICmd *) ior->io_Data;
X		struct reqsense *rs;
X
X		if (err != HFERR_BadStatus) {
X			fprintf(stderr, "Got error %d from DoIO()!\n", err);
X			return( err );
X		}
X		rs = (struct reqsense *) scsicmd->scsi_SenseData;
X		if (err = rs->bits[2]) {
X			if (err & FILEMARK) {
X				fputs("Filemark encountered.\n", stderr);
X				return( err );
X			} else if (err & EOM) {
X				fputs("End-Of-Media encountered.\n", stderr);
X				return( err );
X			} else {
X				err &= SENSEKEY;
X				fprintf(stderr, "sense key = %d (%s)\n", err, sensekey(err));
X				if (err == 1)			/* Recovered Error */
X					break;
X				else if (err != 6)		/* Unit Attention */
X					return( err );
X			}
X		}
X	}
X	return( 0 );
X}
@EOF

chmod 666 tctl.c

exit 0
------ cut here ------ cut here ------ cut here ------ cut here ------
-- 
Frank J. Edwards		|  "I did make up my own mind -- there
2677 Arjay Court		|   simply WASN'T ANY OTHER choice!"
Palm Harbor, FL  34684-4504	|		-- Me
Phone (813) 786-3675 (voice)	|    Only Amiga Makes It Possible...