/*
 * TRACKCOPY.C
 *
 * (C) Copyright Eddy Carroll 1989
 *
 * This program allows you to copy a portion of one disk device to a portion
 * of another device which has similar sector and track layout, though may
 * have a different number of cylinders. It is primarily intended to allow
 * a recoverable ram disk like RAD: to be preloaded directly from floppy
 * disk at high speed, a track at a time, instead of having to copy in files
 * manually.
 *
 * Usage: tcopy <srcdevice> <destdevice> <start> <stop> <newstart>
 *
 * The parameters are as follows (example values are in brackets)
 *
 * <srcdevice>	The device to copy from (DF0:)
 * <destdevice>	The device to copy to (RAD:)
 * <start>		The starting cylinder on <srcdevice> (60)
 * <end>		The ending cylinder on <srcdevice> (79)
 * <newstart>	The starting cylinder on <destdevice> (0)
 *
 * For example, supposing you want to setup a disk that you can load into
 * a 264K RAD (equals 24 cylinders of 2 tracks of 11 sectors of 512 bytes).
 * First of all, you setup RAD: manually by mounting it and then copying
 * in the appropriate files. Then you put a blank formatted disk into
 * drive 0 and give the command:
 *
 * 		tcopy RAD: DF0: 0 23 56
 *
 * Meaning: Copy cylinders 0-23 of RAD: to cylinders 56-79 of DF0:.
 *
 * You then add enough files to DF0: to make a minimal boot disk, taking care
 * not to add more than (56-40 = 16) cylinders of data (or 176K), since
 * this would cause the data you've just written directly onto the disk
 * to be overwritten. Then put the command:
 *
 *		tcopy DF0: RAD: 56 79 0
 *
 * into your startup-sequence, and when you reboot, RAD: will be reloaded
 * with the appropriate information.
 * ----
 * An additional option may be compiled in by defining the constant
 * RAMBO at compile time. When this is done, tcopy will have
 * some additional options which allow rad: to be automatically mounted
 * and initialised. This is designed to remove the overhead of having
 * to do a MOUNT RAD: in your startup-sequence. When this is the case,
 * the following options may be specified before the rest of the command
 * line. If none are specified, RAD: is not mounted. Defaults are in ().
 *
 *     -v<name>    Sets RAD: volume name to be <name>  ("RAMB0")
 *     -n<name>    Sets RAD: device name to be <name>  ("RAD") (no colon!)
 *     -d<name>    Sets RAD: exec device to be <name>  ("ramdrive.device")
 *	   -e<txt>	   Echos <txt> to stdout
 *	   -a		   Always do track copy, even if RAD: already mounted
 *     -f#         Sets flags for OpenDevice() to #    (0)
 *     -m#         Sets BufMemType to #                (1)
 *     -p#         Sets boot priority to #             (-1)
 *     -t#         Sets number of tracks(%) to #       (10)
 *     -u#         Sets unit number of device to be #  (0)
 *
 * (%) Actually the number of cylinders, but people seem to like to think
 *     of them as tracks.
 */

#define TRUE			1
#define FALSE			0
#define LASTCHAR(s)		(s[strlen(s)-1])

#ifndef LATTICE_50
#include "system.h"
#endif

extern struct DosLibrary	   *DOSBase;

typedef struct IORequest       IOREQ;
typedef struct MsgPort         MSGPORT;
typedef struct Process         PROC;
typedef struct StandardPacket  STDPKT;

void inhibit(MSGPORT *devport, int mode);
/*
 *		Structure representing a disk device
 */
typedef struct {
	char  *devname;			/* Name of exec device for this disk	*/
	int   isfloppy;			/* True if device is trackdisk.device	*/
	ULONG unit;				/* Unit number of above exec device		*/
	ULONG blocksize;		/* Number of bytes per block			*/
	ULONG blockspertrack;	/* Number of blocks/sectors per track	*/
	ULONG surfaces;			/* Number of tracks per cylinder		*/
	ULONG lowcyl;			/* Starting cylinder of disk on device 	*/
	ULONG numcyl;			/* Number of cylinders on this disk		*/
} DISKDEV;

