summaryrefslogtreecommitdiff
path: root/lib/rlib.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rlib.c')
-rw-r--r--lib/rlib.c591
1 files changed, 591 insertions, 0 deletions
diff --git a/lib/rlib.c b/lib/rlib.c
new file mode 100644
index 0000000..db84548
--- /dev/null
+++ b/lib/rlib.c
@@ -0,0 +1,591 @@
+/*
+ * AUTHOR
+ * N. Nielsen
+ *
+ * LICENSE
+ * This software is in the public domain.
+ *
+ * The software is provided "as is", without warranty of any kind,
+ * express or implied, including but not limited to the warranties
+ * of merchantability, fitness for a particular purpose, and
+ * noninfringement. In no event shall the author(s) be liable for any
+ * claim, damages, or other liability, whether in an action of
+ * contract, tort, or otherwise, arising from, out of, or in connection
+ * with the software or the use or other dealings in the software.
+ *
+ * SUPPORT
+ * Send bug reports to: <nielsen@memberwebs.com>
+ */
+
+/* =========================================================================
+ * Main RLIB
+ * 2000-2001 Copyright, Nate Nielsen
+ */
+
+#include <stdarg.h>
+#include "common/usuals.h"
+#include "common/compat.h"
+#include "lib/rlib.h"
+#include "priv.h"
+#include "rep.h"
+#include "common/binfile.h"
+#include "common/repfile.h"
+
+/* rlibInit: --------------------------------------------------------------
+ * Initialize a stream. We already expect stream to be zero'd
+ */
+
+int rlibInit(r_stream* stream, long options)
+{
+ byte* op = NULL;
+ int i, j;
+ struct internal_state* state;
+
+ if(stream->state != NULL)
+ return R_INVARG;
+
+ /* Allocate a state */
+ state = stream->state =
+ (struct internal_state*)malloc(sizeof(struct internal_state));
+
+ if(!state)
+ return R_NOMEM;
+
+ zero(*state);
+
+ /* Init the stream for the current session */
+ if(!vmInit(stream))
+ return R_NOMEM;
+
+ state->options = options;
+
+ /* Init our case table */
+ /* we only do this once for the whole bunch */
+ for (i = 0; i < 256; i++)
+ stream->state->caseTranslate[i] = i;
+ for (j = 'a'; j <= 'z'; j++)
+ stream->state->caseTranslate[j] = j - ('a' - 'A');
+
+ return R_OK;
+}
+
+
+/* rlibCompile: ----------------------------------------------------
+ * Compile a script
+ */
+int rlibCompile(r_script* script, const char* data)
+{
+ return compilerRun(script, data);
+}
+
+
+/* rlibClear: -------------------------------------------------------------
+ * Prepare stream for a new file
+ */
+void rlibClear(r_stream* stream)
+{
+ if(stream)
+ {
+ if(stream->state)
+ vmClean(stream);
+ }
+}
+
+
+
+/* rLibRun: ---------------------------------------------------------------
+ * The replacement coordinator, output etc...
+*/
+int rlibRun(r_stream* stream, r_script* script, int done)
+{
+ #define RETURN(r) do { retv = r; goto finally; } while(0)
+
+ struct internal_state* state = stream->state;
+ size_t backup, curOffset;
+ int retv = R_OK;
+
+ if(!stream->state)
+ return R_INVARG;
+
+ /* Usually have to have an output function, unless just a matcher */
+ if(!stream->fWrite && !(state->options & RLIB_MODE_MATCHER))
+ return R_INVARG;
+ /* And if we are a matcher we have to have a match function */
+ if(state->options & RLIB_MODE_MATCHER && !stream->fMatch)
+ return R_INVARG;
+
+
+ /*
+ * Keep at least 1/2 amount of backup
+ * Backup is only kept if no matches occur in backup area
+ */
+ backup = done ? 0 : stream->availIn / 2;
+
+ /*
+ * Since there are conversions (between absolute and relative offsets)
+ * below, we need to keep inOffset the same for this session. So copy
+ * it and update it again at the end of the function.
+ */
+ curOffset = 0;
+
+ /* Need some data to work with */
+ if(!stream->nextIn || !stream->availIn)
+ RETURN(R_IN);
+
+
+ stream->total = 0;
+
+ /* Execute the script */
+ retv = vmExecute(stream, script);
+
+ /* Error? */
+ if(retv < 0)
+ RETURN(retv);
+
+ /* Fall through to commit code */
+ if(retv == R_DONE)
+ backup = 0;
+
+ if(!(state->options & RLIB_MODE_MATCHER))
+ {
+ /*
+ * Here we write out what we've found so far and advance the
+ * pointers
+ *
+ * On the first round we look something like this
+ *
+ * <- state->searched ->
+ * |---------------------------------------------| <- stream->nextIn
+ * |----------------------------------------| <- stream->nextOut
+ * replacements found ^- leftovers
+ */
+
+ /* toWrite is the size of the current block we need to transfer */
+ size_t toWrite;
+ /* block is the amount we can actually transfer (depending on output buffer) */
+ size_t block = stream->availIn;
+
+#ifdef VERBOSE
+ dumpRelpacements(stream);
+#endif
+
+ while(state->replaces)
+ {
+ /* Get the amount of data till the current replace */
+ toWrite = block = ABS_TO_REL(state->replaces->beg, state) - curOffset;
+
+ if(!(state->options & RLIB_MODE_PARSER))
+ {
+ /* ... write out up to current replacement... */
+ if(!(stream->fWrite)(stream, stream->nextIn, block))
+ RETURN(R_IOERR);
+ }
+
+ stream->nextIn += block;
+ stream->availIn -= block;
+ curOffset += block;
+
+
+ /* ... check space for replacement ... */
+ block = strlen(state->replaces->text);
+
+ /* ...write out replacement... */
+ if(!(stream->fWrite)(stream, (byte*)state->replaces->text, block))
+ RETURN(R_IOERR);
+
+ /* ... and skip (!) the text that we replaced */
+ block = state->replaces->end - state->replaces->beg;
+ stream->nextIn += block;
+ stream->availIn -= block;
+ curOffset += block;
+
+#ifdef _DEBUG
+ /* Check if things are in the right order */
+ if(state->replaces->next)
+ ASSERT(state->replaces->end <= state->replaces->next->end);
+#endif
+ /* Go to the next replacement */
+ state->replaces = replacementPop(state->replaces);
+ }
+
+ /*
+ * Now we check how much data we have left and copy
+ * up to backup if we have more
+ */
+
+ /* Copy out till backup marker */
+ if((stream->availIn) >= backup)
+ {
+ /* Get block size ... */
+ toWrite = block = stream->availIn - backup;
+
+ if(!(state->options & RLIB_MODE_PARSER))
+ {
+ if(!(stream->fWrite)(stream, stream->nextIn, block))
+ RETURN(R_IOERR);
+ }
+
+ stream->nextIn += block;
+ stream->availIn -= block;
+ curOffset += block;
+ }
+ }
+
+ /*
+ * After this the search could start anew
+ * unless in flush mode or done
+ */
+ if(done == true)
+ RETURN(R_DONE);
+ else
+ RETURN(R_IN);
+
+finally:
+ state->offset += curOffset;
+
+ return retv;
+}
+
+
+/* rlibSetVar: -----------------------------------------------------------
+ * Set a variable for the script to use
+ */
+int rlibSetVar(r_stream* stream, const char* var, const char* val)
+{
+ if(!stream || !stream->state || !var || !val)
+ return R_INVARG;
+
+ variablesClear(&(stream->state->vars), var);
+ return variablesAdd(&(stream->state->vars), var, val) ? R_OK : R_NOMEM;
+}
+
+
+/* rlibDump: --------------------------------------------------------------
+ * Dump the rep script opts for debugging
+ */
+void rlibDump(r_script* script, FILE* f)
+{
+ opsDump(script->ops, f);
+}
+
+
+/* rlibFree: --------------------------------------------------------------
+ * Free associated structures data etc...
+ */
+void rlibFree(r_stream* stream, r_script* script)
+{
+ struct internal_state* state;
+
+ if(stream)
+ {
+ if(state = stream->state)
+ {
+ /* Let execution free it's stuff */
+ vmFree(stream);
+
+ /* And free the state */
+ free(state);
+ stream->state = NULL;
+ }
+
+ zero(*stream);
+ }
+
+ if(script)
+ {
+ if(script->ops)
+ free(script->ops);
+
+ if(script->error)
+ free(script->error);
+
+ zero(*script);
+ }
+
+}
+
+
+/* scriptSetError: -------------------------------------------------
+ * Set the stream error text to whatever
+ */
+void scriptSetError(r_script* script, const char* format, ...)
+{
+ char* msg;
+
+ va_list vl;
+ va_start(vl, format);
+
+ if(script->error)
+ free(script->error);
+
+ if(vasprintf(&msg, format, vl) != -1)
+ script->error = msg;
+ else
+ script->error = NULL;
+
+ va_end(vl);
+}
+
+/* compileAlready: ------------------------------------------------------
+ * See if the file has already been compiled and load if so
+ */
+int compileAlready(r_context* ctx, FILE* file)
+{
+ /* Should have already read header */
+ bfval val;
+ BFILE h = NULL;
+ int retv = R_OK;
+ r_uint temp;
+
+ if(!(h = bfStartFile(file)))
+ RETURN(R_INVARG);
+
+ if(!repfReadHeader(h))
+ RETURN(R_IN);
+
+ while(bfReadValueInfo(h, &val))
+ {
+ if(ferror(file))
+ RETURN(R_IOERR);
+
+ if(feof(file))
+ RETURN(R_INVARG);
+
+ switch(val.id)
+ {
+ case REPVAL_BUFSIZE:
+ if(ctx->block == 0)
+ bfReadValueData(h, &val, &(ctx->block));
+ continue;
+
+ case REPVAL_PARSEMODE:
+ if(bfReadValueData(h, &val, &temp))
+ ctx->options |= (temp != 0) ? RLIB_MODE_PARSER : 0;
+ continue;
+
+ case REPVAL_SCRIPT:
+ {
+ ctx->script.ops = (r_byte*)malloc(val.len);
+ if(!ctx->script.ops)
+ RETURN(R_NOMEM);
+
+ bfReadValueData(h, &val, ctx->script.ops);
+ ctx->script.len = val.len;
+ continue;
+ }
+ break;
+ }
+
+ bfSkipValueData(h, &val);
+ }
+
+finally:
+ if(h)
+ bfClose(h);
+
+ return retv;
+}
+
+
+int repLoad(r_context* ctx, FILE* f)
+{
+ int retv = R_OK;
+ char* buff = NULL;
+ size_t len;
+ int r;
+
+ rlibFree(NULL, &(ctx->script));
+
+ /* Okay now try and read header, and possibly process
+ an already executable file. */
+ switch(r = compileAlready(ctx, f))
+ {
+ /* It's already compiled */
+ case R_OK:
+ RETURN(R_OK);
+ break;
+
+ /* It's not compiled so compile */
+ case R_IN:
+ break;
+
+ /* Failed processing */
+ default:
+ if(r < 0)
+ RETURN(r);
+ break;
+ };
+
+ /* Get file size */
+ len = 0;
+
+ if(fseek(f, 0, SEEK_END) || !(len = ftell(f)) ||
+ fseek(f, 0, SEEK_SET))
+ RETURN(R_IOERR);
+
+ buff = (char*)malloc(len + 1);
+ if(!buff)
+ RETURN(R_NOMEM);
+
+ if(fread(buff, 1, len, f) != len)
+ RETURN(R_IOERR);
+
+ /* Needs compiling */
+ buff[len] = '\0';
+
+ /* Init the stream */
+ r = rlibCompile(&(ctx->script), buff);
+ if(r < 0)
+ RETURN(r);
+
+finally:
+ if(buff)
+ free(buff);
+
+ return retv;
+}
+
+int repInit(r_context* ctx)
+{
+ rlibInit(&(ctx->stream), ctx->options);
+ return R_OK;
+}
+
+int repFile(r_context* ctx, FILE* fIn)
+{
+ r_uint batch; /* Current batch size */
+ r_uint block = ctx->block; /* Block size */
+ r_byte* buff = NULL; /* Input buffer */
+ int retv = R_OK;
+ int r = R_IN;
+ int total = 0;
+
+ if(!block)
+ {
+ if(!fseek(fIn, 0, SEEK_END) && (block = ftell(fIn)) != ~0)
+ block++;
+
+ else
+ block = 0;
+
+ fseek(fIn, 0, SEEK_SET);
+ }
+
+ else
+ {
+ block *= 2;
+ }
+
+ /* Okay now if it's a sane value just allocate it */
+ if(block)
+ {
+ if(block > 0x0F00000)
+ RETURN(R_NOMEM);
+
+ /* Allocate buffers */
+ buff = (r_byte*)malloc(block);
+ if(!buff)
+ RETURN(R_NOMEM);
+ }
+
+ /* Hook buffers to the stream */
+ ctx->stream.nextIn = buff;
+ ctx->stream.availIn = 0;
+
+ /* While we either have more data to put in or more output... */
+ while(!feof(fIn) || r == R_DONE)
+ {
+ /* If rlib wants data then give it */
+ if(r == R_IN)
+ {
+ /* This is a normal standard read */
+ if(buff)
+ {
+ /* Move data to front */
+ memmove(buff, ctx->stream.nextIn, ctx->stream.availIn);
+
+ /* Set pointer to data */
+ ctx->stream.nextIn = buff;
+
+ /* Read rest of block */
+ batch = fread(ctx->stream.nextIn + ctx->stream.availIn,
+ sizeof(r_byte), block - ctx->stream.availIn, fIn);
+
+ ctx->stream.availIn += batch;
+
+ if(ferror(fIn))
+ RETURN(R_IOERR);
+ }
+
+ /* Here we read as much as possible in one shot allocating as we go*/
+ else
+ {
+ batch = 0;
+ block = 0;
+
+ while(!feof(fIn))
+ {
+ if(ferror(fIn))
+ RETURN(R_IOERR);
+
+ block += 0x4000;
+
+ if(block > MAX_BUFF ||
+ !(buff = reallocf(buff, block)))
+ RETURN(R_NOMEM);
+
+ batch += fread(buff + batch, sizeof(r_byte),
+ 0x4000, fIn);
+ }
+
+ ctx->stream.nextIn = buff;
+ ctx->stream.availIn += batch;
+ }
+ }
+
+ /* call rlib */
+ r = rlibRun(&(ctx->stream), &(ctx->script), feof(fIn));
+
+ // Oops!
+ if(r < 0)
+ RETURN(r);
+
+ total += ctx->stream.total;
+ }
+
+finally:
+ if(buff)
+ free(buff);
+
+ ctx->stream.total = total;
+
+ return retv;
+}
+
+int fileOutput(struct r_stream* stream, byte* data, size_t len)
+{
+ FILE* f = (FILE*)stream->arg;
+ return (fwrite(data, 1, len, f) == len) && (!ferror(f));
+}
+
+int repFiles(r_context* ctx, FILE* fIn, FILE* fOut)
+{
+ void* arg = ctx->stream.arg;
+ r_write func = ctx->stream.fWrite;
+ int ret;
+
+ ctx->stream.fWrite = fileOutput;
+ ctx->stream.arg = fOut;
+
+ ret = repFile(ctx, fIn);
+
+ ctx->stream.fWrite = func;
+ ctx->stream.arg = arg;
+
+ return ret;
+}
+
+void repFree(r_context* ctx)
+{
+ rlibFree(&(ctx->stream), &(ctx->script));
+}