/*
 * $VER: SecureDelete.c 42.1 (10.8.95)
 *
 * by Bruce M. Simpson <bsimpson@touchdwn.demon.co.uk>
 *
 * A 'secure' deletion program for files on any filesystem.  It works by
 * overwriting the data of the file itself, and making sure the buffers are
 * forced out to disk.  Later versions will probably incorporate a special
 * algorithm designed to wipe out all traces of the file's data from the
 * magnetic medium.  The old file header does NOT get deleted, as this is
 * up to the filesystem, however file headers can be removed from Amiga
 * filesystem volumes by using a tool such as ReOrg, which is admittedly
 * overkill, but there's no 100% kosher way to eliminate the file headers
 * unless the handler specifically a custom packet for doing so...
 *
 * I could make this remove every single trace of a file from an AmigaDOS
 * volume, but hey, life's too short, and anyway I see the blocks getting
 * overwrittem when I check up using DiskX and SnoopDos, so I'm satisfied.
 *
 * THIS PROGRAM HAS NO WARRANTY EXPRESSED OR IMPLIED. ANY DAMAGE CAUSED
 * TO FILES OR COMPUTER SYSTEMS BY RUNNING THS PROGRAM IS NOT THE AUTHOR'S
 * RESPONSIBILITY. USE THIS PROGRAM AT YOUR OWN RISK.
 *
 */

#include <exec/types.h>
#include <exec/alerts.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <dos/dos.h>
#include <dos/dosextens.h>

#define __USE_SYSBASE
#include <proto/exec.h>
#include <proto/utility.h>
#include <proto/dos.h>

#include "debug.h"

static const TEXT verstag[] = "$VER: SecureDelete 42.1 (10.8.85)\0";
static const TEXT ProgName[] = "SecureDelete";
static const TEXT cmdtemplate[] = "FILE/A";

#define OS_LIB_VERSION 37L	/* release 2 or better */
#define EMPTY_MEM_SIZE 512	/* kludgy - may look up device blocksize later */

long __builtin_getreg(int);

