/*
** This is a further modification to the Bformat program developed by
** Bob Bush.  This generation now allows the formatting of any device
** using either file system.  It still maps out bad sectors, but does it
** a sector at a time instead of a track at a time.  This is particularly
** useful for the formatting of hard disks.
**
** The command format is:
**    BFormat DRIVE <drive> NAME <name> [FFS|NOFFS] [FAST|QUICK|SLOW] [QuIeT]
** please note that the format is strictly enforced.  All field must be 
** specified in the specified order.
*/

/* bformat.c */

/*
   When you format a diskette dos grabs 2 blocks for its own use.
   Block 880 is the Root block. Block 881 (normally on a fresh format) is
   the bitmap which in itself indicates the blocks that are available for use
   or currently in use. Generally, a '1' bit means the corresponding block
   is available. A '0' bit means that the block is in use. This sounds
   relatively simple but the AmigaDos implementation needs some 
   explanation.
   
   AmigaDos Block Allocation Map (512 bytes -or- 128 longwords)
   ------------------------------------------------------------
   LongWord   Usage
   --------   -----
      0       location of the block checksum
      1       first bitmap allocation longword, which breaks down as follows:

     hi-order bit (31)              lo-order bit (0)
    /                              /
   |                              |
   11111111111111111111111111111111 (32 bit longword)
    \\                           \\\
     \\_block #32                 \\\_block #2
      \_block #33                  \\_block #3
                                    \_block #4

 (The above example indicates that blocks 2 thru 33 are available for use)
   
   You might wonder why the bitmap starts at block #2 and not block #0?
   I suppose since the first 2 blocks of every disk are not available for
   file storage, AmigaDos simply chooses to ignore the fact that they exist.
   Another reason could be that if the bitmap included blocks zero and one,
   it might be too easy to figure out.. Hmmmmm..
   Actually I think it corresponds to the well documented (ha) Mountlist
   parameter named 'Reserved' which can be used to segment a hard disk into
   more than one logical drive  If you look at it in that light, the first
   bit in the bitmap corresponds to a block number which is the same as the 
   number of 'Reserved' blocks in the Mountlist file.. Have yet to verify
   this on my hard disk but it sounds logical.  ( Note from Tom Nery - 
   Unfortunately the 2 block offset is not related to the 'Reserved'
   parameter, It was a good guess though).

   Inany case, the remainder of the bitmap looks the same e.g. the next
   longword (lo order bit) starts the map for block #34 -- and so on, until
   the map terminates at block #1760...
   
   With the above info, you should be able to figure out how this program
   works.
*/

/* 
   Date      Programmer  Modification
 =========   ==========  =============================================
 91-Feb-10   T.A.Nery    Removed support for '-C' option but added 
                         support for all mounted disk type devices,
                         including FFS. Also added support for various
                         levels of disk verificaition.
 88-Aug-18   R.W.Bowers  Created this header, added Execute "code" above
 88-Aug-15   R.W.Bowers  Fixed a "minor" BUG!  Using the -C[heck]
				option "erased" the entire disk!!
 88-Aug-14   R.W.Bowers  Changed reporting from "Track xx" to
				"Cyl yy, Head z"
 88-Jul-10   R.W.Bowers  Changed # tracks from 160 to "Tracks"
 88-Jul-10   R.W.Bowers  Compiles under Lattice 4.0 or, with -dMANX,
				under MANX
 88-Mar-28	Bob Bush  cpu-73105,1332 - compiled with Lattice 4.0 
*/

#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 "intuition/intuition.h"
#include "libraries/dos.h"
#include "libraries/dosextens.h"
#include "libraries/filehandler.h"
#include "stdio.h"
#include <string.h>

#define	DONE	0
#define CONT	1

#define	FAILURE	0
#define	SUCCESS	1

#define	OLD	0x444f5300L
#define	FFS	0x444f5301L

#define FAST	1
#define QUICK	2
#define SLOW	3

#define	NEXT_DEVICE(device)    ((struct DosList *) BADDR(device->dol_Next))

