/*
 * File/Device copier by Chris Hooper (cdh@mtu.edu)
 *
 *	This program can copy files to devices and devices to files, etc.
 *
 * Usage:  (when complete)
 *    dcp {source} {dest} [-n number of blocks] [-b buffer size] [-s start block]
 *
 *
 * This product is distributed as freeware with no warrantees expressed or
 * implied.  There are no restrictions on distribution or application of
 * this program other than it may not be used in contribution with or
 * converted to an application under the terms of the GNU Public License.
 * Also, this notice must accompany all redistributions and/or modifications
 * of this program.
 *
 */

#include <stdio.h>
#include <exec/types.h>
#include <devices/trackdisk.h>
#include <libraries/dos.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <exec/io.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <exec/memory.h>

char *strchr();
struct FileSysStartupMsg *find_startup();

/* NONE   = Could not be opened */
/* DEVICE = Block device opened */
/* FILE   = Regular AmigaDOS file opened */
/* STDIO  = STDIO channel specified */

#define tNONE 	0
#define tDEVICE	1
#define tFILE 	2
#define tSTDIO 	3

struct file_info {
	char *name;		/* filename as specified on cmdline */
	int   dtype;		/* NONE, DEVICE, FILE, or STDIO */
	FILE *strIO;		/* stream file pointer */
	struct IOExtTD *trackIO;/* device packet pointer */
	int currentblk;		/* current read/write block in device/file */
	int maxblk;		/* maximum allowed read/write blk in dev/file */
};

#define dREAD  0
#define dWRITE 1

#define BTOC(x) ((x)<<2)
#define CTOB(x) ((x)>>2)

struct file_info in;
struct file_info out;

char *progname;
char *block_buffer = NULL;
int debug=1;
int buffer_blocks = 50;
char iobuf[128];

main(argc, argv)
int argc;
char *argv[];
{
	int names = 0;
	int index;

	progname = argv[0];

	setvbuf(stderr, iobuf, _IOLBF, sizeof(iobuf));

	if (argc < 3)
		print_usage();

	for (index = 1; index < argc; index++) {
		if (!strcmp(argv[index], "-n")) {	    /* number of blocks */
			fprintf(stderr, "-n is not currently supported\n");
			exit(1);
		} else if (!strcmp(argv[index], "-b")) {    /* buffer size */
			index++;
			if (index < argc) {
				buffer_blocks = atoi(argv[index]);
				if (buffer_blocks < 1) {
					fprintf(stderr, "number of buffer blocks required for -b parameter\n");
					exit(1);
				}
			} else  {
				fprintf(stderr, "number of buffer blocks required for -b parameter\n");
				exit(1);
			}
		} else if (!strcmp(argv[index], "-s")) {    /* start block */
			fprintf(stderr, "-s is not currently supported\n");
			exit(1);
		} else {
			names++;
			if (names == 1)
				in.name  = argv[index];
			else if (names == 2)
				out.name = argv[index];
			else {
				fprintf(stderr, "If input=%s and output=%s, what is %s for?\n",
					in.name, out.name, argv[index]);
				exit(1);
			}
		}
	}

	if (names != 2) {
		printf("Source and destination are required for %s\n", progname);
		exit(1);
	}

	while ((block_buffer == NULL) && (buffer_blocks)) {
		block_buffer = (char *) AllocMem(buffer_blocks *
						 TD_SECTOR, MEMF_PUBLIC);
		if (block_buffer == NULL)
			buffer_blocks >>= 1;
	}
	if (block_buffer == NULL) {
		fprintf(stderr, "Unable to allocate even 1 block for copy buffer!\n");
		exit(1);
	}
#ifdef DEBUG
	else
		fprintf(stderr, "%d buffers allocated\n", buffer_blocks);
#endif

	do_open(&in,  dREAD);
	do_open(&out, dWRITE);

	setvbuf(stderr, NULL, _IONBF, 0);
	if ((in.dtype != tNONE) && (out.dtype != tNONE))
		while (do_io(&in, dREAD))
			if (do_io(&out, dWRITE) == 0)
				break;
	setvbuf(stderr, iobuf, _IOLBF, sizeof(iobuf));

	fprintf(stderr, "\n");

	do_close(&in);
	do_close(&out);

	FreeMem(block_buffer, buffer_blocks * TD_SECTOR);
}