#ifdef RAMBO
/************************** Declarations for RAMBO **************************/

struct ExpansionBase *ExpansionBase;

char execname[] = "ramdrive.device";
char dosname[]  = "RAD";
char *volname   = "RAMB0";
int bootpri     = -1;

/*
 *		Parameter block for mounting RAD:
 */
struct MountParamBlock {
	char	*dosname;
	char	*execname;
	ULONG	unit;
	ULONG	flags;
	ULONG	size;
	ULONG	blocksize;
	ULONG	origin;
	ULONG	surfaces;
	ULONG	sectorperblock;
	ULONG	sectorspertrack;
	ULONG	reserved;
	ULONG	prealloc;
	ULONG	interleave;
	ULONG	lowcyl;
	ULONG	highcyl;
	ULONG	numbuffers;
	ULONG	bufmemtype;
} mount = {
	dosname,
	execname,
	0,					/*  2: Unit number						*/
	0,					/*  3: OpenDevice() flags				*/

	/* This is where the environment block starts */
	11,					/*  4: Table upper bound				*/
	512>>2,				/*  5: Number of longwords per block	*/
	0,					/*  6: Sector origin - unused			*/
	2,					/*  7: Number of surfaces				*/
	1,					/*  8: Sectors per block - unused		*/
	11,					/*  9: Sectors per track				*/
	2,					/* 10: Reserved blocks - boot block		*/
	0,					/* 11: Reserved blocks at end			*/
	0,					/* 12: Interleave						*/
	0,					/* 13: Low cylinder						*/
	10,					/* 14: High cylinder					*/
	5 					/* 15: Number of buffers				*/
};

/*********************** End of declarations for RAMBO *********************/
#endif RAMBO

/*
 *		Global variables
 */

char *srcname;					/* AmigaDos name of source device			*/
char *destname;					/* AmigaDos name of destination device		*/
DISKDEV src[1];					/* Source disk device						*/
DISKDEV dest[1];				/* Destination disk device					*/
struct IOStdReq *srcreq;		/* Standard request for source device		*/
struct IOStdReq *destreq;		/* Standard request for destination device	*/
MSGPORT *reqport;				/* Message port for replies from devices	*/
MSGPORT *destport;				/* Message port of process of dest. device	*/
void *buffer;					/* Pointer to data buffer for track r/w		*/
long cylsize;					/* Size in bytes of a single cylinder		*/
int	srcdevopen;					/* True if source exec device is open		*/
int	destdevopen;				/* True if destination device is open		*/
int inhibited;					/* True if destination device is inhibited	*/


/*
 *		print()
 *		-------
 *		Outputs a message to stdout
 */
void print(char *s)
{
	Write(Output(), s, strlen(s));
}
#define print2(s1,s2)    (print(s1),print(s2))
#define print3(s1,s2,s3) (print(s1),print(s2),print(s3))

/*
 *		numtostr()
 *		----------
 *		Simple little function which returns a pointer to a static string
 *		representation of the passed in number.
 */
char *numtostr(int n)
{
	static char s[20];
	int i = 19;

	s[19] = '\0';
	if (n)
		while (n) {
			s[--i] = '0' + (n % 10);
			n /= 10;
		}
	else
		s[--i] = '0';
	return(&s[i]);
}


/*
 *		cleanup()
 *		---------
 *		Closes all opened resources, and exits with specified error code.
 */