struct	INFO {
	char	Drive[80];	/* Drive Name (eg. df0:) 		*/
	char	Label[80];	/* Disk Label (eg. Empty)		*/
	char	Device[80]; 	/* Device Name (eg. trackdisk.device)	*/
	int	Unit;		/* Device's unit number, per mountlist	*/
	long	Sectors;	/* Number of Sectors per track		*/
	long	Surfaces;	/* Number of Surfaces 			*/
	long	LoCyl;		/* Starting Cylinder number		*/
	long	HiCyl;		/* Ending Cylinder number		*/
	long	FileSystem;	/* File system being used		*/
				/*	0x444f5300 - Old File System	*/
				/*	0x444f5301 - Fast File System	*/
	};

extern struct DosLibrary *DOSBase;

struct	INFO	Info;

BOOL formatting = FALSE;

ULONG	bitmap_size;	/* Number of bytes allocated to init the bitmaps */
long	*buffer,*bitmap,*track = NULL;
short	abort;
long	TestPattern[4] ={0xFFFFFFFFL, 0xAAAAAAAAL, 0x55555555L, 0x00000000L};
struct	IOStdReq	*mreq  = NULL;
struct	MsgPort		*mport,*dosport = NULL;
struct	StandardPacket	*dpacket;		/* packet for dos */
int	Verify_mode;
int	Quiet;


/*
** Convert a BPCL string to a 'C' style string
*/
int convert_bstr(out,in,colon)
	BYTE	*in;
	char	*out;
	int	 colon;
{
	if ((*in >= 59) || (*in == 0))
		return(FALSE);

	memcpy(out, in+1, *in);
	if (colon == TRUE)
	{
		out[*in] = ':';
		out[(*in)+1] = '\0';
	}
	else
		out[*in] = '\0';

	return(TRUE);
}


void write_block(block,buff)
	long	block;
	long	*buff;
{
	mreq->io_Length = 512;
	mreq->io_Data = (APTR)buff;
	mreq->io_Command = CMD_WRITE;
	mreq->io_Offset = block * 512;
	DoIO(mreq);
}


void Update()
{
	mreq->io_Length = 1;
	mreq->io_Command = CMD_UPDATE;
	DoIO(mreq);
}


int Clear()
{
	mreq->io_Length = 0;
	mreq->io_Command = CMD_CLEAR;
	DoIO(mreq);
	return(0);
}


/*
** Check for the existence of the specified device.  If found, set the 
** default format parameters for the specific mountlist entry.
*/

int check_dev(dev,drive)
	struct DeviceNode *dev;
	char		   drive[];
{
	struct	FileSysStartupMsg *startup;
	char	Name[80];
	ULONG	*dosenv;


	if (convert_bstr(Name,BADDR(dev->dn_Name),TRUE) == TRUE)
	{
		if (stricmp(Name,drive) == 0)
		{
			strcpy(Info.Drive,Name);
		}
		else
			return(FAILURE);
	}
	else
		return(FAILURE);

	startup = (struct FileSysStartupMsg *)(BADDR(dev->dn_Startup));
	Info.Unit = startup->fssm_Unit;

	if (convert_bstr(Name,BADDR(startup->fssm_Device),FALSE) == TRUE)
	{
		strcpy(Info.Device,Name);
	}

	dosenv = (ULONG *) (BADDR(startup->fssm_Environ));

	Info.Sectors  = dosenv[5];
	Info.Surfaces = dosenv[3];
	Info.LoCyl    = dosenv[9];
	Info.HiCyl    = dosenv[10];

	Info.FileSystem = OLD;
	if (dosenv[16] == FFS)
		Info.FileSystem = FFS;

	return(SUCCESS);
}


void cmd_error()
{
printf("\nCommand Format Error.  Command Syntax is:\n");
printf("    BFormat DRIVE <drive> NAME <name> [FFS|NOFFS] [FAST|QUICK|SLOW] [QuIeT]\n");
	exit(10);
}


int check_struct(dev,drive)
	struct DosList *dev;
	char		drive[];
{
	if ((dev->dol_Type) == DLT_DEVICE)
		if (check_dev(dev,drive) == SUCCESS)
			return(DONE);
	
	return(CONT);
}
	

