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

/* Bob Bush cpu-73105,1332
   compiled with Lattice 4.0 28-Mar-88 */

#include "stdio.h"
#include "string.h"
#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"

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;

BOOL formatting = FALSE;
BOOL correct    = FALSE;

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

    printf("\n** BREAK **\n");
    if(formatting) {
       printf("Do you really want to abort? (y/n) ");
       gets(buf); c = buf[0];
       if(c == 'y' || c == 'Y') {   
         cleanup(999);
         printf("Warning! disk may be un-useable..\n");
         return(1); }
       else
	 return(0);
     }
    return(1);
}

main(argc,argv)
int argc;
char *argv[];
{
   static int error,fstat,j,c,err,badcount;
   err = onbreak(&brk);
   badcount = 0;

   if(argc < 2) 
      usage_err(0);
   if(!strcmp(argv[1],"?"))
      usage_err(1);


   if(argc == 3) {
      fix(argv[2]);
      if(!strcmp(argv[2],"-C"))
	  correct = TRUE;
      else
	  usage_err(1);
   }

   if(correct)
       printf("Insert Disk to be corrected in drive 'DF0:'.\n");
   else
       printf("Insert Disk to be formatted in drive 'DF0:'.\n");
   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,0,mreq,0))
	cleanup(1);
   inhibit(TRUE) ;

   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;

   if(!correct)
	validate_required();

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

   if(correct) {
       for(j = 0; j < 160; j++) {
           printf("\rVerify Track # %3ld",j);
             if(!(fstat = verify_track(j))) {
	       printf(" -- Bad Track! allocated as used..\n");
	       badcount += 11;
             }
             else
	        set_track(j);
       }
    }
    else {
       for(j = 0; j < 160; j++) {
           printf("\rFormat Track # %3ld",j);
           format_track(j);
           printf("\rVerify Track # %3ld",j);
             if(!(fstat = verify_track(j))) {
	       printf(" -- Bad Track! allocated as used..\n");
	       badcount += 11;
           }
           else
	      set_track(j);
       }
    }

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

   init_root(buffer,argv[1]);
   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** Program Completed **\n");
   printf("%ld Bad blocks allocated as used\n",badcount);
   cleanup(0);
}

fix(s)
char *s;
{
    s++;
    *s = toupper(*s);
}

validate_required()
{
   int fstat;

   /* try formatting block 0 */
   set_fbuff(track);
   format_track(0);
   formatting = TRUE;
   if(!(fstat = verify_track(0))) {
	printf("\nUnable to format track #0..disk not useable..\n\n");
	cleanup(100);
   }
   
   /* try formatting root block next */
   format_track(80);
   if(!(fstat = verify_track(80))) {
	printf("\nUnable to format root block..disk not useable..\n\n");
	cleanup(100);
   }
}

usage_err(b)
int b;
{
    if(b)
	printf("Command Error\n");
    printf("Usage: bformat volume_name <-c> (assumes df0:)\n");
    exit(0);
}

/* mark track as available */

set_track(tnum)
int tnum;
{
    int j;

    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)) {
	   printf("\nUnable to verify critical track # %d \n Aborted..\n");
	   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("DF0:") ;
   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);
    if(err == 999)
	return(0);
    exit(0);
}	
