/* Format a floppy disk (880k drive).
 *
 * Author:	Mark R. Rinfret
 * Date:	06/28/87
 *
 * Additions:	Olaf Barthel
 *		ED Electronic Design Hannover
 *
 * 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)
 *
 * 04/14/89 -O.B-
 *
 *		Added forward declarations, SendPacket, Inhibit, new
 *		messages, format verification and lots of other
 *		featuress.
 *
 * 08/26/87 -MRR-
 *
 *		Modified FormatDisk to delay 3 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 <libraries/dosextens.h>
#include <devices/trackdisk.h>
#include <exec/memory.h>
#include <stdio.h>

extern void		*AllocMem();
extern struct MsgPort	*CreatePort();
extern struct MsgPort	*DeviceProc();
extern struct IOExtTD	*CreateExtIO();

/* The NUMTRACKS definition is considered being obsolete in the
 * 1.2/1.3 headerfiles. If there is still somebody who has the
 * definition in his trackdisk.h, let's try to avoid a compiler
 * warning.
 */

#ifndef NUMTRACKS
#define NUMTRACKS	(80 * 2)
#endif NUMTRACKS

#define MAX_NAME	30
#define TRACKSIZE	NUMSECS * TD_SECTOR

/* These little "quickies" disable the CTRL-C abort function.
 * They work on the Aztec 'C' only and were borrowed from
 * Matt Dillon's Shell.
 */

BreakCheck()
{
	return(SetSignal(0,0) & SIGBREAKF_CTRL_C);
}

BreakReset()
{
	SetSignal(0,SIGBREAKF_CTRL_C);
}

Chk_Abort()
{
	return(0);
}

/* Format a floppy disk - hardwired for the 3.5" 880k floppy drives.
 * Called with:
 *		drivename:	device name (DF0, etc.)
 *		name:		new volume name
 *		options:	1 to shut up about formatting status
 *				2 to verify formatting
 *				3 for both
 * Returns:
 *		Zero on success, 1 on failure
 * Note:
 *		I 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.
 */