int verify(drive)
	char	drive[];
{
	struct RootNode	*rootNode;
	struct DosInfo	*dosInfo;
	struct DosList	*firstDevice, *device;

	rootNode    = (struct RootNode   *) DOSBase->dl_Root;
	dosInfo     = (struct DosInfo    *) BADDR(rootNode->rn_Info);
	firstDevice = (struct DosList *) BADDR(dosInfo->di_DevInfo);

	for (device = firstDevice ; device ; device = NEXT_DEVICE(device))
	{
		if (check_struct(device,drive) == DONE)
			return(SUCCESS);
	}
	printf("\nDrive %s Not Found", drive);
	return(FAILURE);
}
	

int chk_params(cnt,args)
	int	 cnt;
	char	*args[];
{
	int	 i;

/* Check if the correct number of arguments */
	if (cnt < 5)
		return(FAILURE);

/* Make sure all keywords are valid */
	if (stricmp(args[1],"DRIVE"))
		return(FAILURE);
	else if (verify(args[2]) != SUCCESS)
		return(FAILURE);

	if (stricmp(args[3],"NAME"))
		return(FAILURE);
	else
		strcpy(Info.Label,args[4]);

	Verify_mode = QUICK;
	Quiet       = FALSE;
	for (i = 5 ; i < cnt ; ++i)
	{
		if (stricmp(args[i],"FFS") == 0)
			Info.FileSystem = FFS;
		else if (stricmp(args[i],"NOFFS") == 0)
			Info.FileSystem = OLD;
		else if (stricmp(args[i],"SLOW") == 0)
			Verify_mode = SLOW;
		else if (stricmp(args[i],"QUICK") == 0)
			Verify_mode = QUICK;
		else if (stricmp(args[i],"FAST") == 0)
			Verify_mode = FAST;
		else if (strcmp(args[i],"QuIeT") == 0)
			Quiet = TRUE;
		else
			return(FAILURE);
	}

	return(SUCCESS);
}


void inhibit(t)
	long	t;	/* true or false */
{
	struct MsgPort *handler;
	struct StandardPacket *packet = dpacket ;

	handler = (struct MsgPort *)DeviceProc(Info.Drive);
	if (!((handler == NULL) || (dosport == NULL)))
	{	
		packet->sp_Msg.mn_Node.ln_Name = (char *)&(packet->sp_Pkt);
		packet->sp_Pkt.dp_Link = &(packet->sp_Msg);
		packet->sp_Pkt.dp_Port = dosport;
		packet->sp_Pkt.dp_Type = ACTION_INHIBIT;
		packet->sp_Pkt.dp_Arg1 = t;
		PutMsg(handler, packet);
		WaitPort(dosport);
		GetMsg(dosport);
	}
}


void cleanup(err)
	int	err;
{
	inhibit(FALSE);
	if(mreq)
	{
		CloseDevice(mreq);	
		DeleteStdIO(mreq);
    	}

	if(mport)
		DeletePort(mport);
	if(dosport)
		DeletePort(dosport);
	if(dpacket)
		FreeMem(dpacket,sizeof(struct StandardPacket));
	if(bitmap)
		FreeMem(bitmap,bitmap_size);
	if(buffer)
		FreeMem(buffer,512);
	if(track)
		FreeMem(track,512 * Info.Sectors);
	exit(0);
}	


/* handle ctrl-c, ctrl-d aborts */
int brk()
{
	int	c;
	char	buf[20];

	if(formatting)
	{
		printf("\n*** [7;43mBreak[0;40m ***");
		printf("\nDo you really want to abort (y/n)? ");
		gets(buf); c = buf[0];
		if (c == 'y' || c == 'Y')
		{
		    printf(
			"\n[43mWarning!  [40mDisk may be unusable.\n\n");
		    cleanup(0);  /* Was cleanup(999); */
       		}
		else
			abort = 0;

       		return(0);
     	}
}


void format_track(tnum)
	int	tnum;
{

	mreq->io_Length = 512 * Info.Sectors;
	mreq->io_Data = (APTR)track;
	mreq->io_Command = TD_FORMAT;
	mreq->io_Offset = tnum * Info.Sectors * 512;
	DoIO(mreq);
}


void write_track(tnum)
	int	tnum;
{
	mreq->io_Length = 512 * Info.Sectors;
	mreq->io_Data = (APTR)track;
	mreq->io_Command = CMD_WRITE;
	mreq->io_Offset = tnum * Info.Sectors * 512;
	DoIO(mreq);
}


