/*************************************************************************/ /* 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 #include #include #include #include #include #include #include #include #include #include #include #include /*************************************************************************/ /* 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); }