1034 lines
33 KiB
C
1034 lines
33 KiB
C
/* parse_cmdline.c - parsing of command line options */
|
|
|
|
#include "common_func.h" /* should be included before the C library files */
|
|
#include <assert.h>
|
|
#include <locale.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h> /* stat() */
|
|
#ifdef _WIN32
|
|
#include <windows.h> /* 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=<format string> [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=<n> ", _("Descend at most <n> levels of directories.\n"));
|
|
print_help_line(" -o, --output=<file> ", _("File to output calculation or checking results.\n"));
|
|
print_help_line(" -l, --log=<file> ", _("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=<file> ", _("Load a printf-like template from the <file>\n"));
|
|
print_help_line(" -p, --printf=<format string> ",
|
|
_("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; /* "--<option name>" */
|
|
int fail = 0;
|
|
assert((**parg)[0] == L'-' && (**parg)[1] == L'-');
|
|
|
|
/* search for the '=' sign */
|
|
length = ((eq_sign = wcschr(wname, L'=')) ? (size_t)(eq_sign - wname) : wcslen(wname));
|
|
option->name = name = (char*)rsh_malloc(length + 1);
|
|
rsh_vector_add_ptr(opt.mem, name);
|
|
if (length < 30) {
|
|
size_t i = 0;
|
|
for (; i < length; i++) {
|
|
if (((unsigned)wname[i]) <= 128) name[i] = (char)wname[i];
|
|
else {
|
|
fail = 1;
|
|
break;
|
|
}
|
|
}
|
|
name[i] = '\0';
|
|
|
|
name += 2; /* skip "--" */
|
|
length -= 2;
|
|
} else fail = 1;
|
|
|
|
if (fail) fail_on_unknow_option(w2c(**parg));
|
|
#else
|
|
option->name = **parg;
|
|
name = **parg + 2; /* skip "--" */
|
|
length = ((eq_sign = strchr(name, '=')) ? (size_t)(eq_sign - name) : strlen(name));
|
|
name[length] = '\0';
|
|
#endif
|
|
/* search for the option by its name */
|
|
for (t = cmdline_opt; t->type && (strncmp(name, t->long_name, length) != 0 ||
|
|
strlen(t->long_name) != length); t++) {
|
|
}
|
|
if (!t->type) {
|
|
fail_on_unknow_option(option->name); /* report error and exit */
|
|
}
|
|
|
|
option->o = t; /* store the option found */
|
|
if (is_param_required(t->type)) {
|
|
/* store parameter without a code page conversion */
|
|
option->parameter = (eq_sign ? eq_sign + 1 : *(++(*parg)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parsed program command line.
|
|
*/
|
|
struct parsed_cmd_line_t
|
|
{
|
|
blocks_vector_t options; /* array of parsed options */
|
|
int argc;
|
|
char **argv;
|
|
int n_files; /* the number of files */
|
|
rsh_tchar** files; /* array of files specified by command line */
|
|
#ifdef _WIN32
|
|
rsh_tchar** warg; /* program arguments in Unicode */
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* Parse command line arguments.
|
|
*
|
|
* @param cmd_line structure to store parsed options data
|
|
*/
|
|
static void parse_cmdline_options(struct parsed_cmd_line_t* cmd_line)
|
|
{
|
|
int argc;
|
|
int n_files = 0, b_opt_end = 0;
|
|
rsh_tchar** files;
|
|
rsh_tchar **parg, **end_arg;
|
|
parsed_option_t *next_opt;
|
|
|
|
#ifdef _WIN32
|
|
parg = cmd_line->warg = CommandLineToArgvW(GetCommandLineW(), &argc);
|
|
if ( NULL == parg || argc < 1) {
|
|
die(_("CommandLineToArgvW failed\n"));
|
|
}
|
|
#else
|
|
argc = cmd_line->argc;
|
|
parg = cmd_line->argv;
|
|
#endif
|
|
|
|
/* allocate array for files */
|
|
files = (rsh_tchar**)rsh_malloc(argc * sizeof(rsh_tchar*));
|
|
end_arg = parg + argc;
|
|
|
|
/* loop by program arguments */
|
|
for (parg++; parg < end_arg; parg++)
|
|
{
|
|
/* if argument is not an option */
|
|
if ((*parg)[0] != RSH_T('-') || (*parg)[1] == 0 || b_opt_end) {
|
|
/* it's a file, note that '-' is interpreted as stdin */
|
|
files[n_files++] = *parg;
|
|
continue;
|
|
}
|
|
|
|
assert((*parg)[0] == RSH_T('-') && (*parg)[1] != 0);
|
|
|
|
if ((*parg)[1] == L'-' && (*parg)[2] == 0) {
|
|
b_opt_end = 1; /* string "--" means end of options */
|
|
continue;
|
|
}
|
|
|
|
/* check for "--" */
|
|
if ((*parg)[1] == RSH_T('-')) {
|
|
cmdline_opt_t *t;
|
|
|
|
/* allocate parsed_option */
|
|
rsh_blocks_vector_add_empty(&cmd_line->options, 16, sizeof(parsed_option_t));
|
|
next_opt = rsh_blocks_vector_get_item(&cmd_line->options, cmd_line->options.size - 1, 16, parsed_option_t);
|
|
|
|
/* find the long option */
|
|
parse_long_option(next_opt, &parg);
|
|
t = next_opt->o;
|
|
|
|
/* process encoding and -o/-l options early */
|
|
if (is_output_modifier(t->type)) {
|
|
apply_option(&opt, next_opt);
|
|
}
|
|
} else if ((*parg)[1] != 0) {
|
|
/* found '-'<some string> */
|
|
rsh_tchar* ptr;
|
|
|
|
/* parse short options. A string of several characters is interpreted
|
|
* as separate short options */
|
|
for (ptr = *parg + 1; *ptr; ptr++) {
|
|
cmdline_opt_t *t;
|
|
char ch = (char)*ptr;
|
|
|
|
#ifdef _WIN32
|
|
if (((unsigned)*ptr) >= 128) {
|
|
ptr[1] = 0;
|
|
fail_on_unknow_option(w2c(ptr));
|
|
}
|
|
#endif
|
|
/* allocate parsed_option */
|
|
rsh_blocks_vector_add_empty(&cmd_line->options, 16, sizeof(parsed_option_t));
|
|
next_opt = rsh_blocks_vector_get_item(&cmd_line->options, cmd_line->options.size - 1, 16, parsed_option_t);
|
|
|
|
next_opt->buf[0] = '-', next_opt->buf[1] = ch, next_opt->buf[2] = '\0';
|
|
next_opt->name = next_opt->buf;
|
|
next_opt->parameter = NULL;
|
|
|
|
/* search for the short option */
|
|
for (t = cmdline_opt; t->type && ch != t->short1 && ch != t->short2; t++);
|
|
if (!t->type) fail_on_unknow_option(next_opt->buf);
|
|
next_opt->o = t;
|
|
if (is_param_required(t->type)) {
|
|
next_opt->parameter = (ptr[1] ? ptr + 1 : *(++parg));
|
|
if (!next_opt->parameter) {
|
|
/* note: need to check for parameter here, for early -o/-l options processing */
|
|
log_error(_("argument is required for option %s\n"), next_opt->name);
|
|
rsh_exit(2);
|
|
}
|
|
}
|
|
|
|
/* process encoding and -o/-l options early */
|
|
if (is_output_modifier(t->type)) {
|
|
apply_option(&opt, next_opt);
|
|
}
|
|
if (next_opt->parameter) break; /* a parameter ends the short options string */
|
|
}
|
|
}
|
|
|
|
} /* for */
|
|
|
|
cmd_line->n_files = n_files;
|
|
cmd_line->files = files;
|
|
}
|
|
|
|
/**
|
|
* Apply all parsed command line options: set binary flags, store strings,
|
|
* and do complex options handling by calling callbacks.
|
|
*
|
|
* @param cmd_line the parsed options information
|
|
*/
|
|
static void apply_cmdline_options(struct parsed_cmd_line_t *cmd_line)
|
|
{
|
|
size_t count = cmd_line->options.size;
|
|
size_t i;
|
|
for (i = 0; i < count; i++) {
|
|
parsed_option_t* o = (parsed_option_t*)rsh_blocks_vector_get_ptr(
|
|
&cmd_line->options, i, 16, sizeof(parsed_option_t));
|
|
|
|
/* process the option, if it was not applied early */
|
|
if (!is_output_modifier(o->o->type)) {
|
|
apply_option(&opt, o);
|
|
}
|
|
}
|
|
|
|
/* if no formating options were specified at the command line */
|
|
if (!opt.printf_str && !opt.template_file && !opt.sum_flags && !opt.fmt) {
|
|
/* copy the format from config */
|
|
opt.printf_str = conf_opt.printf_str;
|
|
opt.template_file = conf_opt.template_file;
|
|
}
|
|
|
|
if (!opt.printf_str && !opt.template_file) {
|
|
if (!opt.fmt) opt.fmt = conf_opt.fmt;
|
|
if (!opt.sum_flags) opt.sum_flags = conf_opt.sum_flags;
|
|
}
|
|
|
|
if (!opt.mode) opt.mode = conf_opt.mode;
|
|
opt.flags |= conf_opt.flags; /* copy all non-sum options */
|
|
|
|
if (opt.files_accept == 0) {
|
|
opt.files_accept = conf_opt.files_accept;
|
|
conf_opt.files_accept = 0;
|
|
}
|
|
if (opt.files_exclude == 0) {
|
|
opt.files_exclude = conf_opt.files_exclude;
|
|
conf_opt.files_exclude = 0;
|
|
}
|
|
if (opt.crc_accept == 0) {
|
|
opt.crc_accept = conf_opt.crc_accept;
|
|
conf_opt.crc_accept = 0;
|
|
}
|
|
if (opt.bt_announce == 0) {
|
|
opt.bt_announce = conf_opt.bt_announce;
|
|
conf_opt.bt_announce = 0;
|
|
}
|
|
|
|
if (opt.embed_crc_delimiter == 0) opt.embed_crc_delimiter = conf_opt.embed_crc_delimiter;
|
|
if (!opt.path_separator) opt.path_separator = conf_opt.path_separator;
|
|
if (opt.find_max_depth < 0) opt.find_max_depth = conf_opt.find_max_depth;
|
|
if (!(opt.flags & OPT_RECURSIVE)) opt.find_max_depth = 0;
|
|
if (opt.flags & OPT_EMBED_CRC) opt.sum_flags |= RHASH_CRC32;
|
|
if (opt.openssl_mask == 0) opt.openssl_mask = conf_opt.openssl_mask;
|
|
|
|
/* set defaults */
|
|
if (opt.embed_crc_delimiter == 0) opt.embed_crc_delimiter = " ";
|
|
}
|
|
|
|
/**
|
|
* Try to detect hash sums options from program name.
|
|
*
|
|
* @param progName the program name
|
|
*/
|
|
static void set_default_sums_flags(const char* progName)
|
|
{
|
|
char *buf;
|
|
int res = 0;
|
|
|
|
/* remove directory name from path */
|
|
const char* p = strrchr(progName, '/');
|
|
if (p) progName = p+1;
|
|
#ifdef _WIN32
|
|
p = strrchr(progName, '\\');
|
|
if (p) progName = p+1;
|
|
#endif
|
|
|
|
/* convert progName to lowercase */
|
|
buf = str_tolower(progName);
|
|
|
|
if (strstr(buf, "crc32")) res |= RHASH_CRC32;
|
|
if (strstr(buf, "md4")) res |= RHASH_MD4;
|
|
if (strstr(buf, "md5")) res |= RHASH_MD5;
|
|
if (strstr(buf, "sha1")) res |= RHASH_SHA1;
|
|
if (strstr(buf, "sha256")) res |= RHASH_SHA256;
|
|
if (strstr(buf, "sha512")) res |= RHASH_SHA512;
|
|
if (strstr(buf, "sha224")) res |= RHASH_SHA224;
|
|
if (strstr(buf, "sha384")) res |= RHASH_SHA384;
|
|
if (strstr(buf, "sha3-256")) res |= RHASH_SHA3_256;
|
|
if (strstr(buf, "sha3-512")) res |= RHASH_SHA3_512;
|
|
if (strstr(buf, "sha3-224")) res |= RHASH_SHA3_224;
|
|
if (strstr(buf, "sha3-384")) res |= RHASH_SHA3_384;
|
|
if (strstr(buf, "tiger")) res |= RHASH_TIGER;
|
|
if (strstr(buf, "tth")) res |= RHASH_TTH;
|
|
if (strstr(buf, "btih")) res |= RHASH_BTIH;
|
|
if (strstr(buf, "aich")) res |= RHASH_AICH;
|
|
if (strstr(buf, "gost")) res |= RHASH_GOST;
|
|
if (strstr(buf, "gost-cryptopro")) res |= RHASH_GOST_CRYPTOPRO;
|
|
if (strstr(buf, "has160")) res |= RHASH_HAS160;
|
|
if (strstr(buf, "ripemd160")) res |= RHASH_RIPEMD160;
|
|
if (strstr(buf, "whirlpool")) res |= RHASH_WHIRLPOOL;
|
|
if (strstr(buf, "edonr256")) res |= RHASH_EDONR256;
|
|
if (strstr(buf, "edonr512")) res |= RHASH_EDONR512;
|
|
if (strstr(buf, "snefru256")) res |= RHASH_SNEFRU128;
|
|
if (strstr(buf, "snefru128")) res |= RHASH_SNEFRU256;
|
|
if (strstr(buf, "ed2k-link") || strstr(buf, "ed2k-hash")) res |= OPT_ED2K_LINK;
|
|
else if (strstr(buf, "ed2k")) res |= RHASH_ED2K;
|
|
|
|
if (strstr(buf, "sfv") && opt.fmt == 0) opt.fmt = FMT_SFV;
|
|
if (strstr(buf, "magnet") && opt.fmt == 0) opt.fmt = FMT_MAGNET;
|
|
|
|
free(buf);
|
|
|
|
/* change program flags only if opt.sum_flags was not set */
|
|
if (!opt.sum_flags) {
|
|
opt.sum_flags = (res ? res : (opt.fmt == FMT_MAGNET ? RHASH_TTH | RHASH_ED2K | RHASH_AICH : RHASH_CRC32));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy a parsed options object.
|
|
*
|
|
* @param o pointer to the options object to destroy.
|
|
*/
|
|
void options_destroy(struct options_t* o)
|
|
{
|
|
file_mask_free(o->files_accept);
|
|
file_mask_free(o->files_exclude);
|
|
file_mask_free(o->crc_accept);
|
|
rsh_vector_free(o->bt_announce);
|
|
rsh_vector_free(o->mem);
|
|
destroy_file_search_data(o->search_data);
|
|
}
|
|
|
|
/**
|
|
* Check that options do not conflict with each other.
|
|
* Also make some final options processing steps.
|
|
*/
|
|
static void make_final_options_checks(void)
|
|
{
|
|
unsigned ff; /* formating flags */
|
|
|
|
if ((opt.flags & OPT_VERBOSE) && conf_opt.config_file) {
|
|
/* note that the first log_msg call shall be made after setup_output() */
|
|
log_msg(_("Config file: %s\n"), (conf_opt.config_file ? conf_opt.config_file : _("None")));
|
|
}
|
|
|
|
if (opt.bt_batch_file) opt.mode |= MODE_TORRENT;
|
|
if (opt.mode & MODE_TORRENT) opt.sum_flags |= RHASH_BTIH;
|
|
|
|
/* check that no more than one program mode specified */
|
|
if (opt.mode & (opt.mode - 1)) {
|
|
die(_("incompatible program modes\n"));
|
|
}
|
|
|
|
ff = (opt.printf_str ? 1 : 0) | (opt.template_file ? 2 : 0) | (opt.fmt ? 4 : 0);
|
|
if ((opt.fmt & (opt.fmt - 1)) || (ff & (ff - 1))) {
|
|
die(_("too many formating options\n"));
|
|
}
|
|
|
|
if (!opt.crc_accept) opt.crc_accept = file_mask_new_from_list(".sfv");
|
|
|
|
if (opt.openssl_mask) rhash_transmit(RMSG_SET_OPENSSL_MASK, 0, opt.openssl_mask, 0);
|
|
}
|
|
|
|
/**
|
|
* Parse command line options.
|
|
*
|
|
* @param argv program arguments
|
|
*/
|
|
void read_options(int argc, char *argv[])
|
|
{
|
|
struct parsed_cmd_line_t cmd_line;
|
|
|
|
memset(&opt, 0, sizeof(opt));
|
|
opt.mem = rsh_vector_new_simple();
|
|
opt.find_max_depth = -1;
|
|
|
|
/* initialize cmd_line */
|
|
memset(&cmd_line, 0, sizeof(cmd_line));
|
|
rsh_blocks_vector_init(&cmd_line.options);
|
|
cmd_line.argv = argv;
|
|
cmd_line.argc = argc;
|
|
|
|
/* parse command line and apply encoding options */
|
|
parse_cmdline_options(&cmd_line);
|
|
read_config();
|
|
|
|
#ifdef _WIN32
|
|
/* set default encoding if no encoding options were specified, */
|
|
/* this should be done here, even if config file was not found. */
|
|
if ( (opt.flags & OPT_ENCODING) == 0 ) opt.flags |= OPT_UTF8;
|
|
#endif
|
|
|
|
/* setup the program output */
|
|
IF_WINDOWS(setup_console());
|
|
setup_output();
|
|
|
|
apply_cmdline_options(&cmd_line); /* process the rest of command options */
|
|
|
|
/* options were processed, so we don't need them anymore */
|
|
rsh_blocks_vector_destroy(&cmd_line.options);
|
|
|
|
/* set the files and directories to be processed later */
|
|
opt.search_data = create_file_search_data(cmd_line.files, cmd_line.n_files, opt.find_max_depth);
|
|
opt.n_files = cmd_line.n_files;
|
|
|
|
free(cmd_line.files);
|
|
|
|
#ifdef _WIN32
|
|
LocalFree(cmd_line.warg);
|
|
#endif
|
|
|
|
make_final_options_checks();
|
|
|
|
set_default_sums_flags(argv[0]); /* detect default hashes from program name */
|
|
}
|