void cleanup(int code)
{
	if (buffer)
		FreeMem(buffer, cylsize);

	if (srcdevopen) {
		if (src->isfloppy) {	/* Turn off drive motor if floppy disk */
			srcreq->io_Command = TD_MOTOR;
			srcreq->io_Length = 0;
			DoIO((IOREQ *)srcreq);
		}
		CloseDevice((IOREQ *)srcreq);
	}
	if (destdevopen) {
		if (dest->isfloppy) {	/* Turn off drive motor if floppy disk */
			destreq->io_Command = TD_MOTOR;
			destreq->io_Length = 0;
			DoIO((IOREQ *)destreq);
		}
		CloseDevice((IOREQ *)destreq);
	}

	if (inhibited)
		inhibit(destport, FALSE);

	if (srcreq)
		DeleteStdIO(srcreq);
	if (destreq)
		DeleteStdIO(destreq);

	if (reqport)
		DeletePort(reqport);

#ifdef RAMBO
	if (ExpansionBase)
		CloseLibrary((struct Library *)ExpansionBase);
#endif RAMBO

	exit(code);
}


/*
 *		chkabort()
 *		----------
 *		A replacement for Lattice's chkabort(), which doesn't carry all
 *		the extra baggage. If CTRL-C is detected, this function never
 *		returns but instead calls cleanup. Since Lattice's exit() code
 *		may call chkabort(), a check is made to ensure that cleanup()
 *		only gets called once, otherwise there would be a problem if the
 *		user pressed Ctrl-C twice in quick succession.
 */
void chkabort()
{
	static int gotctrlc = FALSE;
	if (!gotctrlc && (SetSignal(0,0) & SIGBREAKF_CTRL_C)) {
		gotctrlc = TRUE;
		print("^C\n");
		cleanup(20);
	}
}

/*
 *		GetVolume()
 *		-----------
 *		This function searches the device list for the named volume, and
 *		fills in the passed DISKDEV structure with information about the
 *		volume. If the named volume is not a device, or is not a disk,
 *		then FALSE is returned. It is not an error to pass in NULL as
 *		a pointer to the DISKDEV structure. In this case, nothing will
 *		be filled in, but TRUE or FALSE will be returned indicating whether
 *		the device is a disk device or not.
 */
int GetVolume(char *devname, DISKDEV *dev)
{
	struct RootNode *rootnode;
	struct DosInfo *dosinfo;
	struct DeviceNode *devnode;
	struct FileSysStartupMsg *startup;
	struct DosEnvec *dosenvec;
	unsigned char *p;
	int namelen = strlen(devname);

	if (LASTCHAR(devname) != ':')	/* Device names must end with ':' */
		return (FALSE);
	/*
	 *		First of all, find the device
	 */
	rootnode = (struct RootNode *)DOSBase->dl_Root;
	dosinfo = (struct DosInfo *)BADDR(rootnode->rn_Info);
	devnode = (struct DeviceNode *)BADDR(dosinfo->di_DevInfo);

	Forbid();	/* Make sure list doesn't change while we scan it */

	while (devnode != NULL) {
		p = (unsigned char *)BADDR(devnode->dn_Name)+1;
		if (!strnicmp(devname, p, namelen-1)) {	/* Don't compare the ':' */
			/*
			 *		Matched name successfully. Now check if it's a device.
			 *		Note that we carry on searching if it's not a device
			 *		(rather than returning FALSE immediately) since there
			 *		may be a volume called RAD: as well as a device called
			 *		RAD:, for example.
			 */
			if (devnode->dn_Type == DLT_DEVICE) {
				if (devnode->dn_Startup < 20)	/* Is there a startup bit?	*/
					goto notfound;				/* If not, not disk device	*/
					/* Eek! A GOTO! */

				startup = (struct FileSysStartupMsg *)
										BADDR(devnode->dn_Startup);

				if (dev) {
					dev->devname = ((char *)BADDR(startup->fssm_Device))+1;
					dev->isfloppy = (!strcmp(TD_NAME, dev->devname));
					dev->unit = startup->fssm_Unit;
				}

				if (startup->fssm_Environ < 20)
					goto notfound;
				/* Another GOTO! The Earth will end in 5 seconds... */

				dosenvec = (struct DosEnvec *)BADDR(startup->fssm_Environ);

				if (dev) {
					dev->blocksize		= dosenvec->de_SizeBlock << 2;
					dev->blockspertrack	= dosenvec->de_BlocksPerTrack;
					dev->surfaces		= dosenvec->de_Surfaces;
					dev->lowcyl			= dosenvec->de_LowCyl;
					dev->numcyl			= (dosenvec->de_HighCyl -
												dosenvec->de_LowCyl) + 1;
				}
				Permit();
				return (TRUE);
			}
		}
		devnode = (struct DeviceNode *)BADDR(devnode->dn_Next);
	}
notfound:
	Permit();
	return (FALSE);
}

