// *naka* - find a file within another file #include #include #include #include #include #include #include #include #include #include #include #include static const char* _compdate_readable() { static const time_t comp = PROG_COMPILED_TIMESTAMP; #define DATE_MAX 20 static _Thread_local char date[DATE_MAX] = {0}; debug_assert(sizeof(date) == DATE_MAX); struct tm* time = gmtime(&comp); if(!date[0]) { usize w = strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", time); DBG(ifU(!w || w>=DATE_MAX) FATAL("Increase DATE_MAX, %lu is too low (strftime(): %lu)", DATE_MAX, w)); TRACE("size of hr time: %lu / %lu", w, sizeof(date)); } return date; #undef DATE_MAX } static void pi_print_trace_infos(FILE* out) { const char* name; enum trace_level cmax = trace_max_level(); for(int i=0;i<(int)_TRACE_LEVEL_NUM;i++) { enum trace_level lv = (enum trace_level)i; name = trace_name_of(lv); if(name) { debug_assert(AS(lv, int) < AS(_TRACE_LEVEL_NUM, int) && AS(lv, int) >= 0); const char* desc = lv[_trace_level_descriptions]; assert(desc); fprintf(out, "-> level %d: \"%s\" - %s%s\n", i, name, desc, cmax==lv ? " (currently set)" : ""); } } enum trace_level def = _TRACE_LEVEL_DEFAULT; name = trace_name_of(def); debug_assert(name); fprintf(out, "(default level: %d, \"%s\")\n", def, name); fprintf(out, "Set `LOG_LEVEL=` to control logging verbosity level at runtime. The name string is case-sensitive.\n"); } static void prog_info(FILE* out) { fprintf(out, PROG_NAME " v%s - " PROG_DESCRIPTION #ifdef DEBUG " (debug build)" #endif "\n written by %s with <3 (compiled at %s UTC." #ifdef DEBUG " %lu uts)" #else ")" #endif "\n license %s.\n", v_ctoss(v_rawtoc(PROG_VERSION)), PROG_AUTHOUR, _compdate_readable(), #ifdef DEBUG PROG_COMPILED_TIMESTAMP, #endif PROG_LICENSE); fprintf(out, "\nLogging levels: (set with env-var `%s')\n", _TRACE_CONTROL_ENV_NAME); pi_print_trace_infos(out); } void usage(FILE* out, int argc, char** argv) { IGNORE(argc); prog_info(out); fprintf(out, "\nUsage: %s \n", argv[0] ?: PROG_NAME); fprintf(out, "Usage: %s --help\n", argv[0] ?: PROG_NAME); } // err: 0 for normal exit and print to stdout. // err: nonzero for abnormal exit and print to stderr. // // noinline: We don't want this hoisted. noinline noreturn void usage_then_exit(int err, int argc, char** argv) { usage( err ? stderr : stdout, argc, argv); exit(err); } static int map_haystacks(const char* const * h, map_t maps[pOUT]) { const char* path; usize d =0; while( (path = *h++) ) { map_t *pOUT c = maps++; INFO("Mapping haystack file `%s' (to map addr %p)", path, c); debug_assert(c); if(!map_handle_err(map_file(path, false, 0, 0, c))) { //Handle unmapping previous haystacks before return // Prep reset maps to before `c` for unwind maps-=1; ERROR("Failed to map file `%s', rolling back %lu maps. (erp: %p, prev: %p)", path, d, c, maps); while( d --> 0 ) ifU(!map_handle_err(map_free(* (--maps)))) WARN("Failed to free map at %p (in %lu unwind)", maps, d); return 0; } d+=1; } return 1; } int main(int argc, char** argv) { if(!trace_init()) { ERROR("Unknown trace value for %s: `%s'", _TRACE_CONTROL_ENV_NAME, getenv(_TRACE_CONTROL_ENV_NAME) ?: ""); } TRACE("main start with %d (pn: %s, a1: %s), log trace level (ctrl envar `%s'): %d", argc, argv[0], argv[1] ?: "", _TRACE_CONTROL_ENV_NAME, (int)trace_max_level()); if(!argv[1]) inv_args: usage_then_exit(PROG_RET_ARGS, argc, argv); else if(strcmp(argv[1], "--help")==0) usage_then_exit(0, argc, argv); else if(!argv[2]) goto inv_args; int m_rval =0; map_t needle; INFO("Mapping needle file `%s'", argv[1]); if(!map_handle_err(map_file(argv[1], false, 0, 0, &needle))) return PROG_RET_MAP_NEEDLE_FAILED; if(!map_handle_err(map_preload(&needle, false))) WARN("Failed to advise kernel about memory access: needle"); // Remove exe name from argv: now is (needle, haystacks...) argv+=1; // Setup haystack maps usize hsn = AS(argc-2, usize); map_t haystacks[hsn]; TRACE("Attempting to map %lu haystacks", hsn); if(!map_haystacks((const char**)(argv+1), haystacks)) { // Unmap needle before return. ifU(!map_handle_err(map_free(needle))) WARN("Failed to unmap needle before exiting after failed haystack maps"); return PROG_RET_MAP_HAYSTACK_FAILED; } #ifdef _FEATURE_PARALLEL // Multi-threaded TODO("Multithreaded processing currently unimplemented."); //TODO: Setup thread-pool. //TODO: Dispatch haystack maps to threadpool. //TODO: Use either `cmp_find()` or `cmp_find_many()` to find the `needle` (mapped above) within those haystacks. //TODO: Within the threadpool: output information regarding each match/nonmatch. //TODO: Join the threadpool and consolidate results. //TODO: Iterate through the haystack numbers match results, return the first non-match haystack number through `PROG_RET_MATCH_HAYSTACK_N_FAILED(n)` (haystack numbers are always nonzero). If all were matched, return 0 #else // Single-threaded INFO("Computing result for %lu haystacks...", hsn); comp_multires_t* full_result = comp_match_all(&needle, hsn, haystacks, false); // Number of results actually computed by `comp_match_all()` may differ from `hsn` if `abort_on_fail` is true (currently its hardcoded to false but that may change.) usize cres = full_result->num_results; INFO("Computed result: Suc. Matched %lu / %lu hs. Full match: %s (first failure: %d)", cres, hsn, full_result->all_matched ? "yes" : "no", full_result->idx_first_failure); //TODO: Print out the aggregate (full_result) results (cres) in a reasonable format. free(full_result); #endif for(int i=0;i<(int)hsn;i++) { INFO("Unmapping haystack haystack file `%s' (#%d) (map addr %p)", argv[i+1], i+1, haystacks+i); ifU(!map_handle_err(map_free(haystacks[i]))) m_rval = !m_rval ? PROG_RET_UNMAP_HAYSTACK_N_FAILED(i+1) : m_rval; } TRACE("Unmap haystacks ended with main rval: %d", m_rval); INFO("Unmapping needle file `%s'", *argv); ifU(!map_handle_err(map_free(needle))) m_rval = !m_rval ? PROG_RET_UNMAP_NEEDLE_FAILED : m_rval; TRACE("main end: rval %d", m_rval); return m_rval; }