/* * rhash_main.c: compute CRC32, MD5, SHA1, Tiger, DC++ TTH and eDonkey 2000 hashes * * rhash is a small utility written in C that computes various message * digests of files. The message digests include CRC32, MD5, SHA1, TTH, * ED2K, GOST and many other. */ #include "common_func.h" /* shall be included before the C library files */ #include #include #include /* free() */ #include #include #include #include "librhash/rhash.h" #include "win_utils.h" #include "find_file.h" #include "calc_sums.h" #include "hash_update.h" #include "file_mask.h" #include "hash_print.h" #include "parse_cmdline.h" #include "output.h" #include "rhash_main.h" const char *vers = "\0$VER: " PACKAGE_NAME " " PACKAGE_VERSION " (08.11.2016)"; struct rhash_t rhash_data; /** * Check if the file must be skipped. Returns 1 if the file path * is the same as the output or the log file path. * * @param file the file to check * @param mask the mask of accepted files * @return 1 if the file should be skipped, 0 otherwise */ static int must_skip_file(file_t* file) { const rsh_tchar* path = get_file_tpath(file); /* check if the file path is the same as the output or the log file path */ return (opt.output && are_paths_equal(path, opt.output)) || (opt.log && are_paths_equal(path, opt.log)); } /** * Callback function to process files while recursively traversing a directory. * It hashes, checks or updates a file according to the current work mode. * * @param file the file to process * @param preprocess non-zero when preprocessing files, zero for actual processing. */ static int find_file_callback(file_t* file, int preprocess) { int res = 0; assert(!FILE_ISDIR(file)); assert(opt.search_data); if (rhash_data.interrupted) { opt.search_data->options |= FIND_CANCEL; return 0; } if (preprocess) { if (!file_mask_match(opt.files_accept, file->path) || (opt.files_exclude && file_mask_match(opt.files_exclude, file->path)) || must_skip_file(file)) { return 0; } if (opt.fmt & FMT_SFV) { print_sfv_header_line(rhash_data.out, file, 0); } rhash_data.batch_size += file->size; } else { int not_root = !(file->mode & FILE_IFROOT); if (not_root) { if ((opt.mode & (MODE_CHECK | MODE_UPDATE)) != 0) { /* check and update modes use the crc_accept list */ if (!file_mask_match(opt.crc_accept, file->path)) { return 0; } } else { if (!file_mask_match(opt.files_accept, file->path) || (opt.files_exclude && file_mask_match(opt.files_exclude, file->path))) { return 0; } } } if (must_skip_file(file)) return 0; if (opt.mode & (MODE_CHECK | MODE_CHECK_EMBEDDED)) { res = check_hash_file(file, not_root); } else { if (opt.mode & MODE_UPDATE) { res = update_hash_file(file); } else { /* default mode: calculate hash */ const char* print_path = file->path; if (print_path[0] == '.' && IS_PATH_SEPARATOR(print_path[1])) print_path += 2; res = calculate_and_print_sums(rhash_data.out, file, print_path); if (rhash_data.interrupted) return 0; rhash_data.processed++; } } } if (res < 0) rhash_data.error_flag = 1; return 1; } /* previous SIGINT handler */ void (*prev_sigint_handler)(int) = NULL; /** * Handler for the SIGINT signal, sent when user press Ctrl+C. * The handler prints message and exits the program. * * @param signum the processed signal identifier SIGINT */ static void ctrl_c_handler(int signum) { (void)signum; rhash_data.interrupted = 1; if (rhash_data.rctx) { rhash_cancel(rhash_data.rctx); } } #define MAX_TEMPLATE_SIZE 65536 /** * Load printf-template from file, specified by options or config. */ static int load_printf_template(void) { FILE* fd = fopen(opt.template_file, "rb"); char buffer[8192]; size_t len; int error = 0; if (!fd) { log_file_error(opt.template_file); return 0; } rhash_data.template_text = rsh_str_new(); while (!feof(fd)) { len = fread(buffer, 1, 8192, fd); if (ferror(fd)) break; rsh_str_append_n(rhash_data.template_text, buffer, len); if (rhash_data.template_text->len >= MAX_TEMPLATE_SIZE) { log_msg(_("%s: template file is too big\n"), opt.template_file); error = 1; } } if (ferror(fd)) { log_file_error(opt.template_file); error = 1; } fclose(fd); rhash_data.printf_str = rhash_data.template_text->str; return !error; } /** * Free data allocated by an rhash_t object * * @param ptr pointer to rhash_t object */ void rhash_destroy(struct rhash_t* ptr) { free_print_list(ptr->print_list); rsh_str_free(ptr->template_text); if (ptr->rctx) rhash_free(ptr->rctx); IF_WINDOWS(restore_console()); } static void i18n_initialize(void) { setlocale(LC_ALL, ""); /* set locale according to the environment */ #ifdef USE_GETTEXT bindtextdomain("rhash", LOCALEDIR); /* set the text message domain */ textdomain("rhash"); #endif /* USE_GETTEXT */ } extern struct ExecBase *SysBase; void cleanup(void) { freeall(); } /** * RHash program entry point. * * @param argc number of program arguments including the program path * @param argv program arguments * @return the program exit code, zero on success and 1 on error */ int main(int argc, char *argv[]) { timedelta_t timer; int exit_code; int sfv; SysBase = *((struct ExecBase **) 4); atexit(freeall); i18n_initialize(); /* initialize locale and translation */ memset(&rhash_data, 0, sizeof(rhash_data)); rhash_data.out = stdout; /* set initial output streams */ rhash_data.log = stderr; /* can be altered by options later */ init_hash_info_table(); read_options(argc, argv); /* load config and parse command line options */ prev_sigint_handler = signal(SIGINT, ctrl_c_handler); /* install SIGINT handler */ rhash_library_init(); /* in benchmark mode just run benchmark and exit */ if (opt.mode & MODE_BENCHMARK) { unsigned flags = (opt.flags & OPT_BENCH_RAW ? BENCHMARK_CPB | BENCHMARK_RAW : BENCHMARK_CPB); if ((opt.flags & OPT_BENCH_RAW) == 0) { fprintf(rhash_data.out, _("%s v%s benchmarking...\n"), PROGRAM_NAME, get_version_string()); } run_benchmark(opt.sum_flags, flags); exit_code = (rhash_data.interrupted ? 3 : 0); rsh_exit(exit_code); } if (opt.n_files == 0) { if (argc > 1) { log_warning(_("no files/directories were specified at command line\n")); } /* print short usage help */ log_msg(_("Usage: %s [OPTION...] ...\n\n" "Run `%s --help' for more help.\n"), CMD_FILENAME, CMD_FILENAME); rsh_exit(0); } assert(opt.search_data != 0); /* setup printf formating string */ rhash_data.printf_str = opt.printf_str; if (opt.template_file) { if (!load_printf_template()) rsh_exit(2); } else if (!rhash_data.printf_str && !(opt.mode & (MODE_CHECK | MODE_CHECK_EMBEDDED))) { /* initialize printf output format according to '--' options */ init_printf_format( (rhash_data.template_text = rsh_str_new()) ); rhash_data.printf_str = rhash_data.template_text->str; if (opt.flags & OPT_VERBOSE) { char* str = rsh_strdup(rhash_data.printf_str); log_msg(_("Format string is: %s\n"), str_trim(str)); free(str); } } if (rhash_data.printf_str) { rhash_data.print_list = parse_print_string(rhash_data.printf_str, &opt.sum_flags); } opt.search_data->options = FIND_SKIP_DIRS; opt.search_data->options |= (opt.flags & OPT_FOLLOW ? FIND_FOLLOW_SYMLINKS : 0); opt.search_data->call_back = find_file_callback; if ((sfv = (opt.fmt == FMT_SFV && !opt.mode))) { print_sfv_banner(rhash_data.out); } /* preprocess files */ if (sfv || opt.bt_batch_file) { /* note: errors are not reported on preprocessing */ opt.search_data->call_back_data = 1; scan_files(opt.search_data); fflush(rhash_data.out); } /* measure total processing time */ rsh_timer_start(&timer); rhash_data.processed = 0; /* process files */ opt.search_data->options |= FIND_LOG_ERRORS; opt.search_data->call_back_data = 0; scan_files(opt.search_data); if ((opt.mode & MODE_CHECK_EMBEDDED) && rhash_data.processed > 1) { print_check_stats(); } if (!rhash_data.interrupted) { if (opt.bt_batch_file && rhash_data.rctx) { rhash_final(rhash_data.rctx, 0); save_torrent_to(opt.bt_batch_file, rhash_data.rctx); } if ((opt.flags & OPT_SPEED) && !(opt.mode & (MODE_CHECK | MODE_UPDATE)) && rhash_data.processed > 1) { double time = rsh_timer_stop(&timer); print_time_stats(time, rhash_data.total_size, 1); } } else { /* check if interruption was not reported yet */ if (rhash_data.interrupted == 1) report_interrupted(); } exit_code = (rhash_data.error_flag ? 1 : opt.search_data->errors_count ? 2 : rhash_data.interrupted ? 3 : 0); options_destroy(&opt); if (rhash_data.out && rhash_data.out != stdout) fclose(rhash_data.out); if (rhash_data.log && rhash_data.log != stderr) fclose(rhash_data.log); rhash_destroy(&rhash_data); /* return non-zero error code if error occurred */ return exit_code; }