/* * 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: */ /* ========================================================================= * Main RLIB * 2000-2001 Copyright, Nate Nielsen */ #include #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) { 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)) { free(state); stream->state = NULL; 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(done ? R_DONE : 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) 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; } #ifdef _DEBUG void* memstr(void* mem, size_t sz, const char* str) { size_t len = strlen(str); const char* t; const char* e; ASSERT(str && mem); if(len > sz) return NULL; for(t = (const char*)mem, e = t + (sz - len); t < e; t++) { if(memcmp(t, str, len) == 0) return (void*)t; } return NULL; } #endif 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(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)); }