/* * 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: */ #include #include #include #include #include #include #include #include #include "common/usuals.h" #include "common/compat.h" #include "lib/rlib.h" #include "lib/rep.h" #include "common/repfile.h" #include "file.h" /* Amount of context that will be shown in a prompt surrounding the text */ #define CONFIRM_CONTEXT 0x20 /* Extension used for backup files */ const char* kBackupExt = ".x_r"; /* ------------------------------------------------------------------------ * GLOBALS */ /* * Since WIN32 has a hokey (did I say hokey, I meant crappy) shell * we add a bit more file selection functionality if building there. * We allow a set wildcards to be specified. These apply when recursing * folders. */ #ifdef _WIN32 #define MAX_WILDCARDS 0x20 const char* kAllFiles = "*.*"; /* When recursing through folders this is the set of files to replace */ char* g_wildcards[MAX_WILDCARDS]; #endif /* Quiet mode or not? */ bool g_showStatus = true; /* Backups or not? */ bool g_keepBackups = false; /* * For status updates the amount of replacements and the amount of * files seen */ long g_totalReplaces = 0; long g_totalFiles = 0; /* ------------------------------------------------------------------------ * FORWARD DECLARATIONS */ /* The two match callbacks */ int matchConfirm(r_stream* stream, r_replace* repl); int matchStatus(r_stream* stream, r_replace* repl); /* The main replace functions */ int replaceFolder(r_context* ctx, char* szWildCard); int replaceFile(r_context* ctx, FILE* fIn, FILE* fOut); int replaceFilename(r_context* ctx, const char* szIn, const char* szOut); int replaceSingleFile(r_context* ctx, const char* szIn); /* Error stuff */ int usage(); /* ------------------------------------------------------------------------ */ int main(int argc, char* argv[]) { /* We use one r_stream throughout for speed */ r_context context; bool fileMode = false; int ch = 0; /* * Function return values throughout program are a bit different * * 0 = normal operation * -1 = nothing happened but successful * >0 = error code */ int ret = 0; int r = R_OK; /* Enough params? */ if(argc < 2) return usage(); /* Init the stream/context */ memset(&context, 0, sizeof(context)); /* A little preparation */ context.stream.fMatch = matchStatus; #ifdef _WIN32 *g_wildcards = NULL; while((ch = getopt(argc, argv, "bcipqw:z:")) != -1) #else while((ch = getopt(argc, argv, "bcipqz:")) != -1) #endif { switch(ch) { /* Use backups */ case 'b': g_keepBackups = true; break; case 'i': fileMode = true; break; /* Confirmation mode */ case 'c': context.stream.fMatch = matchConfirm; break; /* Quiet mode */ case 'q': g_showStatus = false; break; /* Parse mode */ case 'p': context.options |= RLIB_MODE_PARSER; break; #ifdef _WIN32 case 'w': { /* Get all the wildcards out */ size_t len = 0; char* arg = optarg; char** wildcards = g_wildcards; /* * Enumerate the wild cards seperating them * at semi-colons or colons */ while((len = strcspn(arg, ";")) && (wildcards < (g_wildcards + MAX_WILDCARDS))) { bool last = (arg[len] == '\0'); arg[len] = '\0'; *wildcards = arg; wildcards++; arg += len; arg++; if(last) break; } /* Null terminate the array */ *wildcards = NULL; } break; #endif case 'z': context.block = atoi(optarg); if(context.block <= 0x20) errx(2, "invalid argument. specify block size greater than 32."); break; case '?': default: return usage(); break; } } argc -= optind; argv += optind; #ifdef _WIN32 if(*g_wildcards == NULL) { g_wildcards[0] = "*.*"; g_wildcards[1] = NULL; } #endif /* * The next param should be the script file * so read it... */ if(argc > 0) { FILE* file = fopen(argv[0], "rb"); if(!file) err(1, "couldn't open rep script file: %s", argv[0]); r = repLoad(&context, file); fclose(file); if(r < 0) exit(errmsg(r, &(context.script))); } else return usage(); /* * Now initialize the rlib library structures */ r = repInit(&context); if(r < 0) exit(errmsg(r, NULL)); argc--; argv++; /* * Okay now we have two options: * * If -f was specified we read each argument and replace the file in place, * possibly with backups, descend into folders etc... * * Or if not specified we read from the in file and output to the outfile * If out or both in and out are not specified then use stdin/stdout */ if(!fileMode) { char* in = "-"; char* out = "-"; int ret; if(argc > 2) usage(); if(argc > 0) in = argv[0]; if(argc > 1) out = argv[1]; ret = rlibSetVar(&(context.stream), "FILENAME", in); if(ret < 0) ret = errmsg(ret, &(context.script)); else ret = replaceFilename(&context, in, out); } else { while(argc > 0) { if(isDirectory(argv[0])) ret = replaceFolder(&context, argv[0]); else ret = replaceSingleFile(&context, argv[0]); if(ret > 0) break; argc--; argv++; } } /* Release the stream */ repFree(&context); /* Give some stats */ if(ret == 0 && g_showStatus) fprintf(stderr, "\n[%d replacements in %d files]\n", (int)g_totalReplaces, (int)g_totalFiles); /* Done! */ return ret <= 0 ? 0 : ret; } /* replaceFolder: -------------------------------------------------------------- * Enumerate all files in a folder and do a replace on each one using * backups (Note: this function is called recursively) */ int replaceFolder(r_context* ctx, char* folder) { struct dirent ent; char old[260]; /* Save the current folder (as we'll change it) */ int ret = 0; DIR* dir; /* * We use a null wildcard as the default and replace below * if running under WIN32 */ char* wildcard = NULL; char** wildcards = &wildcard; /* Backup the current directory */ if(!getcwd(old, 260)) err(1, "couldn't get working folder"); #ifdef _WIN32 /* * Now loop through all the wildcards and search for them * in the folder */ wildcards = g_wildcards; while(*wildcards) { #endif dir = dir_first(folder, *wildcards, &ent); if(dir == INVALID_DIR && errno != ENOENT) err(1, "couldn't list path: %s", folder); /* Any files */ if(dir != INVALID_DIR) { /* Change dir to the folder we're searching in */ chdir(folder); /* And for every file */ do { if(!(ent.d_type & DT_DIR)) { int r = 0; if(g_showStatus) fprintf(stderr, "%s ", ent.d_name); r = replaceSingleFile(ctx, ent.d_name); if(g_showStatus) fputc('\n', stderr); if(r > 0) return r; if(r > ret) ret = r; } } while(dir_next(dir, *wildcards, &ent)); if(errno != 0 && errno != ENOENT) err(1, "couldn't list path: %s", folder); dir_close(dir); /* * Change directory back to where we saved it * the reason we do this is that in some cases (sub-folders) */ chdir(old); } #ifdef _WIN32 wildcards++; } #endif /* Now we enumerate all the folders */ dir = dir_first(folder, NULL, &ent); if(dir == INVALID_DIR && errno != ENOENT) err(1, "couldn't list path: %s", folder); /* Any files? */ if(dir != INVALID_DIR) { /* Go into folder */ chdir(folder); do { /* For each folder... */ if(ent.d_type & DT_DIR) { int r = 0; /* ... that's not dots ... */ if(isDots(ent.d_name)) continue; /* ... recursively call ourselves */ ret = replaceFolder(ctx, ent.d_name); if(r > 0) return r; if(r > ret) ret = r; } } while(dir_next(dir, NULL, &ent)); if(errno != 0 && errno != ENOENT) err(1, "couldn't list path: %s", folder); dir_close(dir); /* Change directory back */ chdir(old); } /* Did anything change? */ return ret; } /* replaceSingleFile: ------------------------------------------------------- * Replace a file into itself using temp/backups */ int replaceSingleFile(r_context* ctx, const char* file) { int ret = 2; char temp [260 + 4]; /* Temp buffer for filenames */ ret = rlibSetVar(&(ctx->stream), "FILENAME", file); if(ret < 0) return errmsg(ret, &(ctx->script)); /* Make a temp/backup name */ strcpy(temp, file); strcat(temp, kBackupExt); /* Now we do this 2 ways, if we're keeping backups then... */ if(g_keepBackups) { /* Rename the original file to the backup */ if(rename(file, temp)) err(1, "couldn't make backup file: %s", temp); /* * Do a replacement operation back to the original * from the backup */ ret = replaceFilename(ctx, temp, file); /* If there were no replacements */ if(ret == -1) { /* Remove the replacement file */ unlink(file); /* And rename the backup file back */ if(rename(temp, file)) err(1, "couldn't rename file: %s", file); } } /* No backups, much faster! */ else { /* Do a replacement operation to a temp file */ ret = replaceFilename(ctx, file, temp); /* If there were replacements */ if(ret == 0) { /* Remove original file */ unlink(file); /* Rename temp to original */ if(rename(temp, file)) err(1, "couldn't rename file: %s", file); } /* If no replacements */ else { /* Remove temp file */ unlink(temp); } } return ret; } /* replaceFilename: ----------------------------------------------------------- * Replace one file into another */ int replaceFilename(r_context* ctx, const char* in, const char* out) { FILE* fIn; FILE* fOut; int ret; if(!strcmp(in, "-")) { /* * If someone specified stdin as the input make sure we're * not going to put up confirmations as we need input from * stdin ie: YNA or whatever key */ if(ctx->stream.fMatch == matchConfirm) errx(2, "can't confirm replacements while using stdin as input"); /* Input is stdin */ fIn = stdin; } /* Input is a file */ else { #ifdef _WIN32 if(!(fIn = fopen(in, "rb"))) #else if(!(fIn = fopen(in, "r"))) #endif err(1, "couldn't open input file: %s", in); } /* Output is stdout */ if(!strcmp(out, "-")) fOut = stdout; /* Output is a file */ #ifdef _WIN32 else if(!(fOut = fopen(out, "wb"))) #else else if(!(fOut = fopen(out, "w"))) #endif err(1, "couldn't open output file: %s", in); /* Do replacements! */ ret = replaceFile(ctx, fIn, fOut); /* Close any files */ if(fIn != stdin) fclose(fIn); if(fOut != stdout) fclose(fOut); return ret; } /* matchConfirm: ----------------------------------------------------------------- * Confirmation callback for rlib. Used when -i flag is given */ int matchConfirm(r_stream* stream, r_replace* repl) { /* Remember if someone pressed A */ static bool all = false; char ch = 0; int ret; size_t context_start, context_end; if(all) return 1; fprintf(stderr, "\n------------- ORIGINAL TEXT between ~{ and }~ markers ---------------\n"); /* Figure out if we have some context before replacement */ /* and how much */ context_start = 0; if(repl->from >= CONFIRM_CONTEXT) context_start = repl->from - CONFIRM_CONTEXT; /* Figure out if we have context after replacement */ context_end = stream->availIn; if(stream->availIn >= repl->from + repl->flen + CONFIRM_CONTEXT) context_end = repl->from + repl->flen + CONFIRM_CONTEXT; /* Write Context Before */ fwrite(stream->nextIn + context_start, sizeof(char), repl->from - context_start, stderr); /* Write our tag */ fprintf(stderr, "~{"); /* Write from text */ fwrite(stream->nextIn + repl->from, sizeof(char), repl->flen, stderr); /* Write tag */ fprintf(stderr, "}~"); /* Write Context After */ fwrite(stream->nextIn + repl->from + repl->flen, sizeof(char), context_end - (repl->from + repl->flen), stderr); /* Separator */ fprintf(stderr, "\n----------------------------- NEW TEXT -------------------------------\n"); // Replace with this text fwrite(repl->to, sizeof(byte), repl->tlen, stderr); /* Prompt */ fprintf(stderr, "\n----------------------------------------------------------------------\n"); fprintf(stderr, "(YES\\No\\All)? "); setbuf(stdin, NULL); while(ch != 'y' && ch != 'n' && ch != 'a' && ch != '\r') ch = tolower(getchar()); if(ch == 'a') stream->fMatch = matchStatus; fprintf(stderr, "\r \r"); ret = (ch == 'y' || ch == 'a' || ch == '\r') ? true : false; if(ret) g_totalReplaces++; return ret; } /* matchStatus: ---------------------------------------------------------- * Confirmation function for rlib. Used for status display */ int matchStatus(r_stream* stream, r_replace* repl) { if(g_showStatus) fputc('.', stderr); g_totalReplaces++; return 1; } /* outputMsg: -------------------------------------------------------------- * Callback for rlib when a message goes to the console. */ void outputMsg(struct r_stream* stream, const char* data) { fprintf(stderr, g_showStatus ? "\n%s " : "%s\n", data); } /* replaceFile: ------------------------------------------------------------ * Replace one file into another using file handles. This is where * the main stuff actually happens. */ int replaceFile(r_context* ctx, FILE* fIn, FILE* fOut) { int ret; ctx->stream.fMessage = outputMsg; ctx->stream.total = 0; ret = repFiles(ctx, fIn, fOut); /* fatal errors */ if(ret < 0) return errmsg(ret, &(ctx->script)); ret = (ctx->stream.total > 0) ? 0 : -1; g_totalFiles++; rlibClear(&(ctx->stream)); return ret; } /* usage: ------------------------------------------------------------- * Display usage info */ int usage() { #ifdef _WIN32 fprintf(stderr, "usage: rep [-cpqx] [ -z buffsize ] script infile outfile\n"); fprintf(stderr, " rep -i [-bcpqx] [ -z buffsize ] [ -w wildcard ] script file ...\n"); #else fprintf(stderr, "usage: rep [-cpq] [ -z buffsize ] script infile outfile\n"); fprintf(stderr, " rep -i [-bcpq] [ -z buffsize ] script file ...\n"); #endif return 2; }