342 lines
8.8 KiB
C
342 lines
8.8 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h> /* free() */
|
|
#include <signal.h>
|
|
#include <locale.h>
|
|
#include <assert.h>
|
|
|
|
#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...] <FILE>...\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 '--<hashname>' 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;
|
|
}
|