/*
 *		help()
 *		------
 *		Prints out a help message about tcopy
 */
void help()
{
	print(
"Tcopy (C) Copyright Eddy Carroll, January 1990. Freely distributable.\n"
);
#ifdef RAMBO
#define print13(a,b,c,d,e,f,g,h,i,j,k,l,m) (print3(a,b,c),print3(d,e,f),\
									print3(g,h,i),print3(j,k,l),print(m))
print13(
"Usage: tcopy <flags> <from> <to> <start> <end> <newstart>\n\n",
"  <flags>     If any of the following are included, then RAD: will be\n",
"              automatically mounted when tcopy is run:\n\n",
"              -v<name>    Sets RAD: volume name to be <name> (\"RAMB0\")\n",
"              -n<name>    Sets RAD: device name to be <name>   (\"RAD\")\n",
"              -d<name>    Use exec device <name>   (\"ramdrive.device\")\n",
"              \"-e<txt>\"   Prints <txt> to stdout\n",
"              -a          Do trackcopy, even if RAD: already mounted\n",
"              -f#         Sets flags for OpenDevice() to #         (0)\n",
"              -m#         Sets BufMemType to #                     (1)\n",
"              -p#         Sets boot priority to #                 (-1)\n",
"              -t#         Sets number of tracks to #              (10)\n",
"              -u#         Sets unit number of exec device to #     (0)\n\n"
);
#else
	print("Usage: tcopy <from> <to> <start> <end> <newstart>\n\n");
#endif RAMBO
	print("  <from>      Device to copy from (e.g. DF0:)\n");
	print("  <to>        Device to copy to (e.g. RAD:)\n");
	print("  <start>     Cylinder to start reading from (e.g. 70)\n");
	print("  <end>       Cylinder to end reading at (e.g. 79)\n");
	print("  <newstart>  Cylinder to start writing at (e.g. 0)\n");
}


/*
 *		opendevs()
 *		----------
 *		Opens the source and destination devices, allocates buffer for
 *		reading and writing etc. Note that if either device is a floppy
 *		drive, the buffer must be allocated in chip memory or trackdisk.device
 *		won't be able to blit into it.
 */
void opendevs()
{
	long memflags = 0;

	if (src->isfloppy || dest->isfloppy)
		memflags |= MEMF_CHIP;

	cylsize = src->blocksize * src->blockspertrack * src->surfaces;
	buffer = AllocMem(cylsize, memflags);
	if (!buffer) {
		print("Not enough memory for track buffer\n");
		cleanup(20);
	}

	reqport = (MSGPORT *)CreatePort(0,0);
	if (!reqport) {
		print("Couldn't allocate message port\n");
		cleanup(20);
	}

	srcreq	= CreateStdIO(reqport);
	destreq = CreateStdIO(reqport);
	if (!srcreq || !destreq) {
		print("Couldn't allocate IO request - memory is low!\n");
		cleanup(20);
	}

	if (OpenDevice(src->devname, src->unit, (IOREQ *)srcreq, 0L)) {
		print3("Can't open source ", src->devname, "\n");
		cleanup(20);
	}
	srcdevopen = TRUE;

	if (OpenDevice(dest->devname, dest->unit, (IOREQ *)destreq, 0L)) {
		print3("Can't open destination ", dest->devname, "\n");
		cleanup(20);
	}
	destdevopen = TRUE;
}