void set_fbuff(buff,sectors,value)
	ULONG	buff[];
	int	sectors;
	long	value;
{
	int	j;

	for(j = 0; j < (128 * sectors);j++)
		buff[j] = value;
}


/* verify track with abort on critical tracks */
int verify_track(tnum)
	int	tnum;
{
	int	test;
	int	length;

	length = 512 * Info.Sectors;

	if (Verify_mode == SLOW)
	{
		for (test = 0 ; test < 4 ; ++test)
		{
			set_fbuff(track,Info.Sectors,TestPattern[test]);
			write_track(tnum);

			mreq->io_Length = length;
			mreq->io_Data = (APTR)track;
			mreq->io_Command = CMD_READ;
			mreq->io_Offset = tnum * Info.Sectors * 512;
			DoIO(mreq);
			if ((mreq->io_Error) || (mreq->io_Actual != length))
        			return(0);
		}
	}
	else if (Verify_mode == QUICK)
	{
		mreq->io_Length = length;
		mreq->io_Data = (APTR)track;
		mreq->io_Command = CMD_READ;
		mreq->io_Offset = tnum * Info.Sectors * 512;
		DoIO(mreq);
		if ((mreq->io_Error) || (mreq->io_Actual != length))
        		return(0);
	}
	else
	{
		mreq->io_Length = 512;
		mreq->io_Data = (APTR)track;
		mreq->io_Command = CMD_READ;
		mreq->io_Offset = tnum * Info.Sectors * 512;
		DoIO(mreq);
		if ((mreq->io_Error) || (mreq->io_Actual != 512))
        		return(0);
	}

       	return(1);
}


void chk_abort()
{
	if (SetSignal(0L, (ULONG) (SIGBREAKF_CTRL_C)) & (SIGBREAKF_CTRL_C))
		brk();
}


void validate_required()
{
	int	fstat;
	int	sel_track;

/*	chk_abort();	*/

/*
** First calculate the starting track number
*/
	sel_track = Info.LoCyl * Info.Surfaces;
/*
** Verify that track 0 is usable.
*/
	set_fbuff(track,Info.Sectors,Info.FileSystem);
	format_track(sel_track);
/*	chk_abort();	*/

	formatting = TRUE;
	if (!(fstat = verify_track(sel_track)))
	{
		printf("\n[7;43mUnable to format track #0.  ");
		printf("Disk not useable.[0;40m\n\n");
		cleanup(100);
	}
   
/*
** Now calculate the Root Block's track number
*/
	sel_track += (((Info.HiCyl - Info.LoCyl + 1) * Info.Surfaces)/2);
/*
** Verify that  root block track is usable.
*/
	format_track(sel_track);
	if (!(fstat = verify_track(sel_track)))
	{
		printf("\n[43;7mUnable to format root block.  ");
		printf("Disk not useable.[0;40m\n\n");
		cleanup(100);
   	}
}


/* mark block as allocated (in use) */
void clear_bitmap(block)
	long	block;
{
	long	lindex;

	if ((block - 2) > -1)
	{
		lindex = (block - 2) / 32;
		bitmap[lindex] = bitmap[lindex] & (~(1 << ((block-2) % 32)));
	}
}


/* mark block as available for use */
void set_bitmap(block)
	int	block;
{
	int	lindex;

	if ((block - 2) > -1)
	{
		lindex = (block - 2) / 32;
		bitmap[lindex] = bitmap[lindex] | (1 << ((block-2) % 32));
	}
}


/* mark track as available */
int set_track(tnum)
	int	tnum;
{
	int	j;

	mreq->io_Command = TD_PROTSTATUS;
	DoIO(mreq);
	if(mreq->io_Actual)
	{
		printf("Disk Write Protected!\n");
		printf("\n[43mWarning!  [40mDisk may be unusable.\n\n");
		cleanup(0);  /* Was cleanup(999); */
		return(0);
	}

	for(j = (tnum * Info.Sectors) ; 
			j < (tnum * Info.Sectors + Info.Sectors);j++)
		set_bitmap(j);
}


