/*
 * On a clear disk you can seek forever...
 *
 * Clear out all unused blocks on a disk, and if a whole track is
 * unused, delete the appropriate track file.
 *
 * $Id: xdclear.c,v 1.1 1993/11/08 13:23:03 Rhialto Rel $
 * $Log: xdclear.c,v $
 * Revision 1.1  1993/11/08  13:23:03  Rhialto
 * Initial revision
 *
 *
 * (C) Copyright 1993 by Olaf 'Rhialto' Seibert. All rights reserved.
 */

#include "xpkdisk.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dos/dos.h>
#include <dos/filehandler.h>

extern struct DosLibrary *DOSBase;
extern struct ExecBase *SysBase;

struct FileSysStartupMsg Fssm;
struct DosEnvec De;
struct IOExtTD *Io;
struct MsgPort *Port;
struct MsgPort *HandlerPort;
ULONG		MaxKey;
ULONG		RootBlockKey;
ULONG	       *RootBlock;
ULONG		BitsPerBitmapBlock;
ULONG		BMBlocksPerBitmapExtBlock;
ULONG		BitmapBlockNr = -2;
ULONG	       *BitmapBlock;
ULONG		BitmapExtBlockNr = -1;
ULONG	       *BitmapExtBlock;
ULONG	       *ZeroBlock;
ULONG		Offset;
int		XpkDisk;
int		Pretend;
int		Verbose;

int		SectorsCleared;
int		SectorsReallyCleared;
int		TracksDeleted;
int		TracksReallyDeleted;

#define debug(x)    printf x
#define debug0(x)

#define SIZE	De.de_SizeBlock

#define BM_BLOCKS_PER_ROOTBLOCK     ((-25)-(-49)+1)
#define BM_BLOCKS_PER_BMEBLOCK	    (SIZE - 1)

int
GetKey(ULONG key, ULONG *buffer)
{
    Io->iotd_Req.io_Command = ETD_READ;
    Io->iotd_Req.io_Offset = Offset + sizeof(ULONG) * SIZE * key;
    Io->iotd_Req.io_Length = sizeof(ULONG) * SIZE;
    Io->iotd_Req.io_Data = (char *)buffer;

    if (DoIO((struct IORequest *)Io)) {
	printf("Read error %d on sector %d!\n",
	    Io->iotd_Req.io_Error, key);
	return Io->iotd_Req.io_Error;
    }
    return 0;
}

int
PutKey(ULONG key, ULONG *buffer)
{
    Io->iotd_Req.io_Command = ETD_WRITE;
    Io->iotd_Req.io_Offset = Offset + sizeof(ULONG) * SIZE * key;
    Io->iotd_Req.io_Length = sizeof(ULONG) * SIZE;
    Io->iotd_Req.io_Data = (char *)buffer;

    if (DoIO((struct IORequest *)Io)) {
	printf("Write error %d on sector %d!\n",
	    Io->iotd_Req.io_Error, key);
	return Io->iotd_Req.io_Error;
    }
    return 0;
}

/*
 * All checksums make the block add to 0.
 */
int
CheckSum(ULONG *buffer)
{
    int 	    i;
    ULONG	    sum = 0;

    for (i = SIZE; i > 0; i--)
	sum += *buffer++;

    return sum? 0 : 1;
}

int
GetBitmapExtBlock(ULONG blocknr)
{
    if (blocknr != BitmapExtBlockNr) {
	ULONG		key;
	ULONG		keynr;

	if (blocknr < BitmapExtBlockNr || BitmapExtBlockNr == (ULONG)-1) {
	    key = RootBlock[SIZE - 24];
	    keynr = 0;
	    if (Verbose >= 2)
		printf("GetBitmapExtBlock %d -> first key %d\n", blocknr, key);
	} else {
	    key = BitmapExtBlock[SIZE - 1];
	    keynr = BitmapExtBlockNr + 1;
	    if (Verbose >= 2)
		printf("GetBitmapExtBlock %d -> next key %d\n", blocknr, key);
	}

	while (key && keynr <= blocknr) {
	    GetKey(key, BitmapExtBlock);
	    BitmapExtBlockNr = keynr;
	    if (Verbose >= 2)
		printf("GetBitmapExtBlock %d -> get %d key %d\n",
			blocknr, keynr, key);
	    key = BitmapExtBlock[SIZE - 1];
	    keynr++;
	}

	if (key == 0 && BitmapExtBlockNr != blocknr) {
	    debug0(("No bitmap extension block #%d!!!\n", keynr));
	    return -97;
	}
    }
    return 0;
}