/*
 *		copytracks()
 *		------------
 *		This is where the actual work gets done. Tracks (cylinders actually)
 *		are copied from start to end on the source device to newstart on
 *		the destination device.
 */
void copytracks(int start, int end, int newstart)
{
	int cyl, retry, numcyls = (end - start) + 1;

	for (cyl = 0; cyl < numcyls; cyl++) {
		/*
		 *		First read in track from source device
		 */
		for (retry = 0; retry < 3; retry++) {
			chkabort();
			srcreq->io_Command = CMD_READ;
			srcreq->io_Length  = cylsize;
			srcreq->io_Offset  = (src->lowcyl + cyl + start) * cylsize;
			srcreq->io_Data    = buffer;
			if (!DoIO((IOREQ *)srcreq))
				break;		/* Succeeded, so break out of loop */
		}
		if (retry == 3) {
			print3("Error reading track ", numtostr(cyl+start)," from disk\n");
			cleanup(20);
		}

		/*
		 *		Now write out track to destination device
		 */
		for (retry = 0; retry < 3; retry++) {
			chkabort();
			destreq->io_Command = CMD_WRITE;
			destreq->io_Length  = cylsize;
			destreq->io_Offset  = (dest->lowcyl + cyl + newstart) * cylsize;
			destreq->io_Data    = buffer;
			if (!DoIO((IOREQ *)destreq))
				break;		/* Succeeded */
		}
		if (retry == 3) {
			print3("Error writing track ", numtostr(cyl), " to disk\n");
			cleanup(20);
		}
	}
}


/*
 *		SendPacket()
 *		------------
 *		``Sort of'' simulates the ARP SendPacket() routine which sends
 *		a packet to AmigaDos, and gets a reply if appropriate. What is
 *		passed in is the action to be executed (one of the ACTION_xxx
 *		definitions in dosextens.h), a pointer to a longword array of 7
 *		arguments to be passed to the device, and the msgport of the device
 *		as returned by DeviceProc("DF0:") for example. If result is non-NULL
 *		then it should be a pointer to a two element array of ULONGs, and it
 *		fills in the 0th element with the primary result, and the the
 *		1st element with the secondary result.
 */
int SendPacket(ULONG action, void *args, MSGPORT *devport, ULONG *result)
{
	PROC *proc = (PROC *)FindTask(NULL);
	STDPKT *packet;

	packet = (STDPKT *)AllocMem(sizeof(STDPKT), MEMF_CLEAR | MEMF_PUBLIC);
	if (!packet)
		return (FALSE);
	packet->sp_Msg.mn_Node.ln_Name = (char *)&packet->sp_Pkt;
	packet->sp_Pkt.dp_Link         = &packet->sp_Msg;
	packet->sp_Pkt.dp_Port         = &proc->pr_MsgPort;
	packet->sp_Pkt.dp_Type         = action;
	memcpy(&packet->sp_Pkt.dp_Arg1, args, sizeof(ULONG) * 7);

	/*
	 *		Okay, we've done the necessary magic to create an AmigaDos
	 *		packet lookalike (thanks to Matt Dillon in Transactor V1.1).
	 *		Now we send the message to the Dos process, and get the reply.
	 *		Then our message will be filled in with the response from the
	 *		Dos process.
	 */
	PutMsg(devport, (struct Message *)packet);
	WaitPort(&proc->pr_MsgPort);
	GetMsg(&proc->pr_MsgPort);
	if (result) {
		result[0] = packet->sp_Pkt.dp_Res1;
		result[1] = packet->sp_Pkt.dp_Res2;
	}
	FreeMem(packet, sizeof(STDPKT));
	return (TRUE);
}



