/* Format a floppy disk (880k drive).
 * Author:		Mark R. Rinfret
 * Date:		06/28/87
 * Description:
 *		This set of routines may be incorporated into a program which
 *	has need of formatting a floppy disk.  I wrote it to support my
 *	hard disk backup utility.
 *
 * History:		(most recent change first)
 *
 * 08/26/87 -MRR- Modified FormatDisk to delay 5 seconds after
 *				  uninhibiting the drive.  This should give enough time
 *                for the validator to do its thing and prevent the
 *				  "Insert disk..." requester from rearing its ugly head
 *                if the application attempts to access the disk as
 *                soon as we return.
 */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/interrupts.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/tasks.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <devices/trackdisk.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <functions.h>

extern struct IOExtTD *CreateExtIO();

static int CkIOErr();

/* #define DEBUG */
#define MAX_NAME	30L
#define TD_WRITE	CMD_WRITE
#define TRACKSIZE	NUMSECS * TD_SECTOR


/* Format a floppy disk - hardwired for the 3.5" 880k floppy drives.
 * Called with:
 *		drivename:	device name (DF0, etc.)
 *      name:		new volume name
 * Returns:
 *		Zero on success, 1 on failure
 * Note:
 *		This routine does not currently perform a verification, as
 *		recommended by the RKM.  Perhaps later...
 *		I also discovered that there's some erroneous crap in 
 *		"The Amiga Programmer's Workbook, Vol. II", by 
 *		Eugene P. Mortimore.  On page 339, he states that only 512
 *		bytes of track data are required for formatting.  The RKM
 *		correctly states that a "track's worth of data" is required.
 *		It took some playing with DiskEd to discover this error.
 */