int
GetBitmapBlock(ULONG blocknr, ULONG *buffer)
{
    ULONG	    key;

    if (blocknr < BM_BLOCKS_PER_ROOTBLOCK) {
	key = RootBlock[SIZE - 49 + blocknr];
	if (Verbose >= 2)
	    printf("GetBitmapBlock %d -> root key %d\n", blocknr, key);
    } else {
	ULONG		tmp =	blocknr - BM_BLOCKS_PER_ROOTBLOCK;
	ULONG		bmeblocknr = tmp / BM_BLOCKS_PER_BMEBLOCK;
	ULONG		offset	   = tmp % BM_BLOCKS_PER_BMEBLOCK;

	if (Verbose >= 2)
	    printf("GetBitmapBlock %d -> extension block %d\n", blocknr, bmeblocknr);

	if ((tmp = GetBitmapExtBlock(bmeblocknr)) == 0)
	    key = BitmapExtBlock[offset];
	else
	    return tmp;

	if (Verbose >= 2)
	    printf("GetBitmapBlock %d -> (ext'd) key %d\n", blocknr, key);
    }
    if (key == 0) {
	debug0(("Key for BitmapBlock #%d is 0.\n", blocknr));
	return -98;
    }
    if (GetKey(key, buffer) == 0) {
	if (CheckSum(buffer) == 0) {
	    printf("Checksum of Bitmap Block #%d, key %d is bad!\n",
		   blocknr, key);
	    return -99;
	}
	return 0;
    }
    return Io->iotd_Req.io_Error;
}

int
GetBit(ULONG key)
{
    ULONG	    blocknr;

    key -= De.de_Reserved;
    blocknr = key / BitsPerBitmapBlock;

    if (blocknr != BitmapBlockNr) {
	if (Verbose >= 2)
	    printf("GetBit %d -> BitmapBlock %d\n", key+De.de_Reserved, blocknr);

	if (blocknr == BitmapBlockNr + 1)
	    memcpy(BitmapBlock + 1, BitmapBlock + SIZE, (SIZE - 1) * 4);
	else
	    GetBitmapBlock(blocknr, BitmapBlock);
	if (GetBitmapBlock(blocknr + 1, BitmapBlock + SIZE) == 0) {
	    memmove(BitmapBlock + SIZE, BitmapBlock + SIZE + 1, (SIZE - 1) * 4);
	} else {
	    memset(BitmapBlock + SIZE, 0, SIZE * 4);
	    debug0(("Zeroed 2nd bitmapblock.\n"));
	}
	BitmapBlockNr = blocknr;
    }

    return key % BitsPerBitmapBlock;	    /* bit offset */
}

void
ZeroOut(int key)
{
    debug0(("ZeroOut %d.\n", key));
    SectorsCleared++;

    if (GetKey(key, ZeroBlock) == 0) {
	int		i;
	ULONG	       *p;

	for (i = SIZE, p = ZeroBlock; i > 0; i--, p++) {
	    if (*p != 0) {
		for (; i > 0; i--, p++)
		    *p = 0;
		if (!Pretend)
		    PutKey(key, ZeroBlock);
		SectorsReallyCleared++;
		break;
	    }
	}
    }
}

void
ZeroTrack(int track)
{
    char	    name[64];
    ULONG	    i;

    TracksDeleted++;
    i = sprintf(name, XPKDISKDIR "Unit%lx/", Fssm.fssm_Unit);
    NewName(&name[i], track);
    if (i = Lock(name, SHARED_LOCK)) {
	UnLock(i);
	TracksReallyDeleted++;
	printf("Delete %s.\n", name);
	if (!Pretend)
	    DeleteFile(name);
    }
}

LONG
min(LONG a, LONG b)
{
    return a < b? a: b;
}

