echo; /* Mar 28, 1988;  Assumes you've assigned the appropriate devices, below
LC -v -r -b -y -oObj: -iIncludes/ BFormat3.c
BLink	FROM	LIB:c.o		+
		Obj:BFormat3.o	+
	TO	Cmd:BFormat3	+
	LIBRARY	LIB:LC.Lib	+
		LIB:Amiga.Lib	+
	VERBOSE			+
	SMALLCODE		+
	SMALLDATA		+
	NODEBUG			+
	XREF	XRef:BFormat3.XRef +
	MAP	Maps:BFormat3.Map F H L O S X
	QUIT
 */

/* bformat.c */

/* simple program to format drive df0:, creating a bitmap which marks
   bad tracks as "in use". Should work for any 3.5" floppy which can hold
   a format on track 0 and 80 (Dos minimum requirements).. I never could
   figure out why Dos didn't do this all by itself.. Should you really have
   to throw out a disk that has one or two lousy tracks? */

/* I've been meaning to write this program for quite a long while but never
   took the time to figure out how AmigaDos stores/manipulates the block
   allocation map (aka bitmap). Well, finally took an evening and threw this
   together. To the best of my determination here is a description of
   the block allocation scheme (for 3.5" floppies anyway..) :

   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..

   In any 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
 * ========	==========	===============================================
   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
   4-July-88:   Fixed for Manx 3.6 and enhanced to provide for df0:-df3:, 
                and to provide more information to the user.  It ain't 
		pretty but it works!  There is no direct support for
                running this program from Workbench.  I tried to keep it
                under 10K!               Tony Solomon  PlinkID: Tony*S
   88-Mar-28	Bob Bush	cpu-73105,1332 - compiled with Lattice 4.0 
 */
/*
	Compile & Link:  cc +l bformat     ln +qcc bformat.o beep.o -lc32
 */

#ifdef MANX
#include "functions.h"
#endif
#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 "stdio.h"

struct IOStdReq   *mreq  = NULL;
struct MsgPort    *mport,*dosport = NULL;
struct StandardPacket *dpacket ;   /* packet for dos */

#define DEVICE_NAME "trackdisk.device"

long *buffer,*bitmap,*track = NULL;
short abort,abt =0;
static int unit =0;
char 	diskname[78],drive[30];
char	Device[60], Volume[60];
#ifdef MANX
extern void beep();
#else
beep(){}
#endif
_wb_parse(){}
extern struct DosLibrary *DOSBase;

BOOL formatting = FALSE;
BOOL correct    = FALSE;

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

    if(formatting) {
       beep();   
       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);
     }
}

main(argc,argv)
int argc;
char *argv[];

{
   int	Tracks	= 160;
   static int error,fstat,j,c,badcount;


   badcount = 0;

   printf("[43;7mBFormat [40mver3.0 By Bob Bush, Tony Solomon & Rick Bowers[43;0m\n");

   if(argc < 2) {
      beep();
      usage_err(0,argv[0]);
      }

   strcpy(drive,argv[1]);
   strcpy(diskname,argv[2]);

   if (GetVolID(drive, Device, Volume) == 0) {
      printf("Device \"%s\" is not mounted.\n", drive);
      cleanup(100);
   }

   if(!strcmp(diskname,"-c") || !strcmp(diskname,"-C")){
      correct = TRUE;
   if (strlen(Volume) == 0)
      strcpy(diskname,"EMPTY");
   else
      strcpy(diskname, Volume);
   }

   if(strlen(diskname) == 0) strcpy(diskname,"Empty");

   if(drive[2] == '0') unit = 0;
	else if(drive[2] == '1') unit =1;
	else if(drive[2] == '2') unit =2;
	else if(drive[2] == '3') unit =3;
	else if(drive[0] == '0') unit =0;
	else if(drive[0] == '1') unit =1;
	else if(drive[0] == '2') unit =2;
	else if(drive[0] == '3') unit =3;
        else usage_err(1,argv[0]);

   while((drive[0] != 'd' && drive[0] != 'D') ||
	 (drive[1] != 'F' && drive[1] != 'f')) usage_err(1,argv[0]);

   printf("\nDrive = [33m%s[31m    Name = [33m%s[31;0m\n",
	  Device, diskname);

   if(correct)
      {
      printf("Insert Disk to be [33mCORRECTED[31;0m in drive %s.\n",
	     Device);
      }
   else
       printf("Insert Disk to be [33mFORMATTED[31;0m in drive %s.\n",
	      Device);
   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(DEVICE_NAME,unit,mreq,0))
	cleanup(1);

   inhibit(TRUE);
   chkabort();
   if(!(bitmap = (long *)AllocMem(512,MEMF_CLEAR | MEMF_CHIP)))
      cleanup(8);
   if(!(buffer = (long *)AllocMem(512,MEMF_CLEAR | MEMF_CHIP)))
      cleanup(8);
   if(!(track = (long *)AllocMem(512 * 11,MEMF_CLEAR | MEMF_CHIP)))
      cleanup(8);

   formatting = TRUE;

   Checkstat();
   if(abt) cleanup(100);

   if(!correct){
	chkabort();
	validate_required();
	}