LONG
FormatDisk(DriveID,DiskName,Options)
char *DriveID,*DiskName;
LONG Options;
{
	LONG CheckSum,DosWord = 0,DiskUnit;
	char *DOS_ID = "DOS",*VolumeName,*DiskBuffer;

	struct MsgPort *DiskPort = NULL;
	struct IOExtTD *DiskReq = NULL;

	ULONG DiskChangeCount,*DiskBlock;
	USHORT Status = 0,i,Track;

	if((DiskUnit = (DriveID[2] - '0')) < 0 || DiskUnit >= NUMUNITS)
	{
		printf("Invalid drive specification!\n");

		Status = ERROR_INVALID_COMPONENT_NAME;
		goto CleanUp;
	}

	if(!(DiskBuffer = (char *)AllocMem(TRACKSIZE,MEMF_CHIP)))
	{
		printf("We are out of memory!\n");

		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;

	for(i = 0 ; i < TRACKSIZE / 4 ; ++i)
		DiskBlock[i] = (DosWord | (i & 0xFF));

	if((DiskPort = CreatePort((char *)NULL,NULL)) == NULL)
	{
		printf("I can't create a port!\n");

		Status = ERROR_NO_FREE_STORE;
		goto CleanUp;
	}

	if(!(DiskReq = (struct IOExtTD *)CreateExtIO(DiskPort,sizeof(struct IOExtTD))))
	{
		printf("We are out of memory!\n");

		Status = ERROR_NO_FREE_STORE;
		goto CleanUp;
	}

	if(Status = OpenDevice(TD_NAME,DiskUnit,DiskReq,0))
	{
		printf("OpenDevice failed!\n");

		goto CleanUp;
	}

	/* Say trackdisk, is there really a disk in the
	 * drive?
	 */

	DiskReq -> iotd_Req . io_Command = TD_CHANGESTATE;
	DoIO(DiskReq);

	if(DiskReq -> iotd_Req . io_Actual)
	{
		printf("No disk in the drive!\n");

		Status = ERROR_NO_DISK;
		goto CleanUp;
	}

	/* And what about the write protect tab, is it set or
	 * unset?
	 */

	DiskReq -> iotd_Req . io_Command = TD_PROTSTATUS;
	DoIO(DiskReq);

	if(DiskReq -> iotd_Req . io_Actual)
	{
		printf("The disk is write protected!\n");

		Status = ERROR_DISK_WRITE_PROTECTED;
		goto CleanUp;
	}

	if(Status = Inhibit(DriveID,1))
	{		
		printf("Unable to inhibit drive!\n");

		goto CleanUp;
	}

	/* Turn the console cursor off. */

	printf("\x9B0 p\n");

	if(Options & 1 << 0)
		printf("\x9B1\x41Formatting quietly...\n");

	/* 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;

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

	for(Track = 0 ; Track < NUMTRACKS ; ++Track)
	{
		if(!(Options & 1 << 0))
			printf("\x9B1\x41Formatting cylinder %d head %d, %d to go \n",Track / 2,Track % 2,(159 - Track) /2);

		if(BreakCheck())
		{
			printf("\x9B1\x41\x9B\x4D*** Formatting aborted!\7\n");
			Status = -1;

			goto CleanUp;
		}

		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 = DiskReq -> iotd_Req . io_Error)
		{
			printf("\x9B1\x41\x9B\x4DFormatting error Cylinder %d.\n",Track / 2);

			goto CleanUp;
		}

		if((Options & 1 << 1) == 1 << 1)
		{
			/* For some very mysterious reasons re-reading the formatted
			 * track affects the contents of the root-block. Instead
			 * of trying to CMD_READ the track we CMD_WRITE it. The
			 * contents of the formatted block and the block to be
			 * written are the same anyway. The error messages should also
			 * be the same as with CMD_READ.
			 */

			if(!(Options & 1 << 0))
				printf("\x9B1\x41Verifying  cylinder %d head %d, %d to go \n",Track / 2,Track % 2,(159 - Track) /2);

			DiskReq -> iotd_Req . io_Command= CMD_WRITE;
			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 = DiskReq -> iotd_Req . io_Error)
			{
				printf("\x9B1\x41\x9B\x4DVerify error Track: %d\n",Track / 2);

				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 */

	VolumeName = (char *)&DiskBlock[108];

	/* Convert input name to BSTR. */

	*VolumeName = strlen(DiskName);

	for(i = 0 ; i < *VolumeName ; ++i)
		*(VolumeName + 1 + i) = *(DiskName + 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= CMD_WRITE;
	DiskReq -> iotd_Req . io_Length	= TD_SECTOR;
	DiskReq -> iotd_Req . io_Offset	= TD_SECTOR * 880;

	DoIO(DiskReq);

	if(Status = DiskReq -> iotd_Req . io_Error)
	{
		printf("\x9B1\x41\x9B\x4DError writing root block!\n");
		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	= 881 * TD_SECTOR;

	DoIO(DiskReq);	/* write out the bitmap */

	if(Status = DiskReq -> iotd_Req . io_Error)
	{
		printf("\x9B1\x41\x9B\x4DError writing bitmap!\n");
		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(DriveID,0);		/* Enable disk validator. */

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

CleanUp:if(Status)	/* Just in case the drive has not been de-inhibited. */
	{
		Inhibit(DriveID,0);		/* Enable disk validator. */

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

	CloseDevice(DiskReq);

	if(DiskBuffer)
		FreeMem(DiskBuffer,TRACKSIZE);

	if(DiskReq)
		DeleteExtIO(DiskReq,sizeof(struct IOExtTD));

	if(DiskPort)
		DeletePort(DiskPort);

	/* Turn the console cursor back on. */

	printf("\x9B p");

	return(Status);
}

/* Inhibit() tells DOS not to enable the Disk-Validator while we are
 * trying to format the disk.
 */

LONG
Inhibit(DriveID,Code)
char *DriveID;
LONG Code;
{
	struct MsgPort *HandlerTask;
	LONG Arg[2];

	if(!(HandlerTask = (struct MsgPort *)DeviceProc(DriveID))) 
		return(1);

	/* Do you remember DOS expects -1 to be true instead of
	 * TRUE (1)?
	 */

	Arg[0] = - Code;

	return(!SendPacket(HandlerTask,ACTION_INHIBIT,Arg,1));
}

/* SendPacket() by Andy Finkel, Peter Lindsay & Carolyn Scheppner
 * sends Packet and arguments to a File System's MsgPort.
 */

LONG 
SendPacket(IDPort,Type,Args,NumArgs)
struct MsgPort *IDPort;
LONG Type,Args[],NumArgs;
{
	struct MsgPort		*ReplyPort;
	struct StandardPacket	*Packet;

	LONG Count,*PacketArgs,Result = NULL;

	if(!(ReplyPort = (struct MsgPort *)CreatePort((char *)NULL,NULL)))
		return(NULL);

	Packet = (struct StandardPacket *)AllocMem(sizeof(struct StandardPacket),MEMF_PUBLIC | MEMF_CLEAR);

	if(Packet)
	{
		Packet -> sp_Msg . mn_Node . ln_Name	= (char *)&(Packet -> sp_Pkt);
		Packet -> sp_Pkt . dp_Link		= &(Packet -> sp_Msg);
		Packet -> sp_Pkt . dp_Port		= ReplyPort;
		Packet -> sp_Pkt . dp_Type		= Type;

		PacketArgs = &(Packet -> sp_Pkt . dp_Arg1);

		for(Count = 0 ; (Count < NumArgs) && (Count < 7) ; Count++)
			PacketArgs[Count] = Args[Count];

		PutMsg(IDPort,Packet);
		WaitPort(ReplyPort);

		GetMsg(ReplyPort);

		Result = Packet -> sp_Pkt . dp_Res1;
		FreeMem(Packet,sizeof(struct StandardPacket));
	}

	DeletePort(ReplyPort);
	return(Result);
}

/* main() introduced for debugging purposes only. */

void
main(argc,argv)
long argc;
char *argv[];
{
	LONG DiskUnit,Error;
	LONG i,Options = 0;

	if(!argc)
		exit(RETURN_FAIL);

	for(i = 0 ; i < strlen(argv[0]) ; i++)
		argv[0][i] = toupper(argv[0][i]);

	if(*argv[1] == '?' || argc == 1)
	{
		printf("[33m[1mFormatter[31m[0m © 1987 by Mark R. Rinfret, updated by Olaf 'Olsen' Barthel.\n\n");

		printf("Usage is: %s {[33mDF0:[31m|[33mDF1:[31m|[33mDF2:[31m|[33mDF3:[31m} [-[33mQV[31m]\n",argv[0]);
		printf("          ");

		for(i = 0 ; i < strlen(argv[0]) ; i++)
			putchar(' ');

		printf(" [33mQ[31m = Quiet, no track/side messages, [33mV[31m = Verify formatting\n");

		exit(RETURN_OK);
	}

	if(*argv[2] == '-')
	{
		for(i = 1 ; i < strlen(argv[2]) ; i++)
		{
			if(toupper(argv[2][i]) == 'Q')
				Options |= 1 << 0;

			if(toupper(argv[2][i]) == 'V')
				Options |= 1 << 1;
		}
	}

	for(i = 0 ; i < strlen(argv[1]) ; i++)
		argv[1][i] = toupper(argv[1][i]);

	if(strlen(argv[1]) != 4 || (strncmp(argv[1],"df",2) && strncmp(argv[1],"DF",2)))
	{
bad_drive:	printf("%s: Drive name may only be [33mDF0:[31m through [33mDF3:[31m!\n",argv[0]);
		exit(RETURN_ERROR);
	}

	if((DiskUnit = (argv[1][2] - '0')) < 0 || DiskUnit > 3)
		goto bad_drive;

	printf("Insert disk in [33m%s[31m, then hit [33mRETURN[31m",argv[1]);

	while(getchar() != '\n');

	if(Error = FormatDisk(argv[1],"Blank",Options))
	{
		printf("%s: [33mFormat failed[31m, Error code = %d.\n",argv[0],Error);
		BreakReset();

		exit(RETURN_FAIL);
	}
	else
		printf("\x9B1\x41\x9B\x4D%s succeeded!\n",argv[0]);

	BreakReset();

	exit(RETURN_OK);
}