do_open(fdes, dtype)
struct file_info *fdes;
int dtype;
{
	int len;

	fdes->currentblk = 0;		/* start at beginning */
	fdes->maxblk = -1;		/* no maximum block */

	if (debug)
		if (dtype == dREAD)
			fprintf(stderr, "Read  ");
		else
			fprintf(stderr, "Write ");

	if (!strcmp(fdes->name, "-")) {		/* stdio */
		fdes->dtype = tSTDIO;
		if (dtype == dREAD)
			fdes->strIO = stdin;
		else
			fdes->strIO = stdout;
		if (debug)
			fprintf(stderr, "STDIO %s\n", fdes->name);
		return;
	}

	len = strlen(fdes->name);
	if (len == 0) {
		fprintf(stderr, "Error opening file for %s.\n",
			(dtype == dREAD) ? "read" : "write");
		return;
	}

	if (fdes->name[len - 1] != ':') {	/* must be a file */
		fdes->currentblk =  0;
		fdes->maxblk     = -1;
		if (dtype == dREAD) {
			struct stat stat_buf;
			if (!stat(fdes->name, &stat_buf))
				fdes->maxblk = (stat_buf.st_size +
						TD_SECTOR - 1) / TD_SECTOR;
		}
		fdes->strIO = fopen(fdes->name, (dtype == dREAD) ? "r" : "w");
		if (fdes->strIO == NULL) {
			fprintf(stderr, "Error opening file %s for %s.\n",
				fdes->name, (dtype == dREAD) ? "read" : "write");
			fdes->dtype = tNONE;
		} else {
			fdes->dtype = tFILE;
			if (debug) {
				fprintf(stderr, "file   %-30s%12s", fdes->name, "");
				fprintf(stderr, "start=%-8d  ", fdes->currentblk);
				fprintf(stderr, "end=%-8d\n", fdes->maxblk);
			}
		}
	} else {				/* must be a device */
		struct FileSysStartupMsg *startup;
		struct DosEnvec *envec;
		char *disk_device;
		int disk_unit, disk_flags;

		startup = find_startup(fdes->name);
		if (startup == NULL) {
			fprintf(stderr, "Error opening device %s for %s; does not exist\n",
				fdes->name, (dtype == dREAD) ? "read" : "write");
			fdes->dtype = tNONE;
			return;
		}

		if (!(fdes->trackIO = (struct IOExtTD *)
			CreateExtIO(CreatePort(0, 0), sizeof(struct IOExtTD)) )) {
			fprintf(stderr, "fatal: Failed to create trackIO structure for %s.\n", fdes->name);
			return;
		}

		disk_device	= ((char *) BTOC(startup->fssm_Device)) + 1;
		disk_unit	= startup->fssm_Unit;
		disk_flags	= startup->fssm_Flags;
		envec		= (struct DosEnvec *) BTOC(startup->fssm_Environ);
		fdes->currentblk= envec->de_LowCyl * envec->de_Surfaces *
				  envec->de_BlocksPerTrack;
		fdes->maxblk	= envec->de_HighCyl * envec->de_Surfaces *
				  envec->de_BlocksPerTrack;

		if (debug) {
			char buf[40];
			sprintf(buf, "[%s]=%s", fdes->name, disk_device);
			fprintf(stderr, "device %-22s ", buf);
			fprintf(stderr, "unit=%-2d  ", disk_unit);
			fprintf(stderr, "flags=%-2d  ", disk_flags);
			fprintf(stderr, "start=%-8d  ", fdes->currentblk);
			fprintf(stderr, "end=%-8d\n", fdes->maxblk);
		}

		if (OpenDevice(disk_device, disk_unit, fdes->trackIO, disk_flags)) {
			fprintf(stderr, "fatal: Unable to open %s unit %d for %s.\n",
				disk_device, disk_unit, fdes->name);
			DeletePort(fdes->trackIO->iotd_Req.io_Message.mn_ReplyPort);
			DeleteExtIO(fdes->trackIO);
			return;
		}

		fdes->trackIO->iotd_Req.io_Command  = (dtype == dREAD) ?
							CMD_READ : CMD_WRITE;
		fdes->trackIO->iotd_Req.io_Data	    = block_buffer;

		fdes->dtype = tDEVICE;
	}
}

struct FileSysStartupMsg *find_startup(name)
char *name;
{
	struct	DosLibrary *DosBase;
	struct	RootNode *rootnode;
	struct	DosInfo *dosinfo;
	struct	DevInfo *devinfo;
	struct	DosEnvec *envec;
	static  struct FileSysStartupMsg *startup;
	char	*devname;
	char	*pos;
	int	notfound = 1;

	if ((pos = strchr(name, ':')) != NULL)
		*pos = '\0';

	DosBase = (struct DosLibrary *) OpenLibrary("dos.library", 0L);

	rootnode= DosBase->dl_Root;
	dosinfo = (struct DosInfo *) BTOC(rootnode->rn_Info);
	devinfo = (struct DevInfo *) BTOC(dosinfo->di_DevInfo);

	while (devinfo != NULL) {
		devname	= (char *) BTOC(devinfo->dvi_Name);
		if (unstrcmp(devname + 1, name)) {
			notfound = 0;
			break;
		}
		devinfo	= (struct DevInfo *) BTOC(devinfo->dvi_Next);
	}

	if (notfound) {
		fprintf(stderr, "%s: is not mounted.\n", name);
		exit(1);
	}

	startup	= (struct FileSysStartupMsg *) BTOC(devinfo->dvi_Startup);
	CloseLibrary(DosBase);
	return(startup);
}