void
CheckBits(int bitoffset, int numbits, ULONG key, int track)
{
    int 	    longindex;
    int 	    longoffset;
    int 	    bits;
    ULONG	   *p;
    char	    ones = 0;
    char	    zeros = 0;
    ULONG	    mask;
    int 	    pass;
    ULONG	    mykey;

    if (Verbose >= 1) {
	printf(" Track %5d... ", track);
    }

    if (key == De.de_Reserved) {
	/* Pretend the reserved sectors are in use */
	zeros = 1;
    }

    for (pass = 0 + !XpkDisk; pass < 2; pass++) {
	longindex = bitoffset / 32;
	longoffset = bitoffset % 32;
	bits = numbits;
	mykey = key;

	p = BitmapBlock + 1 + longindex;
	mask = 1L << longoffset;
	debug0(("Start %08lx, track %d, pass %d, mask %08lx, bits %d\n",
	       *p, track, pass, mask, bits));

	while (bits > 0) {
	    if (*p == 0xFFFFFFFF) {
		if (pass > 0) {
		    int i = min(32-1-longoffset, bits-1);

		    debug0(("bits %d, *p %x, mykey %d: ", bits, *p, mykey));
		    for (; i >= 0; i--) {
			ZeroOut(mykey + i);
		    }
		}
		bits -= 32 - longoffset;
		mask = 1;
		mykey += 32 - longoffset;
		longoffset = 0;
		p++;
		debug0(("Next  %08lx, track %d, pass %d, bits %d\n", *p, track, pass, bits));
		ones = 1;
	    } else if (*p == 0) {
		bits -= 32 - longoffset;
		mask = 1;
		mykey += 32 - longoffset;
		longoffset = 0;
		p++;
		debug0(("Next  %08lx, track %d, pass %d, bits %d\n", *p, track, pass, bits));
		zeros = 1;
	    } else {
		if (*p & mask) {
		    /* Block free */
		    ones = 1;
		    if (pass > 0) {
			debug0(("%08lx ", *p));
			ZeroOut(mykey);
		    }
		} else {
		    /* Block in use */
		    zeros = 1;
		}

		bits--;
		mask <<= 1;
		mykey++;
		if (mask == 0) {
		    longoffset = 0;
		    mask = 1;
		    p++;
		    debug0(("Next  %08lx, track %d, pass %d, bits %d\n", *p, track, pass, bits));
		}
	    }
	}
	if (!ones || !zeros)
	    break;
    }
    if (Verbose) {
	printf("%c\r", ones ? 'Z' : ' ');
	fflush(stdout);
    }
    if (XpkDisk && !zeros)
	ZeroTrack(track);
}

void
Clear(void)
{
    ULONG	    key;
    ULONG	    nextkey;
    ULONG	    track;

    /* First, test the boot block for file system type */
    GetKey(0, RootBlock);
    if ((RootBlock[0] & 0xFFFFFF00) != 'DOS\0') {
	printf("Not an AmigaDOS disk.\n");
	return;
    }

    /* Start at rootblock */
    GetKey(RootBlockKey, RootBlock);
    if (RootBlock[0] != 2 || RootBlock[SIZE-1] != ST_ROOT) {
	printf("Rootblock is not a root block.\n");
	return;
    }
    if (!CheckSum(RootBlock)) {
	printf("Rootblock checksum bad.\n");
	return;
    }
    if (RootBlock[SIZE - 50] == 0) {
	printf("Bitmap valid flag not set.\n");
	return;
    }

    key = De.de_Reserved;
    track = key / De.de_BlocksPerTrack;
    nextkey = (track + 1) * De.de_BlocksPerTrack;

    CheckBits(GetBit(key), nextkey - key, key, track);

    track++;
    key = nextkey;

    for (; key < MaxKey; key = nextkey, track++) {
	int		offset;

	nextkey = key + De.de_BlocksPerTrack;
	offset = GetBit(key);
	CheckBits(offset, De.de_BlocksPerTrack, key, track);
	chkabort();
    }
}

int
OpenAll(char *devicename)
{
    int 	    fail = 0;

    {
	struct DosList *dl;

	if (dl = LockDosList(LDF_DEVICES | LDF_READ)) {
	    if (dl = FindDosEntry(dl, devicename, LDF_DEVICES)) {
		struct FileSysStartupMsg *fssm;
		struct DosEnvec *de;

		fssm = BADDR(dl->dol_misc.dol_handler.dol_Startup);
		de = BADDR(fssm->fssm_Environ);

		Fssm = *fssm;
		Fssm.fssm_Device = (char *)BADDR(Fssm.fssm_Device) + 1;
		De = *de;
		HandlerPort = dl->dol_Task;
	    } else {
		printf("No %s.\n", devicename);
		fail = 1;
	    }
	    UnLockDosList(LDF_DEVICES | LDF_READ);
	} else {
	    printf("Can't LockDosList.\n");
	    fail = 2;
	}
    }
    if (fail != 0)
	return fail;

    if (HandlerPort) {
	DoPkt(HandlerPort, ACTION_FLUSH, 0, 0, 0, 0, 0);
	DoPkt(HandlerPort, ACTION_INHIBIT, -1, 0, 0, 0, 0);
    }

    if (De.de_TableSize < DE_BUFMEMTYPE)
	De.de_BufMemType = MEMF_CHIP | MEMF_PUBLIC;

    Port = CreateMsgPort();
    if (Port == NULL)
	return 3;

    Io = CreateExtIO(Port, sizeof(*Io));
    if (Io == NULL)
	return 4;

    if (fail = OpenDevice((char *)Fssm.fssm_Device, Fssm.fssm_Unit,
		   (struct IORequest *)Io, Fssm.fssm_Flags)) {
	printf("Can't OpenDevice %s unit %d, error %d.\n",
	       Fssm.fssm_Device, Fssm.fssm_Unit, fail);
	return 5;
    }

    Io->iotd_Req.io_Command = TD_CHANGENUM;
    DoIO((struct IORequest *)Io);
    Io->iotd_Count = Io->iotd_Req.io_Actual;

    XpkDisk = strcmp(FilePart((char *)Fssm.fssm_Device), "xpkdisk.device") == 0;

    RootBlock = AllocMem(SIZE * sizeof(ULONG), De.de_BufMemType);
    if (RootBlock == NULL)
	return 6;

    BitmapBlock = AllocMem(2 * SIZE * sizeof(ULONG), De.de_BufMemType);
    if (BitmapBlock == NULL)
	return 7;

    BitmapExtBlock = AllocMem(SIZE * sizeof(ULONG), De.de_BufMemType);
    if (BitmapExtBlock == NULL)
	return 8;

    ZeroBlock = AllocMem(SIZE * sizeof(ULONG), De.de_BufMemType);
    if (ZeroBlock == NULL)
	return 9;

    Offset = sizeof(ULONG) * SIZE *
	     De.de_BlocksPerTrack * De.de_Surfaces * De.de_LowCyl;
    MaxKey = De.de_BlocksPerTrack * De.de_Surfaces *
		    (De.de_HighCyl - De.de_LowCyl + 1);
    RootBlockKey = (MaxKey - 1 + De.de_Reserved) / 2;

    BitsPerBitmapBlock = 32 * (SIZE - 1);

    debug0(("Offset %x, RootBlockKey %d\n", Offset, RootBlockKey));
    debug0(("Bits/BitmapBlock %d\n", BitsPerBitmapBlock));

    if (De.de_BlocksPerTrack > BitsPerBitmapBlock) {
	printf(
"What a funny disk you have! %d blocks/track is more than I can handle!\n",
		De.de_BlocksPerTrack);
	return 10;
    }

    return 0;
}