/*
 *		inhibit()
 *		---------
 *		This function inhibits (if mode is TRUE) or hibits (if mode is FALSE)
 *		(is hibit the opposite of inhibit? Hmmm...) the specified device.
 */
void inhibit(MSGPORT *devport, int mode)
{
	ULONG pktargs[7];
	int i;

	pktargs[0] = mode;			/* Select inhibit or opposite */
	for (i = 1; i < 7; i++)		/* Clear other arguments      */
		pktargs[i] = 0;

	if (!SendPacket(ACTION_INHIBIT, pktargs, devport, NULL)) {
		print("Couldn't send inhibit packet to device\n");
		cleanup(20);
	}
}


#ifdef RAMBO
/*
 *		mountramdisk()
 *		--------------
 *		This procedure simply mounts the device specified in the mount
 *		parameter block.
 */
int mountramdisk()
{
	struct DeviceNode *devnode;
	long args[7];
	int i;
	char *diskname;
	MSGPORT *devport;
	static char dosname[200];	/* Temporary storage for dos device name */

	ExpansionBase = (struct ExpansionBase *)
						OpenLibrary("expansion.library", 0L);
	if (!ExpansionBase) {
		print("Couldn't open expansion.library\n");
		cleanup(20);
	}

	devnode = MakeDosNode(&mount);
	if (!devnode)
		return (FALSE);

	AddDosNode(bootpri, ADNF_STARTPROC, devnode);

	/*
	 *		Now we've mounted the device, let's try and rename it to
	 *		something different.
	 */
	strcpy(dosname, mount.dosname);
	strcat(dosname, ":");
	devport = (MSGPORT *)DeviceProc(dosname);
	if (!devport)
		return (FALSE);
	for (i = 1; i < 7; i++)
		args[i] = 0;
	/*
	 *		Some horrible messing around to make a BSTR
	 */
	diskname = AllocMem(strlen(volname)+1,MEMF_PUBLIC);
	strcpy(diskname+1, volname);
	*diskname = strlen(volname);
	args[0] = ((long)diskname)>>2;
	if (!SendPacket(ACTION_RENAME_DISK, args, devport, NULL))
		print("(Couldn't relabel disk\n");
	/* Don't return an error, even if SendPacket failed! */
	FreeMem(diskname, strlen(volname)+1);
	return (TRUE);
}
#endif RAMBO


/*
 *		mainline()
 *		----------
 */