/* set volume name into root block (BCPL string) */
void set_name(dest,vname)
	char	*dest,*vname;
{
	*dest = (char)strlen(vname);
	dest++;
	while(*vname)
	{
		*dest = *vname;
		dest++; vname++;
	}
}


void zblock(wp) /* clear buffer to zeros */
	ULONG	wp[];
{
	int	j;

	for(j = 0; j < 128; j++)
		wp[j] = 0;
}


/* checksum= 2's complement of sum of all words in block */
ULONG check_sum(buff)
	ULONG	buff[];
{
	int	j;
	ULONG	cksum;

	cksum = 0;
	for(j = 0; j < 128; j++)
		cksum += buff[j];  /* sum all words in block */
	return(-cksum);
}


/* build initialized root block */
void init_root(wp,vname,blk)
	ULONG	wp[];
	char	*vname;
	long	blk;
{
	int	i, bit_blks;
	ULONG	temp;

	temp = 0;
	bit_blks = bitmap_size/512;
	wp[5] = 0;

	zblock(wp);

	wp[0]   = 0x02;         /* type */
	wp[3]   = 0x48;         /* hashtable size */
	wp[78]  = 0xffffffff;   /* 'true' bitmap flag */
	for (i = 1 ; i <= bit_blks ; ++i)
		wp[78+i]  = blk + i;
	DateStamp(&wp[105]);    /* datestamp */
	set_name(&wp[108],vname); /* disk name (BCPL) string */
	DateStamp(&wp[121]);    /* datestamp */
	wp[127] = 0x01;         /* secondary type = root */

	wp[5] = check_sum(wp); /* create root block checksum */

}


void init_bitmap(wp,root_block)
	ULONG	wp[];
	long	root_block;
{
	int   i, bit_blks;

	bit_blks = bitmap_size/512;

	clear_bitmap(root_block); /* set root block to allocated  */

	for (i = 1; i <= bit_blks ; ++i)
		clear_bitmap(root_block + i);
}


/*
** Inhibits dos from trying to access drive while we have control. Also puts
** up DF0:BUSY message, causes dos to re-read disk info on termination.
*/
int Checkstat()
{
	mreq->io_Command = TD_CHANGESTATE;
	DoIO(mreq);

	if(mreq->io_Actual)
	{
		printf("No Disk In Drive!\n");
		return(1);
	}
	mreq->io_Command = TD_PROTSTATUS;
	DoIO(mreq);

	if (mreq->io_Actual)
	{
		printf("Disk Write Protected!\n");
		return(1);
	}
	return(0);
}