/* unsigned string compare */
int unstrcmp(str1, str2)
char *str1;
char *str2;
{
	while (*str1 != '\0') {
		if (*str2 == '\0')
			break;
		else if (*str1 != *str2)
			if ((*str1 >= 'A') && (*str1 <= 'Z')) {
				if ((*str2 >= 'A') && (*str2 <= 'Z'))
					break;
				else if (*str1 != *str2 + 'A' - 'a')
					break;
			} else {
				if ((*str2 >= 'a') && (*str2 <= 'a'))
					break;
				else if (*str1 != *str2 - 'A' + 'a')
					break;
			}
		str1++;
		str2++;
	}
	if (*str1 == *str2)
		return(1);
	else
		return(0);
}

do_close(fdes)
struct file_info *fdes;
{
#ifdef DEBUG
	fprintf(stderr, "closing %s\n", fdes->name);
#endif
	if (fdes->dtype == tSTDIO)
		return;
	else if (fdes->dtype == tFILE)
		fclose(fdes->strIO);
	else if (fdes->dtype == tDEVICE)
		if (fdes->trackIO != NULL) {
			CloseDevice(fdes->trackIO);
			DeletePort(fdes->trackIO->iotd_Req.io_Message.mn_ReplyPort);
			DeleteExtIO(fdes->trackIO);
		}
}

/*    dcp {source} {dest} [-n number of blocks] [-b buffer size] [-s start block] */
print_usage()
{
	fprintf(stderr, "Usage: %s {source} {dest} [-b buffer size]\n", progname);
	exit(1);
}

int last_read_blocks;		/* number of blocks last read */

do_io(fdes, dtype)
struct file_info *fdes;
int dtype;
{
	int io_left = 0;
	int last_write_blks;


	if (dtype == dREAD) {
		io_left = buffer_blocks;

		if (fdes->maxblk > -1)
			if ((fdes->maxblk - fdes->currentblk) < io_left)
				io_left = fdes->maxblk - fdes->currentblk;
/*
fprintf(stderr, "tran=%c buffer_blocks=%d cur=%d max=%d left=%d\n",(dtype == dREAD) ?
	'R' : 'W', buffer_blocks, fdes->currentblk, fdes->maxblk, io_left);
*/

		if (io_left < 1)
			return(0);
	} else {
		io_left = last_read_blocks;

		if (fdes->maxblk > -1)
			if ((fdes->maxblk - fdes->currentblk) < io_left)
				io_left = fdes->maxblk - fdes->currentblk;
/*
fprintf(stderr, "tran=%c buffer_blocks=%d cur=%d max=%d left=%d\n",(dtype == dREAD) ?
	'R' : 'W', buffer_blocks, fdes->currentblk, fdes->maxblk, io_left);
*/

		if (io_left < 1)
			return(0);
	}

	fprintf(stderr, "%s at %-8d  count=%d%5s\r", (dtype == dREAD) ?
		"Read " : "Write", fdes->currentblk, io_left, "");

	if (fdes->dtype == tDEVICE) {
		fdes->trackIO->iotd_Req.io_Length = io_left * TD_SECTOR;
		fdes->trackIO->iotd_Req.io_Offset = fdes->currentblk * TD_SECTOR;
		DoIO(fdes->trackIO);
		if (dtype == dREAD) {
			last_read_blocks = io_left;
		}
	} else {			/* Must be either file or stdio */
		if (dtype == dREAD) {
			if ((last_read_blocks = fread(block_buffer, TD_SECTOR, io_left,
				fdes->strIO)) == EOF)
				return(0);
		} else {				/* file write */
			if ((last_write_blks = fwrite(block_buffer, TD_SECTOR, io_left,
				fdes->strIO)) != io_left) {
				if (last_write_blks >= 0)
				    fprintf(stderr, "destination full before source exhausted.\n");
				else
				    fprintf(stderr, "failure: write did not finish normally\n");
				return(0);
			}
		}
	}

	fdes->currentblk += io_left;

	if ((fdes->maxblk > -1) && (fdes->currentblk > fdes->maxblk))
		return(0);
	else
		return(1);
}