void main(argc,argv)
int argc;
char **argv;
{
	unsigned int start, end, newstart;			/* Cylinder numbers */

#ifdef RAMBO
/************************* Start of RAMBO Stuff ***************************/

	int doload  = FALSE; /* If true, always copy to device even if mounted */

	if (argc > 1 && argv[1][0] == '-') { /* Handle Rambo options */
		static char tempname[200];
		while (argc > 1 && argv[1][0] == '-') {
			char *str = &argv[1][2];		/* Have handy ptr to string	*/
			int num = atoi(str);			/* Have parameter ready		*/

			switch (argv[1][1]) {

				case 'v':				/* Set volume name */
					volname = str;
					break;

				case 'n':				/* Set device name */
					/* Strip trailing ':' */
					if (LASTCHAR(str) == ':')
						LASTCHAR(str) = '\0';
					mount.dosname = str;
					break;

				case 'd':				/* Set exec device name */
					mount.execname = str;
					break;

				case 'u':				/* Set device unit number */
					mount.unit = num;
					break;

				case 'f':				/* Set flags for OpenDevice() */
					mount.flags = num;
					break;

				case 't':				/* Set number of tracks/cylinders */
					mount.highcyl = num - 1;
					break;

				case 'm':				/* Set memory type */
					mount.bufmemtype = num;
					break;

				case 'p':				/* Set boot priority */
					bootpri = num;
					break;

				case 'e':				/* Echo message to stdout */
					print2(&argv[1][2], "\n");
					break;

				case 'a':				/* Force loading into RAD: */
					doload  = TRUE;
					break;

				default:
					print3("Unknown switch ", argv[1], "\n");
					help();
					cleanup(5);
			}
			argv++;
			argc--;
		}

		/*
		 *		Setup defaults for mount okay. Now see if the device is
		 *		to be mounted. If it is, then try and mount it. If it was
		 *		already mounted, then quit immediately, unless forceload is
		 *		in effect.
		 *
		 *		Else, see are there any files in RAD:. If there are, quit
		 *		immediately (since we are under Kikstart 1.2, and RAD: has
		 *		just recovered itself from memory), unless forceload is
		 *		in effect.
		 */
		strcpy(tempname, mount.dosname);
		strcat(tempname, ":");
		if (!GetVolume(tempname, NULL)) {

			if (!mountramdisk()) {
				print3("Error: Couldn't mount device ",tempname,"\n");
				cleanup(20);
			}
		}
		/*
		 *		Scan device and see if there are files on it.
		 *		Do this by seeing if there are any files in
		 *		the directory. If there aren't, then we can
		 *		force the load, otherwise don't force it.
		 */
		{
			struct FileInfoBlock *fib;
			BPTR lock;

			fib = AllocMem(sizeof(struct FileInfoBlock), 0L);
			if (!fib) {
				print("Out of memory allocating FileInfoBlock!\n");
				cleanup(20);
			}
			lock = Lock(tempname, ACCESS_READ);
			if (lock) {
				if (Examine(lock, fib)) {
					if (!ExNext(lock, fib))
						doload = TRUE;
				}
				UnLock(lock);
			}
			FreeMem(fib, sizeof(struct FileInfoBlock));
		}

		/*
		 *		Now see if after all that, we still want to go
		 *		ahead with the load into RAD:. If not, then just
		 *		exit silently (to let the startup-sequence carry
		 *		on okay).
		 */
		if (!doload)
			cleanup(0);
	}

/************************* End of RAMBO Stuff ***************************/
#endif RAMBO

#define NONUM(x) (!isdigit(argv[x][0]))

	if (argc != 6 || NONUM(3) || NONUM(4) || NONUM(5)) {
		help();
		cleanup(5);
	}

	srcname  = argv[1];
	destname = argv[2];
	start    = atoi(argv[3]);
	end      = atoi(argv[4]);
	newstart = atoi(argv[5]);

	if (!GetVolume(srcname, src)) {
		print2(srcname, " is not a valid disk device\n");
		cleanup(20);
	}

	if (!GetVolume(destname, dest)) {
		print2(destname, " is not a valid disk device\n");
		cleanup(20);
	}

#define NOTSAME(x) (src->x != dest->x)

	if (NOTSAME(blocksize) || NOTSAME(blockspertrack) || NOTSAME(surfaces)) {
		print3(srcname, " and ", destname);
		print(" do not have same track sizes\n");
		cleanup(20);
	}

	if (start > end) {
		print("Start track is greater than end track.\n");
		cleanup(20);
	}

	if (end >= src->numcyl) {
		print3("Maximum end track on ", srcname, " is");
		print2(numtostr(src->numcyl - 1), ".\n");
		cleanup(20);
	}

	if ((newstart + (end - start)) >= dest->numcyl) {
		print2("There is not room for ", numtostr(1 + end - start));
		print3(" tracks on ", destname, "\n");
		cleanup(20);
	}

	destport = (MSGPORT *)DeviceProc(destname);
	if (!destport) {
		print3("Can't locate process for device ", destname,"\n");
		cleanup(20);
	}

	/*
	 *		The two devices are valid, so now open the exec devices which
	 *		they use.
	 */
	opendevs();
	inhibit(destport, TRUE);
	inhibited = TRUE;
	copytracks(start, end, newstart);
	cleanup(0);
}
