About

This is a small command-line utility that deletes a file by first overwriting the data with a 4-pass scheme (random, even, odd, random) and optionally benefits from the asyncio.library if present.

Download

Installing

  1. Extract the archive
  2. Copy the qtip executable somewhere in the path.

Compiling

The program was written to be compiled with SAS/C but you can compile it with whatever tool available that has the required AmigaOS libraries and include files. You may need to download dev/c/AsyncIO.lha by Magnus Holmgren since it contains the required include files. If needed, the include files and libraries should be moved to the SAS/C folder (libs under libs, include under include, etc…).

After all the includes and libraries are setup correctly, you can change directory to src and then issue the command:

sc link qtip.c

and it should result in a qtip binary file.

Usage

After copying the binary to an executable path, issue:

echo "test" > RAM:testfile.txt

to generate a test file at RAM:testfile.txt and then issue:

qtip RAM:testfile.txt

in order to delete that file.

Code

qtip.c
/*************************************************************************/
/*    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
/*                                                                       */
/*   qtip                                                                */
/*                                                                       */
/*   Quick and secure erasure of files by performing the following       */
/*   file overwrite operations in sequence:                              */
/*     1.) random pass,                                                  */
/*     2.) even pass (0xAA),                                             */
/*     3.) odd pass (0x55),                                              */
/*     4.) random pass.                                                  */
/*   after which the file is deleted.                                    */
/*                                                                       */
/*   Amiga: Compile with SAS/C:                                          */
/*       sc link parameters=registers errorrexx optimize qtip.c          */
/*                                                                       */
/*************************************************************************/
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <setjmp.h>
#include <dos/dos.h>
#include <limits.h>
 
#include <exec/memory.h>
 
#include <proto/asyncio.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/timer.h>
 
#include <devices/timer.h>
 
/*************************************************************************/
/*                        Wiping definitions.                            */
/*************************************************************************/
#define EVEN 0xAA
#define ODD 0x55
 
/*************************************************************************/
/*                   Conventional length of errors.                      */
/*************************************************************************/
#define ERROR_LENGTH 81
 
/*************************************************************************/
/*              Maximum length of an AmigaDOS path name                  */
/*************************************************************************/
#define PATHNAME_MAX 108
 
/*************************************************************************/
/*        Version string used for querrying the program version.         */
/*************************************************************************/
GLOBAL TEXT version_string[] = 
    "\0$VER: qtip 1.6 "__AMIGADATE__" by Wizardry and Steamworks";
 
/*************************************************************************/
/*              Amiga OS command-line arguments parameters.              */
/*************************************************************************/
STATIC CONST TEXT template[] = "PATH/A/M,ALL/S,QUIET/S";
enum {
    PATH,
    ALL,
    QUIET,
    NUM_ARGS
};
 
/*************************************************************************/
/*          Wizardry and Steamworks' string stack implementation.        */
/*************************************************************************/
 
/* The stringStack structure with top being the index of the next element 
 * to be inserted in stack (the top-most element to be found at top - 1).
 */
typedef struct {
   ULONG size;
   UBYTE **store;
   ULONG top;
} stringStack;
 
/*
 * Creates a new stringStack with a given size.
 */
stringStack* stringStackCreate(ULONG size) {
   stringStack *s = (stringStack*)AllocMem(
       sizeof(stringStack), 
       MEMF_ANY|MEMF_CLEAR
   );
   if ((s->store = (UBYTE**)AllocMem(
       size * sizeof(UBYTE **), 
       MEMF_ANY|MEMF_CLEAR
       )) == NULL) return NULL;
   s->size = size;
   s->top = 0;
   return s;
}
 
/*
 * Takes as parameter a stringStack and returns 1 if the stack is empty
 * or 0 if the stack is not empty.
 */
BOOL stringStackIsEmpty(stringStack *s) {
    return (BOOL)(s->top == 0);
}
 
/*
 * Pushes an element onto the stringStack.
 */