void format_it()
{
	int	error,fstat,badcount;
	int	bytes, i, j;
	int	block_cnt, cyl_cnt;
	int	block_start, cyl, head, trk;
	char	c;
	long	*map_p, block_num, track_size;

	
	badcount    = 0;
	cyl_cnt     = Info.HiCyl - Info.LoCyl + 1;
	block_cnt   = cyl_cnt * Info.Sectors * Info.Surfaces;
	track_size  = Info.Sectors * 512;
	block_start = Info.LoCyl * Info.Sectors * Info.Surfaces;

	if (Quiet != TRUE)
	{
	   printf("\nDrive = [33m%s[31m    Name = [33m%s[31;0m\n",
						    Info.Drive, Info.Label);

	   printf("Insert Disk to be [33mFORMATTED[31;0m in drive %s.\n",
	      							Info.Drive);
	   printf("Press Return when ready: ");
	   c = getchar();
	}
	chkabort();

	if(!(mport = (struct MsgPort *)CreatePort("FLOPPY0",0)))
		cleanup(100);
	if(!(dosport = (struct MsgPort *)CreatePort("DOSPORT",0)))
		cleanup(100);
	if(!(mreq = (struct IOStdReq *)CreateStdIO(mport)))
		cleanup(100);
	if(!(dpacket=(struct StandardPacket *) 
	    AllocMem(sizeof(struct StandardPacket), MEMF_PUBLIC|MEMF_CLEAR)))
		cleanup(100);           

	if(error = OpenDevice(Info.Device,Info.Unit,mreq,0))
		cleanup(1);

	inhibit(TRUE);
/*	chk_abort();	*/

/*
** Determine how many blocks are required for bitmap, then allocate them
** Block size calculations based upon 508 byte blocks, since the first
** four bytes of each block are for checksum.
*/
	bytes = block_cnt/8;
	for (bitmap_size = 508 ; bitmap_size < bytes ; bitmap_size += 508) ;

/*
	printf("\nBitmap_size = %d Bytes or %d Blocks\n",
						bitmap_size,bitmap_size/508);
*/

	bitmap_size = bitmap_size / 508 * 512;
	if(!(bitmap = (long *)AllocMem(bitmap_size, MEMF_CLEAR | MEMF_CHIP)))
		cleanup(8);
	if(!(buffer = (long *)AllocMem(512,MEMF_CLEAR | MEMF_CHIP)))
		cleanup(8);
	if(!(track =  (long *)AllocMem(track_size,MEMF_CLEAR | MEMF_CHIP)))
		cleanup(8);

	if (Checkstat())
		cleanup(100);

/*	chk_abort();	*/
	validate_required();

	for (cyl = Info.LoCyl ; cyl <= Info.HiCyl ; ++cyl)
	{
		if (Quiet != TRUE)
			printf("Formatting Cylinder %d[31;0m\n",cyl);
		for (head = 0 ; head < Info.Surfaces ; ++head)
		{
			trk = cyl * Info.Surfaces + head;

			format_track(trk);
/*			chk_abort();	*/
		}

		if (Quiet != TRUE)
			printf("Verifying  Cylinder %d[31;0m\n",cyl);
		for (head = 0 ; head < Info.Surfaces ; ++head)
		{
			trk = cyl * Info.Surfaces + head;

			if (!(fstat = verify_track(trk)))
			{
				badcount += Info.Sectors;
			}
			else
			{
				trk = (cyl - Info.LoCyl)
						* Info.Surfaces + head;
				set_track(trk);
			}

/*			chk_abort();	*/
		}
	}
	if (Quiet != TRUE)
		printf("\n%d Sectors Allocated as Bad\n",badcount);

/* init boot/root/bitmap blocks and write to disk.. */
	set_fbuff(buffer,1,Info.FileSystem);
	write_block(block_start, buffer);
	set_fbuff(buffer,1,Info.FileSystem);
	write_block(block_start+1, buffer);

/* Root block is middle of device */
	block_num = block_cnt/2 + block_start;
	init_root(buffer,Info.Label,(long)(block_cnt/2));
/* printf("Writing Root Block, #%d\n",block_num);	*/
	write_block(block_num, buffer);

	init_bitmap(bitmap,block_cnt/2);
	for (i = 1, map_p = (long *)bitmap ;
	     			i <= (bitmap_size/512) ; ++i, map_p += 128)
	{
/* calculate checksum for each block and insert */
		for (j = 0 ; j < 127 ; ++j)
			buffer[j + 1] = map_p[j];

		buffer[0] = 0;
		buffer[0] = check_sum(buffer);

/*		printf("Writing Bitmap Block, #%d\n",block_num+i);	*/
		write_block((long)(block_num + i), buffer);
	}

	Update();
	Clear();
   
	mreq->io_Length  = 0;
	mreq->io_Command = TD_MOTOR;   /* turn off motor */
	DoIO(mreq);

	cleanup(0);
}


main(cnt,argi l;
	  );
	Clea );
	n( Ino; ++head)
		{
"p ;
	   to (ean.o*/
	DoIcnt, cyl_cn);

	rrum 2Tj)
			bufferu

main" 8ˆd\n",al)8 *|f ,tSC3f (!(&rack(
	initor>.ºh128)
Lotor */
	D ,tSCu
		{dlock and insert */
		for (j = 0 ; j  mea ));
lida,ru

c (bitmap_size x ));
lida,rmapu0= 0o"p „ru Bytes crify_t(0)*culate<, #Q(eaQ(ea*at *0)))
ck aÐ4C3f (!(&¤(
	inj  = bl = bl = bl aÐ4j(block_startn

	LId1T 0 ,();8i; bitmapap =gšngtha	iniblock10= gc0i)8 *|f e fice= bl = bl = bl aÐ4jÆoitm; + m; Rgc+ m; Rgco,;nbym(!(eeeck(bl)u8to (map_size)8 *|f  gck.. * .ºh128)
Lotock0_u#Q(eaQ(7t 

	riteišngtha