/* parse_cmdline.c - parsing of command line options */ #include "common_func.h" /* should be included before the C library files */ #include #include #include #include #include #include /* stat() */ #ifdef _WIN32 #include /* for SetFileApisToOEM(), CharToOem() */ #endif #include "librhash/rhash.h" #include "win_utils.h" #include "file_mask.h" #include "find_file.h" #include "hash_print.h" #include "output.h" #include "rhash_main.h" #include "parse_cmdline.h" typedef struct options_t options_t; struct options_t conf_opt; /* config file parsed options */ struct options_t opt; /* command line options */ static const char* get_full_program_version(void) { static char version_buffer[64]; sprintf(version_buffer, "%s v%s\n", PROGRAM_NAME, get_version_string()); assert(strlen(version_buffer) < sizeof(version_buffer)); return version_buffer; } static void print_version(void) { fprintf(rhash_data.out, "%s", get_full_program_version()); rsh_exit(0); } static void print_help_line(const char* option, const char* text) { fprintf(rhash_data.out, "%s%s", option, text); } /** * Print program help. */ static void print_help(void) { assert(rhash_data.out != NULL); /* print program version and usage */ fprintf(rhash_data.out, _("%s\n" "Usage: %s [OPTION...] [FILE | -]...\n" " %s --printf= [FILE | -]...\n\n"), get_full_program_version(), CMD_FILENAME, CMD_FILENAME); fprintf(rhash_data.out, _("Options:\n")); print_help_line(" -V, --version ", _("Print program version and exit.\n")); print_help_line(" -h, --help ", _("Print this help screen.\n")); print_help_line(" -C, --crc32 ", _("Calculate CRC32 hash sum.\n")); print_help_line(" --md4 ", _("Calculate MD4 hash sum.\n")); print_help_line(" -M, --md5 ", _("Calculate MD5 hash sum.\n")); print_help_line(" -H, --sha1 ", _("Calculate SHA1 hash sum.\n")); print_help_line(" --sha224, --sha256, --sha384, --sha512 ", _("Calculate SHA2 hash sum.\n")); print_help_line(" --sha3-224, --sha3-256, --sha3-384, --sha3-512 ", _("Calculate SHA3 hash sum.\n")); print_help_line(" -T, --tth ", _("Calculate TTH sum.\n")); print_help_line(" --btih ", _("Calculate BitTorrent InfoHash.\n")); print_help_line(" -A, --aich ", _("Calculate AICH hash.\n")); print_help_line(" -E, --ed2k ", _("Calculate eDonkey hash sum.\n")); print_help_line(" -L, --ed2k-link ", _("Calculate and print eDonkey link.\n")); print_help_line(" --tiger ", _("Calculate Tiger hash sum.\n")); print_help_line(" -G, --gost ", _("Calculate GOST R 34.11-94 hash.\n")); print_help_line(" --gost-cryptopro ", _("CryptoPro version of the GOST R 34.11-94 hash.\n")); print_help_line(" --ripemd160 ", _("Calculate RIPEMD-160 hash.\n")); print_help_line(" --has160 ", _("Calculate HAS-160 hash.\n")); print_help_line(" --edonr256, --edonr512 ", _("Calculate EDON-R 256/512 hash.\n")); print_help_line(" --snefru128, --snefru256 ", _("Calculate SNEFRU-128/256 hash.\n")); print_help_line(" -a, --all ", _("Calculate all supported hashes.\n")); print_help_line(" -c, --check ", _("Check hash files specified by command line.\n")); print_help_line(" -u, --update ", _("Update hash files specified by command line.\n")); print_help_line(" -e, --embed-crc ", _("Rename files by inserting crc32 sum into name.\n")); print_help_line(" -k, --check-embedded ", _("Verify files by crc32 sum embedded in their names.\n")); print_help_line(" --list-hashes ", _("List the names of supported hashes, one per line.\n")); print_help_line(" -B, --benchmark ", _("Benchmark selected algorithm.\n")); print_help_line(" -v, --verbose ", _("Be verbose.\n")); print_help_line(" -r, --recursive ", _("Process directories recursively.\n")); print_help_line(" --skip-ok ", _("Don't print OK messages for successfully verified files.\n")); print_help_line(" -i, --ignore-case ", _("Ignore case of filenames when updating hash files.\n")); print_help_line(" --percents ", _("Show percents, while calculating or checking hashes.\n")); print_help_line(" --speed ", _("Output per-file and total processing speed.\n")); print_help_line(" --maxdepth= ", _("Descend at most levels of directories.\n")); print_help_line(" -o, --output= ", _("File to output calculation or checking results.\n")); print_help_line(" -l, --log= ", _("File to log errors and verbose information.\n")); print_help_line(" --sfv ", _("Print hash sums, using SFV format (default).\n")); print_help_line(" --bsd ", _("Print hash sums, using BSD-like format.\n")); print_help_line(" --simple ", _("Print hash sums, using simple format.\n")); print_help_line(" -m, --magnet ", _("Print hash sums as magnet links.\n")); print_help_line(" --torrent ", _("Create torrent files.\n")); #ifdef _WIN32 print_help_line(" --ansi ", _("Use Windows codepage for output (Windows only).\n")); #endif print_help_line(" --template= ", _("Load a printf-like template from the \n")); print_help_line(" -p, --printf= ", _("Format and print hash sums.\n See the RHash manual for details.\n")); rsh_exit(0); } /** * Print the names of all supported hash algorithms to the console. */ static void list_hashes(void) { int id; for (id = 1; id < RHASH_ALL_HASHES; id <<= 1) { const char* hash_name = rhash_get_name(id); if (hash_name) fprintf(rhash_data.out, "%s\n", hash_name); } rsh_exit(0); } enum file_suffix_type { MASK_ACCEPT, MASK_EXCLUDE, MASK_CRC_ACCEPT }; /** * Process --accept, --exclude and --crc-accept options. * * @param o pointer to the options structure to update * @param accept_string comma delimited string to parse * @param type the type of the option */ static void add_file_suffix(options_t *o, char* accept_string, unsigned type) { file_mask_array** ptr = (type == MASK_ACCEPT ? &o->files_accept : type == MASK_EXCLUDE ? &o->files_exclude : &o->crc_accept); if (!*ptr) *ptr = file_mask_new(); file_mask_add_list(*ptr, accept_string); } /** * Process --bt_announce option. * * @param o pointer to the options structure * @param announce_url the url to parse * @param unused a tottaly unused parameter */ static void bt_announce(options_t *o, char* announce_url, unsigned unused) { (void)unused; /* skip empty string */ if (!announce_url || !announce_url[0]) return; if (!o->bt_announce) o->bt_announce = rsh_vector_new_simple(); rsh_vector_add_ptr(o->bt_announce, rsh_strdup(announce_url)); } /** * Process an --openssl option. * * @param o pointer to the options structure to update * @param openssl_hashes comma delimited string with hash names * @param type ignored */ static void openssl_flags(options_t *o, char* openssl_hashes, unsigned type) { #if defined(USE_OPENSSL) || defined(OPENSSL_RUNTIME) char *cur, *next; (void)type; o->openssl_mask = 0x80000000; /* turn off using default mask */ /* set the openssl_mask */ for (cur = openssl_hashes; cur && *cur; cur = next) { print_hash_info *info = hash_info_table; unsigned bit; size_t length; next = strchr(cur, ','); length = (next != NULL ? (size_t)(next++ - cur) : strlen(cur)); for (bit = 1; bit <= RHASH_ALL_HASHES; bit = bit << 1, info++) { if ( (bit & RHASH_OPENSSL_SUPPORTED_HASHES) && memcmp(cur, info->short_name, length) == 0 && info->short_name[length] == 0) { o->openssl_mask |= bit; break; } } if (bit > RHASH_ALL_HASHES) { cur[length] = '\0'; /* terminate wrong hash name */ log_warning(_("openssl option doesn't support '%s' hash\n"), cur); } } #else (void)type; (void)openssl_hashes; (void)o; log_warning(_("compiled without openssl support\n")); #endif } /** * Process --video option. * * @param o pointer to the options structure to update */ static void accept_video(options_t *o) { add_file_suffix(o, ".avi,.ogm,.mkv,.mp4,.mpeg,.mpg,.asf,.rm,.wmv,.vob", MASK_ACCEPT); } /** * Say nya! Keep secret! =) */ static void nya(void) { fprintf(rhash_data.out, " /\\__/\\\n (^ _ ^.) %s\n (_uu__)\n", /* TRANSLATORS: Keep secret ;) */ _("Purrr...")); rsh_exit(0); } /** * Process on --maxdepth option. * * @param o pointer to the processed option * @param number the string containing the max-depth number * @param param unused parameter */ static void set_max_depth(options_t *o, char* number, unsigned param) { (void)param; if (strspn(number, "0123456789") < strlen(number)) { log_error(_("maxdepth parameter is not a number: %s\n"), number); rsh_exit(2); } o->find_max_depth = atoi(number); } /** * Set the length of a BitTorrent file piece. * * @param o pointer to the processed option * @param number string containing the piece length number * @param param unused parameter */ static void set_bt_piece_length(options_t *o, char* number, unsigned param) { (void)param; if (strspn(number, "0123456789") < strlen(number)) { log_error(_("bt-piece-length parameter is not a number: %s\n"), number); rsh_exit(2); } o->bt_piece_length = (size_t)atoi(number); } /** * Set the path separator to use when printing paths * * @param o pointer to the processed option * @param sep file separator, can be only '/' or '\' * @param param unused parameter */ static void set_path_separator(options_t *o, char* sep, unsigned param) { (void)param; if ((*sep == '/' || *sep == '\\') && sep[1] == 0) { o->path_separator = *sep; #if defined(_WIN32) /* MSYS environment changes '/' in command line to HOME, see http://www.mingw.org/wiki/FAQ */ } else if (getenv("MSYSTEM") || getenv("TERM")) { log_warning(_("wrong path-separator, use '//' instead of '/' on MSYS\n")); o->path_separator = '/'; #endif } else { log_error(_("path-separator is not '/' or '\\': %s\n"), sep); rsh_exit(2); } } /** * Information about a command line option. */ typedef struct cmdline_opt_t { unsigned short type; /* how to process the option, see option_type_t below */ char short1, short2; /* short option names */ char* long_name; /* long option name */ void* ptr; /* auxiliary pointer, e.g. to an opt field */ unsigned param; /* optional integer parameter */ } cmdline_opt_t; enum option_type_t { F_NEED_PARAM = 16, /* flag: option needs a parameter */ F_OUTPUT_OPT = 32, /* flag: option changes program output */ F_UFLG = 1, /* set a bit flag in a uint32_t field */ F_UENC = F_UFLG | F_OUTPUT_OPT, /* an encoding changing option */ F_CSTR = 2 | F_NEED_PARAM, /* store parameter as a C string */ F_TOUT = 3 | F_NEED_PARAM | F_OUTPUT_OPT, F_VFNC = 4, /* just call a function */ F_PFNC = 5 | F_NEED_PARAM, /* process option parameter by calling a handler */ F_UFNC = 6 | F_NEED_PARAM, /* pass UTF-8 encoded parameter to the handler */ F_PRNT = 7, /* print a constant C-string and exit */ }; #define is_param_required(option_type) ((option_type) & F_NEED_PARAM) #define is_output_modifier(option_type) ((option_type) & F_OUTPUT_OPT) /* supported program options */ cmdline_opt_t cmdline_opt[] = { /* program modes */ { F_UFLG, 'c', 0, "check", &opt.mode, MODE_CHECK }, { F_UFLG, 'k', 0, "check-embedded", &opt.mode, MODE_CHECK_EMBEDDED }, { F_UFLG, 'u', 0, "update", &opt.mode, MODE_UPDATE }, { F_UFLG, 'B', 0, "benchmark", &opt.mode, MODE_BENCHMARK }, { F_UFLG, 0, 0, "torrent", &opt.mode, MODE_TORRENT }, { F_VFNC, 0, 0, "list-hashes", list_hashes, 0 }, { F_VFNC, 'h', 0, "help", print_help, 0 }, { F_VFNC, 'V', 0, "version", print_version, 0 }, /* hash sums options */ { F_UFLG, 'a', 0, "all", &opt.sum_flags, RHASH_ALL_HASHES }, { F_UFLG, 'C', 0, "crc32", &opt.sum_flags, RHASH_CRC32 }, { F_UFLG, 0, 0, "md4", &opt.sum_flags, RHASH_MD4 }, { F_UFLG, 'M', 0, "md5", &opt.sum_flags, RHASH_MD5 }, { F_UFLG, 'H', 0, "sha1", &opt.sum_flags, RHASH_SHA1 }, { F_UFLG, 0, 0, "sha224", &opt.sum_flags, RHASH_SHA224 }, { F_UFLG, 0, 0, "sha256", &opt.sum_flags, RHASH_SHA256 }, { F_UFLG, 0, 0, "sha384", &opt.sum_flags, RHASH_SHA384 }, { F_UFLG, 0, 0, "sha512", &opt.sum_flags, RHASH_SHA512 }, { F_UFLG, 0, 0, "sha3-224", &opt.sum_flags, RHASH_SHA3_224 }, { F_UFLG, 0, 0, "sha3-256", &opt.sum_flags, RHASH_SHA3_256 }, { F_UFLG, 0, 0, "sha3-384", &opt.sum_flags, RHASH_SHA3_384 }, { F_UFLG, 0, 0, "sha3-512", &opt.sum_flags, RHASH_SHA3_512 }, { F_UFLG, 0, 0, "tiger", &opt.sum_flags, RHASH_TIGER }, { F_UFLG, 'T', 0, "tth", &opt.sum_flags, RHASH_TTH }, { F_UFLG, 0, 0, "btih", &opt.sum_flags, RHASH_BTIH }, { F_UFLG, 'E', 0, "ed2k", &opt.sum_flags, RHASH_ED2K }, { F_UFLG, 'A', 0, "aich", &opt.sum_flags, RHASH_AICH }, { F_UFLG, 'G', 0, "gost", &opt.sum_flags, RHASH_GOST }, { F_UFLG, 0, 0, "gost-cryptopro", &opt.sum_flags, RHASH_GOST_CRYPTOPRO }, { F_UFLG, 'W', 0, "whirlpool", &opt.sum_flags, RHASH_WHIRLPOOL }, { F_UFLG, 0, 0, "ripemd160", &opt.sum_flags, RHASH_RIPEMD160 }, { F_UFLG, 0, 0, "has160", &opt.sum_flags, RHASH_HAS160 }, { F_UFLG, 0, 0, "snefru128", &opt.sum_flags, RHASH_SNEFRU128 }, { F_UFLG, 0, 0, "snefru256", &opt.sum_flags, RHASH_SNEFRU256 }, { F_UFLG, 0, 0, "edonr256", &opt.sum_flags, RHASH_EDONR256 }, { F_UFLG, 0, 0, "edonr512", &opt.sum_flags, RHASH_EDONR512 }, { F_UFLG, 'L', 0, "ed2k-link", &opt.sum_flags, OPT_ED2K_LINK }, /* output formats */ { F_UFLG, 0, 0, "sfv", &opt.fmt, FMT_SFV }, { F_UFLG, 0, 0, "bsd", &opt.fmt, FMT_BSD }, { F_UFLG, 0, 0, "simple", &opt.fmt, FMT_SIMPLE }, { F_UFLG, 'm', 0, "magnet", &opt.fmt, FMT_MAGNET }, { F_UFLG, 0, 0, "uppercase", &opt.flags, OPT_UPPERCASE }, { F_UFLG, 0, 0, "lowercase", &opt.flags, OPT_LOWERCASE }, { F_CSTR, 0, 0, "template", &opt.template_file, 0 }, { F_CSTR, 'p', 0, "printf", &opt.printf_str, 0 }, /* other options */ { F_UFLG, 'r', 'R', "recursive", &opt.flags, OPT_RECURSIVE }, #ifndef _WIN32 /* Unix-only option */ { F_UFLG, 0, 0, "follow", &opt.flags, OPT_FOLLOW }, #endif { F_UFLG, 'v', 0, "verbose", &opt.flags, OPT_VERBOSE }, { F_UFLG, 0, 0, "gost-reverse", &opt.flags, OPT_GOST_REVERSE }, { F_UFLG, 0, 0, "skip-ok", &opt.flags, OPT_SKIP_OK }, { F_UFLG, 'i', 0, "ignore-case", &opt.flags, OPT_IGNORE_CASE }, { F_UENC, 0, 0, "percents", &opt.flags, OPT_PERCENTS }, { F_UFLG, 0, 0, "speed", &opt.flags, OPT_SPEED }, { F_UFLG, 'e', 0, "embed-crc", &opt.flags, OPT_EMBED_CRC }, { F_CSTR, 0, 0, "embed-crc-delimiter", &opt.embed_crc_delimiter, 0 }, { F_PFNC, 0, 0, "path-separator", set_path_separator, 0 }, { F_TOUT, 'o', 0, "output", &opt.output, 0 }, { F_TOUT, 'l', 0, "log", &opt.log, 0 }, { F_PFNC, 'q', 0, "accept", add_file_suffix, MASK_ACCEPT }, { F_PFNC, 't', 0, "crc-accept", add_file_suffix, MASK_CRC_ACCEPT }, { F_PFNC, 0, 0, "exclude", add_file_suffix, MASK_EXCLUDE }, { F_VFNC, 0, 0, "video", accept_video, 0 }, { F_VFNC, 0, 0, "nya", nya, 0 }, { F_PFNC, 0, 0, "maxdepth", set_max_depth, 0 }, { F_UFLG, 0, 0, "bt-private", &opt.flags, OPT_BT_PRIVATE }, { F_PFNC, 0, 0, "bt-piece-length", set_bt_piece_length, 0 }, { F_UFNC, 0, 0, "bt-announce", bt_announce, 0 }, { F_CSTR, 0, 0, "bt-batch", &opt.bt_batch_file, 0 }, { F_UFLG, 0, 0, "benchmark-raw", &opt.flags, OPT_BENCH_RAW }, { F_PFNC, 0, 0, "openssl", openssl_flags, 0 }, #ifdef _WIN32 /* code pages (windows only) */ { F_UENC, 0, 0, "utf8", &opt.flags, OPT_UTF8 }, { F_UENC, 0, 0, "ansi", &opt.flags, OPT_ANSI }, { F_UENC, 0, 0, "oem", &opt.flags, OPT_OEM }, #endif { 0,0,0,0,0,0 } }; /** * Log a message and exit the program. * * @param msg the message to log */ static void die(const char* msg) { log_error(msg); rsh_exit(2); } /** * Log an error about unknown option and exit the program. * * @param option_name the name of the unknown option encountered */ static void fail_on_unknow_option(const char* option_name) { log_error(_("unknown option: %s\n"), (option_name ? option_name : "?")); rsh_exit(2); } /* structure to store command line option information */ typedef struct parsed_option_t { cmdline_opt_t *o; const char* name; /* the parsed option name */ char buf[4]; void* parameter; /* option argument, if required */ } parsed_option_t; /** * Process given command line option * * @param opts the structure to store results of option processing * @param option option to process */ static void apply_option(options_t *opts, parsed_option_t* option) { cmdline_opt_t* o = option->o; unsigned short option_type = o->type; char* value = NULL; /* check if option requires a parameter */ if (is_param_required(option_type)) { if (!option->parameter) { log_error(_("argument is required for option %s\n"), option->name); rsh_exit(2); } #ifdef _WIN32 if (option_type == F_TOUT) { /* leave the value in UTF-16 */ value = (char*)rsh_wcsdup((wchar_t*)option->parameter); } else if (option_type == F_UFNC) { /* convert from UTF-16 to UTF-8 */ value = wchar_to_cstr((wchar_t*)option->parameter, CP_UTF8, NULL); } else { /* convert from UTF-16 */ value = w2c((wchar_t*)option->parameter); } rsh_vector_add_ptr(opt.mem, value); #else value = (char*)option->parameter; #endif } /* process option, choosing the method by type */ switch (option_type) { case F_UFLG: case F_UENC: *(unsigned*)((char*)opts + ((char*)o->ptr - (char*)&opt)) |= o->param; break; case F_CSTR: case F_TOUT: /* save the option parameter */ *(char**)((char*)opts + ((char*)o->ptr - (char*)&opt)) = value; break; case F_PFNC: case F_UFNC: /* call option parameter handler */ ( ( void(*)(options_t *, char*, unsigned) )o->ptr )(opts, value, o->param); break; case F_VFNC: ( ( void(*)(options_t *) )o->ptr )(opts); /* call option handler */ break; case F_PRNT: log_msg("%s", (char*)o->ptr); rsh_exit(0); break; default: assert(0); /* impossible option type */ } } /** * Search for config file. * * @return the relative path to config file */ static const char* find_conf_file(void) { # define CONF_FILE_NAME "rhashrc" char *dir1, *path; #ifndef _WIN32 /* Linux/Unix part */ /* first check for $HOME/.rhashrc file */ if ( (dir1 = getenv("HOME")) ) { path = make_path(dir1, ".rhashrc"); if (is_regular_file(path)) { rsh_vector_add_ptr(opt.mem, path); return (conf_opt.config_file = path); } free(path); } /* then check for global config */ path = "ENVARC:" CONF_FILE_NAME; if (is_regular_file(path)) { return (conf_opt.config_file = path); } #else /* _WIN32 */ /* first check for the %APPDATA%\RHash\rhashrc config */ if ( (dir1 = getenv("APPDATA")) ) { dir1 = make_path(dir1, "RHash"); path = make_path(dir1, CONF_FILE_NAME); rsh_vector_add_ptr(opt.mem, path); free(dir1); if (is_regular_file(path)) { return (conf_opt.config_file = path); } } /* then check for %HOMEDRIVE%%HOMEPATH%\rhashrc */ /* note that %USERPROFILE% is generally not a user home dir */ if ( (dir1 = getenv("HOMEDRIVE")) && (path = getenv("HOMEPATH"))) { dir1 = make_path(dir1, path); path = make_path(dir1, CONF_FILE_NAME); rsh_vector_add_ptr(opt.mem, path); free(dir1); if (is_regular_file(path)) { return (conf_opt.config_file = path); } } #endif /* _WIN32 */ return (conf_opt.config_file = NULL); /* config file not found */ } /** * Parse config file of the program. * * @return 0 on success, -1 on fail */ static int read_config(void) { #define LINE_BUF_SIZE 2048 char buf[LINE_BUF_SIZE]; FILE* fd; parsed_option_t option; int res; /* initialize conf_opt and opt structures */ memset(&conf_opt, 0, sizeof(opt)); conf_opt.find_max_depth = -1; if (!find_conf_file()) return 0; fd = fopen(conf_opt.config_file, "r"); if (!fd) return -1; while (fgets(buf, LINE_BUF_SIZE, fd)) { size_t index; cmdline_opt_t* t; char* line = str_trim(buf); char *name, *value; if (*line == 0 || IS_COMMENT(*line)) continue; /* search for '=' */ index = strcspn(line, "="); if (line[index] == 0) { log_warning(_("%s: can't parse line \"%s\"\n"), conf_opt.config_file, line); continue; } line[index] = 0; name = str_trim(line); for (t = cmdline_opt; t->type; t++) { if (strcmp(name, t->long_name) == 0) { break; } } if (!t->type) { log_warning(_("%s: unknown option \"%s\"\n"), conf_opt.config_file, line); continue; } value = str_trim(line + index + 1); /* process a long option */ if (is_param_required(t->type)) { rsh_vector_add_ptr(opt.mem, (value = rsh_strdup(value)));; } else { /* possible boolean values for a config file variable */ static const char* strings[] = { "on", "yes", "true", 0 }; const char** cmp; for (cmp = strings; *cmp && strcmp(value, *cmp); cmp++); if (*cmp == 0) continue; } option.name = name; option.parameter = value; option.o = t; apply_option(&conf_opt, &option); } res = fclose(fd); #ifdef _WIN32 if ( (opt.flags & OPT_ENCODING) == 0 ) opt.flags |= (conf_opt.flags & OPT_ENCODING); #endif return (res == 0 ? 0 : -1); } /** * Find long option info, by it's name and retrieve its parameter if required. * Error is reported for unknown options. * * @param option structure to receive the parsed option info * @param parg pointer to a command line argument */ static void parse_long_option(parsed_option_t* option, rsh_tchar ***parg) { size_t length; rsh_tchar* eq_sign; cmdline_opt_t *t; char* name; #ifdef _WIN32 rsh_tchar* wname = **parg; /* "--