void
CloseAll(void)
{
    if (RootBlock)
	FreeMem(RootBlock, SIZE * sizeof(unsigned long));
    if (BitmapBlock)
	FreeMem(BitmapBlock, 2 * SIZE * sizeof(unsigned long));
    if (BitmapExtBlock)
	FreeMem(BitmapExtBlock, SIZE * sizeof(unsigned long));
    if (ZeroBlock)
	FreeMem(ZeroBlock, SIZE * sizeof(unsigned long));
    if (Io) {
	if (Io->iotd_Req.io_Device) {
	    Io->iotd_Req.io_Command = ETD_UPDATE;
	    DoIO((struct IORequest *)Io);
	    Io->iotd_Req.io_Command = TD_MOTOR;
	    Io->iotd_Req.io_Length = 0;
	    DoIO((struct IORequest *)Io);
	    CloseDevice((struct IORequest *)Io);
	}
	DeleteExtIO((struct IORequest *)Io);
    }
    if (Port)
	DeleteMsgPort(Port);
    if (HandlerPort) {
	DoPkt(HandlerPort, ACTION_INHIBIT, 0, 0, 0, 0, 0);
    }

    printf(
"Statistics: %d (%d) sectors (really) cleared,\n"
"            %d (%d) tracks (really) deleted, \n"
"            %d free sectors total.\n",
	    SectorsCleared,
	    SectorsReallyCleared,
	    TracksDeleted,
	    TracksReallyDeleted,
	    SectorsCleared + TracksDeleted * De.de_BlocksPerTrack);
}

int
main(int argc, char **argv)
{
    char	   *colon;
    int 	    ac;
    char	  **av;

    if (DOSBase->dl_lib.lib_Version < 37) {
	printf("Sorry, requires 2.04+ (V37+)\n");
	return 20;
    }

    ac = argc - 1;
    av = argv + 1;

    while (ac > 0 && av[0][0] == '-') {
	char	       *a = av[0] + 1;

	while (a[0]) {
	    switch (a[0]) {
	    case 'n':
		Pretend = 1;
		printf("Pretend mode.\n");
		break;
	    case 'v':
		Verbose++;
		break;
	    default:
		goto usage;
	    }
	    a++;
	}
	ac--;
	av++;
    }

    if (ac < 1) {
    usage:
	printf("Usage: %s [-n] [-v] devicename\n", argv[0]);
	return 20;
    }

    if (colon = strchr(av[0], ':'))
	*colon = '\0';

    atexit(CloseAll);

    if (OpenAll(av[0]) == 0) {
	if (!Pretend) {
	    printf("Hit return to commence clearing %s: or CTRL-C to abort: ",
		    av[0]);
	    fflush(stdout);
	    while (!feof(stdin) && getchar() != '\n')
		/* nothing */ ;
	    chkabort();
	}
	Clear();
    }
    /*CloseAll();*/
}