void stringStackPush(stringStack *s, UBYTE *e) {
    UBYTE **sStore;
    if (s->top > s->size - 1) { // realloc for AmigaOS
        sStore = (UBYTE **)AllocMem(
            (s->size + 1) * sizeof(UBYTE **), 
            MEMF_ANY|MEMF_CLEAR
        );
        CopyMem(s->store, sStore, s->size * sizeof(UBYTE **));
        FreeMem(s->store, sizeof(s->store));
        s->store = sStore;
        ++s->size;
    }
    s->store[s->top] = (UBYTE *)AllocMem(
            strlen(e) * sizeof(UBYTE *) + 1, 
            MEMF_ANY|MEMF_CLEAR
    );
    CopyMem(e, s->store[s->top], strlen(e) * sizeof(UBYTE *) + 1);
    ++s->top;
}
 
/*
 * Pops an element off the stringStack or returns NULL in case the
 * stack is empty.
 */
UBYTE *stringStackPop(stringStack *s) {
   UBYTE *e;
   if (stringStackIsEmpty(s))
       return NULL;
   --s->top;
   e = (UBYTE *)AllocMem(
       strlen(s->store[s->top]) * sizeof(UBYTE *) + 1, 
       MEMF_ANY|MEMF_CLEAR
   );
   CopyMem(
       s->store[s->top], 
       e, 
       strlen(s->store[s->top]) * sizeof(UBYTE *) + 1
   );
   return e;
}
 
/*************************************************************************/
/*                Returns a formatted IO error message.                  */
/*************************************************************************/
UBYTE *FormatIOError(LONG code, UBYTE *path, UBYTE *message) {
    UBYTE *error;
    UBYTE *header;
    if((error = AllocMem(
        ERROR_LENGTH * sizeof(UBYTE) + 1, 
        MEMF_ANY|MEMF_CLEAR)
    ) == NULL) return NULL;
    header = AllocMem(
        (strlen(path) + strlen(message) + 1)  * sizeof(UBYTE) + 1, 
        MEMF_ANY|MEMF_CLEAR
    );
    sprintf(header, "%s %s", path, message);
    Fault(IoErr(), header, error, ERROR_LENGTH);
    return error;
}
 