__regargs ULONG main (void)
{
	register struct ExecBase	*SysBase	 = (*((struct ExecBase **)4L));
	register struct DosLibrary	*DOSBase	 = NULL;
	register struct Library		*UtilityBase = NULL;
	struct Process			*myProcess	= (struct Process *) FindTask(NULL);
	struct FileInfoBlock	*fib		= NULL;
	struct RDArgs			*rdargs		= NULL;
	struct DevProc			*devproc	= NULL;
	STRPTR		filename;
	ULONG		rc = RETURN_FAIL;
	ULONG		currblk;
	ULONG		numblocks;
	ULONG		remainder;
	BPTR		fh = NULL;
	BOOL		ovfail = TRUE;
	void		*emptybuf = NULL;

	if (!( DOSBase = (struct DosLibrary *) OpenLibrary("dos.library", OS_LIB_VERSION )))
	{
		D(bug("%s: Couldn't open %s\n",ProgName,"dos.library"));
		Alert( AG_OpenLib | AO_DOSLib );
		myProcess->pr_Result2 = ERROR_INVALID_RESIDENT_LIBRARY;
		goto cleanup;
	}

	if (!( UtilityBase = OpenLibrary("utility.library", OS_LIB_VERSION )))
	{
		D(bug("%s: Couldn't open %s\n",ProgName,"utility.library"));
		Alert( AG_OpenLib | AO_UtilityLib );
		SetIoErr( ERROR_INVALID_RESIDENT_LIBRARY );
		PrintFault( ERROR_INVALID_RESIDENT_LIBRARY, ProgName);
		goto cleanup;
	}

	if (!( rdargs = ReadArgs(cmdtemplate, (LONG *) &filename, NULL )))
	{
		D(bug("%s: Invalid command line arguments\n",ProgName));
		PrintFault(IoErr(), ProgName);
		goto cleanup;
	}

	/* of course, what we are about to do is only valid under a filesystem */
	/* you can do it on a handler but that makes no sense... */
	if (!IsFileSystem(filename))
	{
		Printf("%s: Device of '%s' is not a FileSystem\n",ProgName);
		rc = RETURN_ERROR; goto cleanup;
	}

	if (!( fh = Open(filename, MODE_OLDFILE )))
	{
		D(bug("%s: Couldn't open file '%s'\n",ProgName,filename));
		PrintFault(IoErr(), ProgName);
		rc = RETURN_ERROR; goto cleanup;
	}

	if (!( fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB, TAG_DONE )))
	{
		D(bug("%s: Couldn't allocate FileInfoBlock\n",ProgName));
		PrintFault(IoErr(), ProgName);
		goto cleanup;
	}

	if (!(ExamineFH(fh,fib)))
	{
		D(bug("%s: Couldn't examine opened file '%s'\n",ProgName,filename));
		PrintFault(IoErr(), ProgName);
		goto cleanup;
	}

	if (!( emptybuf = AllocMem(EMPTY_MEM_SIZE, MEMF_PUBLIC | MEMF_CLEAR )))
	{
		D(bug("%s: Couldn't allocate empty memory block\n",ProgName));
		SetIoErr(ERROR_NO_FREE_STORE);
		PrintFault(ERROR_NO_FREE_STORE, ProgName);
		goto cleanup;
	}

	/* use explicit long math calls to remain pure (compiler bug workaround) */

	numblocks = UDivMod32(fib->fib_Size,EMPTY_MEM_SIZE);
	remainder = __builtin_getreg(1);

	/* overwrite each block */
	if (numblocks)
		for (currblk = 0; currblk < numblocks; currblk++)
			if (Write(fh,emptybuf,EMPTY_MEM_SIZE) != EMPTY_MEM_SIZE)
			{
				D(bug("%s: Error overwriting file '%s'\n",ProgName,filename));
				PrintFault(IoErr(), ProgName);
				rc = RETURN_ERROR; break;
			}

	/* did we fail whilst in the loop? */
	if (currblk == numblocks) ovfail = FALSE;

	/* overwrite remaining bytes */
	if (remainder && !ovfail)
		if (Write(fh,emptybuf,remainder) != remainder)
		{
			D(bug("%s: Error overwriting file '%s'\n",ProgName,filename));
			PrintFault(IoErr(), ProgName);
			rc = RETURN_ERROR;
		}

	/* try and close the file */
	if (!Close(fh))
	{
		D(bug("%s: Error closing file '%s'\n",ProgName,filename));
		PrintFault(IoErr(), ProgName);
		ovfail = TRUE; rc = RETURN_ERROR;
	}
	fh = NULL;

	/* only attempt to delete file if overwriting successful */
	if (!ovfail)
	{
		if (!DeleteFile(filename))
		{
			/* DeleteFile() failed */
			D(bug("%s: Error deleting file '%s'\n",ProgName,filename));
			PrintFault(IoErr(), ProgName);
			rc = RETURN_ERROR;
		}
		else
		{
			/* everything succeeded... */
			Printf("%s: '%s' deleted securely\n",ProgName,filename);
			rc = RETURN_OK; /* flag success */
		}
	}

	if (devproc = GetDeviceProc(filename,devproc))
	{
		/* always flush filesystem if we got this far */
		if (!DoPkt(devproc->dvp_Port,ACTION_FLUSH,NULL,NULL,NULL,NULL,NULL))
		{
			D(bug("%s: Failed to flush filesystem\n",ProgName));
			Printf("%s: Failed to flush filesystem\n",ProgName);
			rc = RETURN_WARN; /* warn user that FLUSH failed */
		}
		FreeDeviceProc(devproc);
	}
	else
	{
		D(bug("%s: GetDeviceProc() failed on '%s'\n",ProgName,filename));
		Printf("%s: Failed to flush filesystem\n",ProgName);
		rc = RETURN_WARN; /* warn user that FLUSH failed */
	}

cleanup:
	if (emptybuf)		FreeMem(emptybuf,EMPTY_MEM_SIZE);
	if (fib)			FreeDosObject(DOS_FIB,(APTR)fib);
	if (fh)				Close(fh);
	if (rdargs)			FreeArgs(rdargs);
	if (UtilityBase)	CloseLibrary(UtilityBase);
	if (DOSBase)		CloseLibrary((struct Library *)DOSBase);
	return(rc);
}