int 
FormatDisk(drivename,name)
	char *drivename; char *name;
{
	long checksum;
	char *dos_id = "DOS";
	long dosword = 0;
	SHORT error;
	struct MsgPort *diskport = NULL;
	struct IOExtTD *diskreq = NULL;
	ULONG diskchangecount;
	struct Process *myprocess;
	struct Window *mywindow;
	USHORT status = 0, i, retry, track;
	int unit; 
	char *volname;

	char *diskbuffer;
	ULONG *diskblock;			/* alias for diskbuffer, ULONG type */

	if (strlen(name) >= MAX_NAME) {
#ifdef DEBUG
		printf("Disk name is too long!\n");
#endif
		status = ERROR_INVALID_COMPONENT_NAME;	
		goto cleanup;
	}

	if ((unit = (drivename[2]-'0')) < 0 || unit >= NUMUNITS) {
#ifdef DEBUG
		printf("FormatDisk: invalid drive specification!\n");
#endif
		status = ERROR_INVALID_COMPONENT_NAME;
		goto cleanup;
	}

	if (!(diskbuffer = 
		AllocMem((long) TRACKSIZE, MEMF_PUBLIC | MEMF_CHIP))) {
		status = ERROR_NO_FREE_STORE;
		goto cleanup;
	}

	/* Store DOS "magic word" in disk block to be written during
	 * formatting. 
	 */
	diskblock = (ULONG *) diskbuffer;/* we'll need this later */
	for (i = 0; i < 3; ++i)
		dosword = (dosword << 8) | dos_id[i];
	dosword = dosword << 8;

#ifdef DEBUG
	printf("dosword is %lx\n",dosword);
#endif
	for (i = 0; i < TRACKSIZE / 4; ++i)
		diskblock[i] = (dosword | (long) (i & 0xff));

	if ((diskport = CreatePort(0L, 0L)) == NULL) {
#ifdef DEBUG
		printf("FormatDisk can't create port!\n");
#endif
		status = 1;						/* is there a better error code? */
		goto cleanup;
	}

	if (!(diskreq = (struct IOExtTD *) 
		CreateExtIO(diskport, (long) sizeof(struct IOExtTD)))) {
		status = 1;
		goto cleanup;
	}

	if (status = OpenDevice(TD_NAME, (long) unit, diskreq, 0L)) {
#ifdef DEBUG
		printf("FormatDisk: OpenDevice error: %d\n",error);
#endif
		goto cleanup;
	}

	if (status = Inhibit(drivename, 1)) {
#ifdef DEBUG
		printf("FormatDisk: unable to inhibit drive!\n");
#endif
		goto cleanup;
	}

/* Get the current disk change count.  This allows the trackdisk
 * driver to detect unwanted disk changes later on.
 */

	diskreq->iotd_Req.io_Command = TD_CHANGENUM;
	DoIO(diskreq);

/* Save a copy of the disk change count. */

	diskchangecount = diskreq->iotd_Req.io_Actual;

#ifdef DEBUG
	printf("Current disk change count is %ld\n", diskchangecount);
#endif


	/* Format the disk, one track at a time. */

	for (track = 0; track < NUMTRACKS; ++track) {
		diskreq->iotd_Req.io_Command = TD_FORMAT;
		diskreq->iotd_Req.io_Flags = 0;
		diskreq->iotd_Req.io_Data = (APTR) diskbuffer;
		diskreq->iotd_Count = diskchangecount;
		diskreq->iotd_Req.io_Length = NUMSECS * TD_SECTOR; 
		diskreq->iotd_Req.io_Offset = track * NUMSECS * TD_SECTOR;
		DoIO(diskreq);

		if (status = CkIOErr(diskreq,"Formatting error")) {
#ifdef DEBUG
			printf("  Track: %d\n",track);
#endif
			goto cleanup;
		}
	}

	/* Now comes some real KLUDGING.  Fill in the root block and the
	 * first hash block.  The information for this was gathered from
	 * the "AmigaDos Technical Reference Manual" and some sleuthing
	 * with DiskEd.
	 */

	for (i = 0; i < 128; ++i)
		diskblock[i] = 0;

	diskblock[0] = 2;			/* T.SHORT (type) */
	diskblock[3] = 128 - 56;	/* hashtable size */
	diskblock[78] = 0xffffffff; /* BMFLAG */
	diskblock[79] = 881;		/* first bitmap block */
	DateStamp(&diskblock[105]);	/* volume last altered date/time */
	DateStamp(&diskblock[121]); /* volume creation date/time */
	volname = (char *) &diskblock[108];
	/* convert input name to BSTR */
	*volname = strlen(name);
	for (i = 0; i < *volname; ++i)
		*(volname + 1 + i) = *(name + i);

	diskblock[127] = 1;			/* ST.ROOT (secondary type) */

	checksum = 0;
	for (i = 0; i < 128; ++i)
		checksum += diskblock[i];

	diskblock[5] = - checksum;

	/* Write the root block out to the disk. */

	diskreq->iotd_Req.io_Command = TD_WRITE;
	diskreq->iotd_Req.io_Length = TD_SECTOR;
	diskreq->iotd_Req.io_Offset = TD_SECTOR * 880L;
	DoIO(diskreq);
	if (status = CkIOErr(diskreq, "Error writing root block")) {
		goto cleanup;
	}

	/* Write the first bitmap block. */

	for (i = 0; i < 56; ++i)
		diskblock[i] = 0xffffffff;

	for (i = 56; i < 128; ++i)
		diskblock[i] = 0;

	diskblock[0] = 0xc000c037;	/* hint: x37 = 55 (last word of map?) */
	diskblock[28] = 0xffff3fff; /* blocks 880, 881 used */
	diskblock[55] = 0x3fffffff; /* blocks 1760, 1761 used? */

	diskreq->iotd_Req.io_Length = TD_SECTOR;
	diskreq->iotd_Req.io_Offset = 881L * TD_SECTOR;
	DoIO(diskreq);					/* write out the bitmap */
	if (status = CkIOErr(diskreq, "Error writing bitmap")) {
		goto cleanup;
	}

	diskreq->iotd_Req.io_Command = ETD_UPDATE;
	diskreq->iotd_Req.io_Flags = 0;
	DoIO(diskreq);

	/* Turn the disk motor off. */

	diskreq->iotd_Req.io_Command = TD_MOTOR;
	diskreq->iotd_Req.io_Length = 0;
	DoIO(diskreq);
	Inhibit(drivename, 0);				/* enable disk validator */

	Delay(3L * TICKS_PER_SECOND);		/* Give it a chance */

cleanup:
	CloseDevice(diskreq);
	if (diskbuffer) FreeMem(diskbuffer, (long) TRACKSIZE);
	if (diskreq) DeleteExtIO(diskreq, (long) sizeof(*diskreq));
	if (diskport) DeletePort(diskport);
	return status;
}

/* Check the disk request block for an error code.  If an error
 * occurred, print the argument string.
 * Called with:
 *		req:	pointer to I/O request structure
 *		msg:	error message string
 * Returns:
 *		error code from request structure
 */
static int CkIOErr(req, msg)
	struct IOStdReq *req; char *msg;
{
	register int code;

	if (code = req->io_Error) {
#ifdef DEBUG
		printf("%s, code: %d\n",msg,code);
#endif
	}
	return code;
}

#ifdef DEBUG
main(argc, argv)
	int argc; char *argv[];
{
	char *diskname;
	char *volname;

	int unit;

	if (argc < 3)
		volname = "GoodJob!";
	else
		volname = argv[2];

	if (argc < 2)
		diskname = "DF1:";
	else {
		diskname = argv[1];
		if (strlen(diskname) != 4 || 
			(strncmp(diskname,"df",2) && strncmp(diskname,"DF",2))) {
bad_drive:
			printf("Drive name may only be df0: through df3:!\n");
			exit(1);
		}
		if ((unit = (diskname[2] - '0')) < 0 || unit > 3)
			goto bad_drive;

	}
	printf("Insert disk in %s, then hit return\n",diskname);
	while (getchar() != '\n');
	if (FormatDisk(diskname,volname))
		printf("FormatDisk failed\n");
	else {
		printf("FormatDisk succeeded\n");
	}
}
#endif