/*************************************************************************/
/*              Wiping function taking as input a file.                  */
/*************************************************************************/
LONG Wipe(UBYTE *file) {
    struct Library *AsyncIOBase = NULL;
    struct Device *TimerBase;
    struct IORequest timereq;
    struct timeval sysTime;
    APTR fp = NULL;
    BPTR lock = NULL;
    LONG size, i;
    UBYTE rnd;
    jmp_buf env;
    LONG errorCode;
 
    /* jump: Close any file-handles if they are opened and terminate. */
    errorCode = setjmp(env);
    if(errorCode != RETURN_OK) {
        /* Close any opened files. */
        switch (AsyncIOBase != NULL) {
            case TRUE:
                CloseAsync(fp);
                CloseLibrary(AsyncIOBase);
                break;
            default:
                Close((BPTR)fp);
                break;
        }
        /* Unlock the file. */
        if(lock != NULL) UnLock(lock);
        /* And return the error code. */
        return errorCode;
    }
 
    /* Attempt to open the asyncio library */
    AsyncIOBase = OpenLibrary("asyncio.library", 0);
 
    /* Lock the file. */
    if((lock = Lock(file, ACCESS_READ)) == NULL) {
        Printf("Unable to acquire a write lock to the specified file.\n");
        return RETURN_ERROR;
    }
 
    /* Either use asyncio library if available or regular file-handling */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            fp = OpenAsync(file, MODE_READ, 1);
            break;
        default:
            fp = (APTR)Open(file, MODE_OLDFILE);
            break;
    }
 
    /* Check if the file could be opened. */
    if (fp == NULL) {
        Printf("Unable to open file: %s for reading.\n", file);
        return RETURN_ERROR;
    }
 
    /* Seek to find the file size. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            size = SeekAsync(fp, 0L, MODE_END);
            if (size == -1) {
                Printf("Cannot seek to the end of the file.\n");
                longjmp(env, RETURN_ERROR);
            }
            size = SeekAsync(fp, 0L, MODE_START);
            break;
        default:
            /* Determine the file-size. */
            size = Seek((BPTR)fp, 0L, OFFSET_END);
            if (size == -1) {
                Printf("Cannot seek to the end of the file.\n");
                longjmp(env, RETURN_ERROR);
            }
            size = Seek((BPTR)fp, 0L, OFFSET_BEGINNING);
            break;
    }
 
    /* Close file. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            CloseAsync(fp);
            break;
        default:
            Close((BPTR)fp);
            break;
    }
 
    /* Open the file again, in append mode if possible. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            fp = OpenAsync(file, MODE_APPEND, 1);
            break;
        default:
            fp = (APTR)Open(file, MODE_OLDFILE);
            break;
    }
 
    /* Check if the file could be opened. */
    if (fp == NULL) {
        Printf("Unable to open file: %s for writing.\n", file);
        return RETURN_ERROR;
    }
 
    /* Seek to the beginning of the file. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            SeekAsync(fp, 0L, MODE_START);
            break;
        default:
            Seek((BPTR)fp, 0L, OFFSET_BEGINNING);
            break;
    }
 
    /* Get the current time. */
    if(OpenDevice("timer.device", 0, &timereq, 0) != 0) {
        Printf("Unable to open timer device.\n");
        longjmp(env, RETURN_ERROR);
    }
    TimerBase = timereq.io_Device;
    GetSysTime(&sysTime);
    CloseDevice(&timereq);
    /* Initialize the seed from the microseconds. */
    srand((unsigned)sysTime.tv_micro);
 
    /* Random pass. */
    i = size;
    do {
        /* check for Ctrl-C */
        if(CheckSignal(SIGBREAKF_CTRL_C) != FALSE) 
            longjmp(env, RETURN_WARN);
        /* generate random char */
        rnd = rand() % 256;
        switch (AsyncIOBase != NULL) {
            case TRUE:
                if (WriteCharAsync(fp, rnd) != 1) {
                    Printf("Failed to write random pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
            default:
                if (Write((BPTR)fp, (APTR)rnd, 1) != 1) {
                    Printf("Failed to write random pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
        }
    } while (--i);
 
    /* Seek to the beginning of the file. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            SeekAsync(fp, 0L, MODE_START);
            break;
        default:
            Seek((BPTR)fp, 0L, OFFSET_BEGINNING);
            break;
    }
 
    /* Even pass: EVEN. */
    i = size;
    do {
        /* check for Ctrl-C */
        if(CheckSignal(SIGBREAKF_CTRL_C) != FALSE) 
            longjmp(env, RETURN_WARN);
        switch (AsyncIOBase != NULL) {
            case TRUE:
                if (WriteCharAsync(fp, EVEN) != 1) {
                    Printf("Failed to write even pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
            default:
                if (Write((BPTR)fp, (APTR)EVEN, 1) != 1) {
                    Printf("Failed to write even pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
        }
    } while (--i);
 
    /* Seek to the beginning of the file. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            SeekAsync(fp, 0L, MODE_START);
            break;
        default:
            Seek((BPTR)fp, 0L, OFFSET_BEGINNING);
            break;
    }
 
    /* Odd pass: ODD. */
    i = size;
    do {
        /* check for Ctrl-C */
        if(CheckSignal(SIGBREAKF_CTRL_C) != FALSE) 
            longjmp(env, RETURN_WARN);
        switch (AsyncIOBase != NULL) {
            case TRUE:
                if (WriteCharAsync(fp, ODD) != 1) {
                    Printf("Failed to write odd pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
            default:
                if (Write((BPTR)fp, (APTR)ODD, 1) != 1) {
                    Printf("Failed to write odd pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
        }
    } while (--i);
 
    /* Seek to the beginning of the file. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            SeekAsync(fp, 0L, MODE_START);
            break;
        default:
            Seek((BPTR)fp, 0L, OFFSET_BEGINNING);
            break;
    }
 
    /* Get the current time. */
    if(OpenDevice("timer.device", 0, &timereq, 0) != 0) {
        Printf("Unable to open timer device.\n");
        longjmp(env, RETURN_ERROR);
    }
    TimerBase = timereq.io_Device;
    GetSysTime(&sysTime);
    CloseDevice(&timereq);
    /* Initialize the seed from the microseconds. */
    srand((unsigned)sysTime.tv_micro);
 
    /* Random pass. */
    i = size;
    do {
        /* check for Ctrl-C */
        if(CheckSignal(SIGBREAKF_CTRL_C) != FALSE) 
            longjmp(env, RETURN_WARN);
        /* generate random char */
        rnd = rand() % 256;
        switch (AsyncIOBase != NULL) {
            case TRUE:
                if (WriteCharAsync(fp, rnd) != 1) {
                    Printf("Failed to write random pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
            default:
                if (Write((BPTR)fp, (APTR)rnd, 1) != 1) {
                    Printf("Failed to write random pass.\n");
                    longjmp(env, RETURN_ERROR);
                }
                break;
        }
    } while (--i);
 
    /* Close file. */
    switch (AsyncIOBase != NULL) {
        case TRUE:
            CloseAsync(fp);
            CloseLibrary(AsyncIOBase);
            break;
        default:
            Close((BPTR)fp);
            break;
    }
 
    /* Unlock the file for deletion. */
    if(lock != NULL) UnLock(lock);
 
    if (DeleteFile(file) != -1) {
        Printf("Unable to remove file: %s\n", file);
        return RETURN_ERROR;
    }
 
    return RETURN_OK;
}
 
/*************************************************************************/
/*                      Traverses a stack of paths.                      */
/*************************************************************************/
LONG WipePath(stringStack *dirStack, 
    stringStack *bakStack, 
    BOOL quiet, 
    BOOL all) {
 
    struct FileInfoBlock *FIB = NULL;
    UBYTE *path;
    BPTR lock = NULL;
    UBYTE *nextPath;
    ULONG nextSize;
    jmp_buf env;
    UBYTE *ioError = NULL;
    LONG errorCode;
 
    /* jump: Free any resources before returning an error. */
    errorCode = setjmp(env);
    if(errorCode != RETURN_OK) {
        /* Free the file information block. */
        if(FIB != NULL) FreeDosObject(DOS_FIB, FIB);
        /* Release the lock. */
        if(lock != NULL) UnLock(lock);
        /* Print any error message. */
        if(ioError != NULL) Printf("%s\n", ioError);
        /* And return the error code. */
        return errorCode;
    }
 
    /* The directory stack is empty so delete the remaining directories. */
    if(stringStackIsEmpty(dirStack)) {
        while(!stringStackIsEmpty(bakStack)) {
            path = stringStackPop(bakStack);
            if (DeleteFile(path) != -1) {
                ioError = FormatIOError(IoErr(), path, "Not Deleted");
                longjmp(env, RETURN_ERROR);
            }
            if(quiet == FALSE) Printf("%s  Deleted\n", path);
        }
        return RETURN_OK;
    }
 
    /* Pop the top element in the stack. */
    path = stringStackPop(dirStack);
 
    /* Lock the path. */
    if((lock = Lock(path, ACCESS_READ)) == NULL) {
        ioError = FormatIOError(IoErr(), path, "Not Deleted");
        longjmp(env, RETURN_ERROR);
    }
 
    /* Create a new file information block to scan the path. */
    if((FIB = (struct FileInfoBlock *)
        AllocDosObject(DOS_FIB, NULL)) == NULL) {
        Printf("Unable to allocate file information block.\n");
        longjmp(env, RETURN_ERROR);
    }
 
    /* If we cannot examine, then bail. */
    if(Examine(lock, FIB) == FALSE) {
        Printf("Unable to examine file information block.\n");
        longjmp(env, RETURN_ERROR);
    }
 
    /* If the specified path is a file then remove it. */
    switch(FIB->fib_DirEntryType > 0) {
        case TRUE: /* This is a directory. */
            /* Examine the rest of the entries in the directory. */
            while(ExNext(lock, FIB)) {
                /* check for Ctrl-C */
                if(CheckSignal(SIGBREAKF_CTRL_C) != FALSE)
                    longjmp(env, RETURN_WARN);
                nextSize = (strlen(path) + 
                    strlen(FIB->fib_FileName)) * 
                    sizeof(UBYTE *) + 1;
                if((nextPath = (UBYTE*)
                    AllocMem(nextSize, MEMF_ANY|MEMF_CLEAR)) == NULL) {
                    Printf("Unable to allocate memory for path.\n");
                    longjmp(env, RETURN_ERROR);
                }
                CopyMem(path, nextPath, nextSize);
                if(!AddPart(nextPath, FIB->fib_FileName, nextSize)) {
                    Printf("Unable to add the file to the path.\n");
                    longjmp(env, RETURN_ERROR);
                }
                /* Check whether the path is a file or a directory. */
                switch(FIB->fib_DirEntryType > 0) {
                    case TRUE: // We have a directory.
                        /* So we push it onto the stack if recursive
                         * mode was selected.
                         */
                        if(all == TRUE)
                            stringStackPush(dirStack, nextPath);
                        break;
                    default: // We have a file.
                        // So we delete it.
                        if(Wipe(nextPath) != 0) {
                            ioError = FormatIOError(
                                IoErr(), 
                                nextPath, 
                                "Not Wiped"
                            );
                            longjmp(env, RETURN_ERROR);
                        }
                        if(quiet == FALSE) 
                            Printf("%s  Wiped\n", nextPath);
                        break;
                }
            }
            /* If we cannot examine, then bail. */
            if(Examine(lock, FIB) == FALSE) {
                Printf("Unable to examine file information block.\n");
                longjmp(env, RETURN_ERROR);
            }
            /* Check to see if the directory has further entries. */
            switch(!ExNext(lock, FIB) && 
                IoErr() == ERROR_NO_MORE_ENTRIES) {
                case FALSE: /* Directory is not empty. */
                    if(FIB != NULL) FreeDosObject(DOS_FIB, FIB);
                    if(lock != NULL) UnLock(lock);
                    /* If we are not recursively wiping and deleting
                     * then do not add the directory for removal.
                     */
                    if(all == FALSE) break;
                    /* Push it onto the back-tracking stack. */
                    stringStackPush(bakStack, path);
                    break;
                default: /* Directory is empty. */
                    /* If we are not recursively wiping and deleting
                     * then do not remove the directory.
                     */
                    if(all == FALSE) break;
                    if(FIB != NULL) FreeDosObject(DOS_FIB, FIB);
                    if(lock != NULL) UnLock(lock);
                    /* So we delete it. */
                    if (DeleteFile(path) != -1) {
                        ioError = FormatIOError(
                            IoErr(), 
                            path, 
                            "Not Deleted"
                        );
                        longjmp(env, RETURN_ERROR);
                    }
                    if(quiet == FALSE) Printf("%s  Deleted\n", path);
                    break;
            }
            break;
        default: /* This is a file. */
            if(FIB != NULL) FreeDosObject(DOS_FIB, FIB);
            if(lock != NULL) UnLock(lock);
            if (DeleteFile(path) != -1) {
                ioError = FormatIOError(
                    IoErr(), 
                    path, 
                    "Not Wiped"
                );
                longjmp(env, RETURN_ERROR);
            }
            if(quiet == FALSE) Printf("%s  Wiped\n", path);
            break;
    }
 
    /* Recurse over the stack till it is empty. */
    return WipePath(dirStack, bakStack, quiet, all);
}
 
int main(int argc, char **argv) {
    struct AnchorPath *AP = NULL;
    stringStack *pathStack = NULL;
    BOOL quiet = FALSE;
    BOOL all = FALSE;
    UBYTE *readPath;
    ULONG pathSize;
    jmp_buf env;
    LONG errorCode;
    UBYTE *ioError = NULL;
 
    /* Setup command-line arguments to retrieve file.*/
    LONG *arg_array;
    struct RDArgs *rdargs = NULL;
    UBYTE **path;
 
    /* jump: Free any resources before returning an error. */
    errorCode = setjmp(env);
    if(errorCode != RETURN_OK) {
        /* Free any command-line arguments. */
        if(rdargs != NULL) FreeArgs(rdargs);
        /* Free the stack. */
        if(pathStack != NULL) FreeMem(pathStack, sizeof(pathStack));
        /* Free the anchor path. */
        if(AP != NULL) MatchEnd(AP);
        /* Print any error message. */
        if(ioError != NULL) Printf("%s\n", ioError);
        /* And return the error code. */
        return errorCode;
    }
 
    /* Allocate memory for arg array. */
    if((arg_array = AllocMem(NUM_ARGS, MEMF_ANY|MEMF_CLEAR)) == NULL) {
        Printf("Unable to allocate memory for command-line arguments.\n");
        longjmp(env, RETURN_ERROR);
    }
 
    /* Read command-line arguments. */
    if((rdargs = ReadArgs(template, arg_array, NULL)) == NULL) {
        Printf("Could not read command-line arguments.\n");
        longjmp(env, RETURN_ERROR);
    }
 
    /* Should we be quiet about deletions and wipes? */
    if(arg_array[QUIET] == -1) quiet = TRUE;
    /* Should we recursively wipe and delete? */
    if(arg_array[ALL] == -1) all = TRUE;
 
    /* Now we go through all the paths / patterns given by the user on the
     * command-line and try to find the paths / patterns in order to add
     * them to a new string stack. 
     */
    pathStack = stringStackCreate(1);
    for(path = (UBYTE **)arg_array[PATH]; *path; ++path) {
        /* Alocate the anchor path with ap_Buf initalized to the size of
         * the path set to the maximum path size since we do not know how
         * large the path expansion will become.
         */
        if((AP = 
            AllocMem(
                sizeof(struct AnchorPath) + PATHNAME_MAX, 
                MEMF_ANY|MEMF_CLEAR
            )
        ) == NULL) {
            Printf("Could not allocate memory for the anchor path.\n");
            longjmp(env, RETURN_ERROR);
        }
 
        /* Set the length of the ap_Buf in ap_Strlen. */
        AP->ap_Strlen = PATHNAME_MAX;
 
        /* Perform the first match. */
        if(MatchFirst(*path, AP) != 0) {
            ioError = FormatIOError(IoErr(), *path, "No Match");
            longjmp(env, RETURN_ERROR);
        }
        /* While there are matches for the patterns, add the expanded
         * files and paths to the stack.
         */
        do {
            /* Now copy the path name to a new location. */
            pathSize = strlen(AP->ap_Buf) * sizeof(UBYTE *) + 1;
            if((readPath = 
                AllocMem(pathSize, MEMF_ANY|MEMF_CLEAR)) == NULL) {
                Printf("Could not allocate memory for the full path.\n");
                longjmp(env, RETURN_ERROR);
            }
            CopyMem(AP->ap_Buf, readPath, pathSize);
            /* ... And push it onto the stack. */
            stringStackPush(pathStack, readPath);
        } while(MatchNext(AP) == 0);
        MatchEnd(AP);
    }
 
    FreeArgs(rdargs);
 
    /* Now traverse the stack and wipe all files and return any errors. */
    return WipePath(pathStack, stringStackCreate(1), quiet, all);
}

amiga/development/os3/securely_delete_files.txt · Last modified: 2022/04/19 08:28 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.