/* format entire disk, building bitmap as we go.. */

   if(correct) {
       for(j = 0; j < Tracks; j++) {
           printf("Verify Cyl %2ld, Head %1ld\n", j/2, j%2);
           if(!(fstat = verify_track(j))) {
             beep();
	     printf("[33mCyl %d, Head %d[31m <-- [7;43mBad Track!  ",
		    j/2, j%2);
	     printf("Allocated as \"in use\"..[0;40m\n");
	     set_track(j);
	     badcount += 11;
             }
	   chkabort();
       } /* for */
    } /* if correct */
    else {
       for(j = 0; j < Tracks; j++) {
           printf("Formating Cyl [33m%2ld[31;0m, Head [33m%1ld[31;0m\n", j/2, j%2);
           format_track(j);
	   chkabort();
           printf("Verifying Cyl [33m%2ld[31;0m, Head [33m%1ld[31;0m\n", j/2, j%2);
           if(!(fstat = verify_track(j))) {
	     beep();
	     printf("[33mCyl %d, Head %d[31m <--[7;43m Bad Track!  ",
		    j/2, j%2);
	     printf("Allocated as \"in use\"..[0;40m\n");
	     badcount += 11;
             }
	   else
	     set_track(j);
	   chkabort();
	}
    }

/* init root/bitmap blocks and write to disk.. */

   if(!correct){
     init_root(buffer,diskname);
     write_block(880,buffer);
     init_bitmap(bitmap);
     write_block(881,bitmap);
     }
     Update();
     Clear();
   
   mreq->io_Length  = 0;
   mreq->io_Command = TD_MOTOR;   /* turn off motor */
   DoIO(mreq);

   printf("\n\n   ** [7;43mProgram Completed[0;40m **\n");
   printf("[43m%ld[40m Bad blocks (allocated as \"in use\")\n", badcount);
   printf("%d Usable bytes free!\n\n", 856928 - (badcount * 488));
   beep();
   cleanup(0);
}

validate_required()
{
   int fstat;
   chkabort;

   /* try formatting block 0 */
   set_fbuff(track);
   format_track(0);
   chkabort();

   formatting = TRUE;
   if(!(fstat = verify_track(0))) {
        beep();
	printf("\n[7;43mUnable to format track #0.  Disk not useable.[0;40m\n\n");
	cleanup(100);
   }
   
   /* try formatting root block next */
   format_track(80);
   if(!(fstat = verify_track(80))) {
        beep();
	printf("\n[43;7mUnable to format root block.  Disk not useable.[0;40m\n\n");
	cleanup(100);
   }
}

usage_err(b,pname)
int b;
char *pname[];
{
    if(b){
    printf("\n[43;7mUsage: %s [DEVICE] <DISKNAME> or <-c>[40;0m\n\n",pname);
    exit(0);
    }
}

/* mark track as available */

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;
	}

    for(j = (tnum * 11); j < (tnum * 11 + 11);j++)
	set_bitmap(j);
}


/* mark block as available for use */

set_bitmap(block)
int block;
{
    int lindex;
    if((block - 2) > -1) {
      lindex = (block - 2) / 32 + 1;

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

/* mark block as allocated (in use) */

clear_bitmap(block)
int block;
{
    int lindex;
    if((block - 2) > -1) {
      lindex = (block - 2) / 32 + 1;

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

format_track(tnum)
int tnum;
{

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

/* verify track with abort on critical tracks */

verify_track(tnum)
int tnum;
{
   mreq->io_Length = 512;
   mreq->io_Data = (APTR)buffer;
   mreq->io_Command = CMD_READ;
   mreq->io_Offset = tnum * 11 * 512;
   DoIO(mreq);
   if(mreq->io_Error) {
        if((tnum == 80) || (tnum == 0)) {
	   beep();
	   printf("\nUnable to verify critical track # %d.\n  Aborted.\n", tnum);
	   cleanup(0);	
	   exit(100);
        }
	else
	   return(0);
   }
   else
        return(1);
}

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

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

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

set_fbuff(buff)
ULONG buff[];
{
   static int j;

   for(j = 0; j < 128;j++)
      buff[j] = 0x444f5300; /* fill buffer with ascii 'DOS' */
}

init_root(wp,vname) /* build initialized root block */
ULONG wp[];
char *vname;
{
   ULONG check_sum();

   static ULONG temp;
   temp = 0;
   zblock(wp);
   wp[0]   = 0x02;         /* type */
   wp[3]   = 0x48;         /* hashtable size */
   wp[78]  = 0xffffffff;   /* 'true' bitmap flag */
   wp[79]  = 0x371;        /* block # of bitmap */
   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 */

}

/* set volume name into root block (BCPL string) */

set_name(dest,vname)
 char *dest,*vname;
{
   *dest = (char)strlen(vname);
   dest++;
   while(*vname) {
      *dest = *vname;
      dest++; vname++;
   }
}


zblock(wp) /* clear buffer to zeros */
ULONG wp[];
{
   static int j;
   for(j = 0; j < 128; j++)
         wp[j] = 0;
}

ULONG check_sum(buff) /* checksum= 2's complement of sum of all words in block */

   ULONG buff[];
{
   static int j;
   static ULONG cksum;

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

init_bitmap(wp)
ULONG wp[];
{
   ULONG check_sum();

   clear_bitmap(880);	    /* set as allocated block 880 (root) and */
   clear_bitmap(881);       /*  block 881 (bitmap) */

   wp[0] = check_sum(wp);   /* calculate checksum and insert */
}

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

inhibit(t)
long t ; /* true -or - false */
{
   struct MsgPort *handler ;
   struct StandardPacket *packet = dpacket ;
   handler = (struct MsgPort *)DeviceProc(Device) ; /* this was "df2:" */
   if (handler == NULL || dosport == NULL)
      return ;
   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) ;
}


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,512);
    if(buffer)
	FreeMem(buffer,512);
    if(track)
	FreeMem(track,512 * 11);
    exit(0);
}	

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

Checkstat()
{
   abt =1;
   mreq->io_Command = TD_CHANGESTATE;
   DoIO(mreq);

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

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


GetVolID(drive, Device, Volume)
char *drive, *Device, *Volume;
{

#define NEXT_DEVICE(device) ((struct DeviceList *) BADDR(device -> dl_Next))

    	struct 	RootNode 	*rootNode;
    	struct 	DosInfo 	*dosInfo;
    	struct 	DeviceList 	*firstDevice;
    	struct 	DeviceList 	*device, *volume;
    		BYTE 		*name;

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

   for (device = firstDevice; device; device = NEXT_DEVICE(device)) {
      if (device -> dl_Type == DLT_DEVICE) {
	 name = (BYTE *) BADDR(device -> dl_Name);
         memcpy(Device, name + 1, *name);
         Device[*name] = ':';
	 Device[(*name)+1] = '\0';
      }
      if (!strnicmp(Device, drive, 3))	/* Is this the specified drive? */
	 break;				/* Yup! */
   }	/* for */

   if (device == NULL) {
      Device[0] = '\0';
      Volume[0] = '\0';
      return(0);
   }

   for (volume = firstDevice; volume; volume = NEXT_DEVICE(volume)) {
      if (volume -> dl_Type == DLT_VOLUME &&
          volume -> dl_Task == device -> dl_Task) {
	 name = (BYTE *) BADDR(volume -> dl_Name);
	 memcpy(Volume, name + 1, *name);
	 Volume[*name] = '\0';
	 break;
      }
   }	/* for */

   if (volume == NULL) {
      Volume[0] = '\0';
      return(0);
   }

   return(1);
}
