parse_cmdline.c 35.6 KB
Newer Older
1 2
/* parse_cmdline.c - parsing of command line options */

3 4
#include <assert.h>
#include <locale.h>
5
#include <stdarg.h>
6 7 8 9
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
10
# include <windows.h> /* for SetFileApisToOEM(), CharToOem() */
11 12
#endif

13 14
#include "parse_cmdline.h"
#include "common_func.h"
15
#include "file.h"
16
#include "file_mask.h"
17
#include "find_file.h"
18
#include "hash_print.h"
19 20
#include "output.h"
#include "rhash_main.h"
21 22
#include "win_utils.h"
#include "librhash/rhash.h"
23 24 25 26 27

typedef struct options_t options_t;
struct options_t conf_opt; /* config file parsed options */
struct options_t opt;      /* command line options */

28 29 30 31 32 33 34 35 36 37
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)
{
38
	rsh_fprintf(rhash_data.out, "%s", get_full_program_version());
39 40 41
	rsh_exit(0);
}

42
static void print_help_line(const char* option, const char* format, ...)
43
{
44 45 46 47
	va_list args;
	va_start(args, format);
	rsh_fprintf(rhash_data.out, "%s", option);
	rsh_vfprintf(rhash_data.out, format, args);
48 49
}

50 51 52 53 54
/**
 * Print program help.
 */
static void print_help(void)
{
55
	const char* hash_sum_format;
56
	assert(rhash_data.out != NULL);
57 58

	/* print program version and usage */
59
	rsh_fprintf(rhash_data.out, _("%s\n"
60
		"Usage: %s [OPTION...] [FILE | -]...\n"
61
		"       %s --printf=<format string> [FILE | -]...\n\n"), get_full_program_version(), CMD_FILENAME, CMD_FILENAME);
62

63
	rsh_fprintf(rhash_data.out, _("Options:\n"));
64 65
	print_help_line("  -V, --version ", _("Print program version and exit.\n"));
	print_help_line("  -h, --help    ", _("Print this help screen.\n"));
66 67 68 69 70 71 72 73 74 75 76 77 78
	/* TRANSLATORS: format string for a help line, like: "Calculate SHA1 hash sum.\n" */
	hash_sum_format = _("Calculate %s hash sum.\n");
	print_help_line("  -C, --crc32   ", hash_sum_format, "CRC32");
	print_help_line("      --crc32c  ", hash_sum_format, "CRC32C");
	print_help_line("      --md4     ", hash_sum_format, "MD4");
	print_help_line("  -M, --md5     ", hash_sum_format, "MD5");
	print_help_line("  -H, --sha1    ", hash_sum_format, "SHA1");
	print_help_line("      --sha224, --sha256, --sha384, --sha512 ", hash_sum_format, "SHA2");
	print_help_line("      --sha3-224, --sha3-256, --sha3-384, --sha3-512 ", hash_sum_format, "SHA3");
	print_help_line("  -T, --tth     ", hash_sum_format, "TTH");
	print_help_line("      --btih    ", hash_sum_format, "BitTorrent InfoHash");
	print_help_line("  -A, --aich    ", hash_sum_format, "AICH");
	print_help_line("  -E, --ed2k    ", hash_sum_format, "eDonkey");
79
	print_help_line("  -L, --ed2k-link  ", _("Calculate and print eDonkey link.\n"));
80 81 82 83 84 85 86 87 88 89 90
	print_help_line("      --tiger   ", hash_sum_format, "Tiger");
	print_help_line("  -G, --gost12-256 ", hash_sum_format, _("GOST R 34.11-2012, 256 bit"));
	print_help_line("      --gost12-512 ", hash_sum_format, _("GOST R 34.11-2012, 512 bit"));
	/* TRANSLATORS: This hash function name should be translated to Russian only */
	print_help_line("      --gost94  ", hash_sum_format, _("GOST R 34.11-94"));
	/* TRANSLATORS: This hash function name should be translated to Russian only */
	print_help_line("      --gost94-cryptopro ", hash_sum_format, _("GOST R 34.11-94 CryptoPro"));
	print_help_line("      --ripemd160  ", hash_sum_format, "RIPEMD-160");
	print_help_line("      --has160  ", hash_sum_format, "HAS-160");
	print_help_line("      --edonr256, --edonr512  ", hash_sum_format, "EDON-R 256/512");
	print_help_line("      --snefru128, --snefru256  ", hash_sum_format, "SNEFRU-128/256");
91 92 93 94 95 96 97 98 99
	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"));
100
	print_help_line("      --file-list=<file> ", _("Process a list of files.\n"));
101
	print_help_line("  -m, --message=<text> ", _("Process the text message.\n"));
102 103 104 105 106
	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"));
107 108
	if (rhash_is_openssl_supported())
		print_help_line("      --openssl=<list> ", _("List hash functions to be calculated using OpenSSL.\n"));
109 110 111 112 113
	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"));
114
	print_help_line("  -g, --magnet  ", _("Print hash sums  as magnet links.\n"));
115
	print_help_line("      --torrent ", _("Create torrent files.\n"));
116
#ifdef _WIN32
117 118 119
	print_help_line("      --utf8    ", _("Use UTF-8 encoding for output (Windows only).\n"));
	print_help_line("      --win     ", _("Use Windows codepage for output (Windows only).\n"));
	print_help_line("      --dos     ", _("Use DOS codepage for output (Windows only).\n"));
120
#endif
121
	print_help_line("      --template=<file> ", _("Load a printf-like template from the <file>\n"));
122 123
	print_help_line("  -p, --printf=<format string>  ", _("Format and print hash sums.\n"));
	print_help_line("                ", _("See the RHash manual for details.\n"));
124
	rsh_exit(0);
125 126 127
}

/**
128
 * Print the names of all supported hash algorithms to the console.
129 130 131
 */
static void list_hashes(void)
{
132
	int id;
133
	for (id = 1; id < RHASH_ALL_HASHES; id <<= 1) {
134
		const char* hash_name = rhash_get_name(id);
135
		if (hash_name) rsh_fprintf(rhash_data.out, "%s\n", hash_name);
136 137
	}
	rsh_exit(0);
138 139
}

140 141 142 143 144 145
enum file_suffix_type {
	MASK_ACCEPT,
	MASK_EXCLUDE,
	MASK_CRC_ACCEPT
};

146
/**
147
 * Add a special file.
148 149
 *
 * @param o pointer to the options structure to update
150
 * @param path the path of the file
151 152
 * @param type the type of the option
 */
153
static void add_special_file(options_t *o, tstr_t path, unsigned file_mode)
154 155
{
	if (o->search_data) {
156
		file_search_add_file(o->search_data, path, file_mode);
157 158 159 160
		opt.has_files = 1;
	}
}

161
/**
162
 * Process --accept, --exclude and --crc-accept options.
163 164 165
 *
 * @param o pointer to the options structure to update
 * @param accept_string comma delimited string to parse
166
 * @param type the type of the option
167
 */
168
static void add_file_suffix(options_t *o, char* accept_string, unsigned type)
169
{
170 171 172
	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();
173
	file_mask_add_list(*ptr, accept_string);
174 175
}

176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
/**
* 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));
}

192 193
/**
 * Process an --openssl option.
194 195 196 197
 *
 * @param o pointer to the options structure to update
 * @param openssl_hashes comma delimited string with hash names
 * @param type ignored
198 199 200
 */
static void openssl_flags(options_t *o, char* openssl_hashes, unsigned type)
{
201
	(void)type;
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
	if (rhash_is_openssl_supported())
	{
		rhash_uptr_t openssl_supported_hashes = rhash_get_openssl_supported_mask();
		char *cur, *next;
		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 & 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);
227 228 229
			}
		}
	}
230 231
	else
		log_warning(_("compiled without openssl support\n"));
232 233 234 235
}

/**
 * Process --video option.
236 237
 *
 * @param o pointer to the options structure to update
238 239 240
 */
static void accept_video(options_t *o)
{
241
	add_file_suffix(o, ".avi,.ogm,.mkv,.mp4,.mpeg,.mpg,.asf,.rm,.wmv,.vob", MASK_ACCEPT);
242 243 244 245 246 247 248
}

/**
 * Say nya! Keep secret! =)
 */
static void nya(void)
{
249 250
	rsh_fprintf(rhash_data.out, "  /\\__/\\\n (^ _ ^.) %s\n  (_uu__)\n",
		/* TRANSLATORS: Keep it secret ;) */
251
		_("Purrr..."));
252
	rsh_exit(0);
253 254 255 256 257 258
}

/**
 * Process on --maxdepth option.
 *
 * @param o pointer to the processed option
259
 * @param number the string containing the max-depth number
260 261 262 263
 * @param param unused parameter
 */
static void set_max_depth(options_t *o, char* number, unsigned param)
{
264
	(void)param;
265
	if (strspn(number, "0123456789") < strlen(number)) {
266
		log_error(_("maxdepth parameter is not a number: %s\n"), number);
267 268 269
		rsh_exit(2);
	}
	o->find_max_depth = atoi(number);
270 271 272
}

/**
273
 * Set the length of a BitTorrent file piece.
274 275
 *
 * @param o pointer to the processed option
276
 * @param number string containing the piece length number
277 278 279 280
 * @param param unused parameter
 */
static void set_bt_piece_length(options_t *o, char* number, unsigned param)
{
281
	(void)param;
282
	if (strspn(number, "0123456789") < strlen(number)) {
283
		log_error(_("bt-piece-length parameter is not a number: %s\n"), number);
284 285 286
		rsh_exit(2);
	}
	o->bt_piece_length = (size_t)atoi(number);
287 288 289 290 291 292 293 294 295 296 297
}

/**
 * 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)
{
298
	(void)param;
299
	if ((*sep == '/' || *sep == '\\') && sep[1] == 0) {
300
		o->path_separator = *sep;
301
#if defined(_WIN32)
302
		/* MSYS environment changes '/' in command line to HOME, see http://www.mingw.org/wiki/FAQ */
303
	} else if (getenv("MSYSTEM") || getenv("TERM")) {
304
		log_warning(_("wrong path-separator, use '//' instead of '/' on MSYS\n"));
305
		o->path_separator = '/';
306
#endif
307
	} else {
308
		log_error(_("path-separator is not '/' or '\\': %s\n"), sep);
309 310
		rsh_exit(2);
	}
311 312 313
}

/**
314
 * Information about a command line option.
315 316 317
 */
typedef struct cmdline_opt_t
{
318
	unsigned short type;  /* how to process the option, see option_type_t below */
319 320
	char  short1, short2; /* short option names */
	char* long_name;      /* long option name */
321
	void* ptr;            /* auxiliary pointer, e.g. to an opt field */
322
	unsigned param;       /* optional integer parameter */
323 324
} cmdline_opt_t;

325 326 327 328 329 330 331
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 */
332 333 334 335 336 337 338
	F_TSTR = 3 | F_NEED_PARAM, /* store parameter as a tstr_t */
	F_TOUT = 4 | F_NEED_PARAM | F_OUTPUT_OPT,
	F_VFNC = 5, /* just call a function */
	F_PFNC = 6 | F_NEED_PARAM, /* process option parameter by calling a handler */
	F_TFNC = 7 | F_NEED_PARAM, /* process option parameter by calling a handler */
	F_UFNC = 8 | F_NEED_PARAM, /* pass UTF-8 encoded parameter to the handler */
	F_PRNT = 9, /* print a constant C-string and exit */
339 340 341 342
};

#define is_param_required(option_type) ((option_type) & F_NEED_PARAM)
#define is_output_modifier(option_type) ((option_type) & F_OUTPUT_OPT)
343

344
/* supported program options */
345 346
cmdline_opt_t cmdline_opt[] =
{
347
	/* program modes */
348
	{ F_UFLG, 'c',   0, "check",  &opt.mode, MODE_CHECK },
349
	{ F_UFLG, 'k',   0, "check-embedded",  &opt.mode, MODE_CHECK_EMBEDDED },
350 351 352 353 354
	{ 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 },
355
	{ F_VFNC, 'V',   0, "version", print_version, 0 },
356

357
	/* hash sums options */
358 359
	{ F_UFLG, 'a',   0, "all",    &opt.sum_flags, RHASH_ALL_HASHES },
	{ F_UFLG, 'C',   0, "crc32",  &opt.sum_flags, RHASH_CRC32 },
360
	{ F_UFLG,   0,   0, "crc32c", &opt.sum_flags, RHASH_CRC32C },
361 362 363 364 365 366 367
	{ 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 },
368 369 370 371
	{ 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 },
372 373 374 375 376
	{ 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 },
377 378 379 380 381 382 383
	{ F_UFLG, 'G',   0, "gost12-256",   &opt.sum_flags, RHASH_GOST12_256 },
	{ F_UFLG,   0,   0, "gost12-512",   &opt.sum_flags, RHASH_GOST12_512 },
	{ F_UFLG,   0,   0, "gost94",   &opt.sum_flags, RHASH_GOST94 },
	{ F_UFLG,   0,   0, "gost94-cryptopro", &opt.sum_flags, RHASH_GOST94_CRYPTOPRO },
	/* legacy: the following two gost options are left for compatibility */
	{ F_UFLG,   0,   0, "gost",   &opt.sum_flags, RHASH_GOST94 },
	{ F_UFLG,   0,   0, "gost-cryptopro", &opt.sum_flags, RHASH_GOST94_CRYPTOPRO },
384 385 386 387 388 389 390 391 392
	{ 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 },

393
	/* output formats */
394 395 396
	{ 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 },
397
	{ F_UFLG, 'g',   0, "magnet",  &opt.fmt, FMT_MAGNET },
398 399
	{ F_UFLG,   0,   0, "uppercase", &opt.flags, OPT_UPPERCASE },
	{ F_UFLG,   0,   0, "lowercase", &opt.flags, OPT_LOWERCASE },
400
	{ F_TSTR,   0,   0, "template",  &opt.template_file, 0 },
401
	{ F_CSTR, 'p',   0, "printf",  &opt.printf_str, 0 },
402

403
	/* other options */
404
	{ F_UFLG, 'r', 'R', "recursive", &opt.flags, OPT_RECURSIVE },
405 406
	{ F_TFNC, 'm',   0, "message", add_special_file, FILE_IFDATA },
	{ F_TFNC,   0,   0, "file-list", add_special_file, FILE_IFLIST },
407
	{ F_UFLG,   0,   0, "follow",  &opt.flags, OPT_FOLLOW },
408 409 410 411 412 413 414 415 416
	{ 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 },
417 418
	{ F_TOUT, 'o',   0, "output", &opt.output, 0 },
	{ F_TOUT, 'l',   0, "log",    &opt.log,    0 },
419 420 421
	{ 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 },
422 423 424 425 426
	{ 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 },
427
	{ F_UFNC,   0,   0, "bt-announce", bt_announce, 0 },
428
	{ F_TSTR,   0,   0, "bt-batch", &opt.bt_batch_file, 0 },
429 430 431 432 433
	{ 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 },
434 435 436
	{ F_UENC,   0,   0, "win",  &opt.flags, OPT_ANSI },
	{ F_UENC,   0,   0, "dos",  &opt.flags, OPT_OEM },
	/* legacy: the following two options are left for compatibility */
437 438
	{ F_UENC,   0,   0, "ansi", &opt.flags, OPT_ANSI },
	{ F_UENC,   0,   0, "oem",  &opt.flags, OPT_OEM },
439
#endif
440
	{ 0,0,0,0,0,0 }
441
};
442
cmdline_opt_t cmdline_file = { F_TFNC, 0, 0, "FILE", add_special_file, 0 };
443 444

/**
445
 * Log a message and exit the program.
446
 *
447
 * @param msg the message to log
448
 */
449
static void die(const char* msg)
450
{
451
	log_error(msg);
452
	rsh_exit(2);
453 454 455
}

/**
456
 * Log an error about unknown option and exit the program.
457
 *
458
 * @param option_name the name of the unknown option encountered
459
 */
460
static void fail_on_unknow_option(const char* option_name)
461
{
462
	log_error(_("unknown option: %s\n"), (option_name ? option_name : "?"));
463
	rsh_exit(2);
464
}
465 466 467 468 469 470 471 472 473

/* 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;
474 475 476 477

/**
 * Process given command line option
 *
478
 * @param opts the structure to store results of option processing
479
 * @param option option to process
480
 */
481
static void apply_option(options_t *opts, parsed_option_t* option)
482
{
483
	cmdline_opt_t* o = option->o;
484
	unsigned short option_type = o->type;
485
	char* value = NULL;
486

487
	/* check if option requires a parameter */
488 489
	if (is_param_required(option_type)) {
		if (!option->parameter) {
490
			log_error(_("argument is required for option %s\n"), option->name);
491 492
			rsh_exit(2);
		}
493

494
#ifdef _WIN32
495
		if (option_type == F_TOUT || option_type == F_TFNC || option_type == F_TSTR) {
496 497
			/* leave the value in UTF-16 */
			value = (char*)rsh_wcsdup((wchar_t*)option->parameter);
498 499 500 501
		}
		else if (option_type == F_UFNC) {
			/* convert from UTF-16 to UTF-8 */
			value = wchar_to_cstr((wchar_t*)option->parameter, CP_UTF8, NULL);
502 503
		} else {
			/* convert from UTF-16 */
504 505
			value = w2c((wchar_t*)option->parameter);
		}
506 507 508 509
		rsh_vector_add_ptr(opt.mem, value);
#else
		value = (char*)option->parameter;
#endif
510
	}
511

512
	/* process option, choosing the method by type */
513
	switch (option_type) {
514 515 516 517 518
	case F_UFLG:
	case F_UENC:
		*(unsigned*)((char*)opts + ((char*)o->ptr - (char*)&opt)) |= o->param;
		break;
	case F_CSTR:
519
	case F_TSTR:
520
	case F_TOUT:
521 522 523 524
		/* save the option parameter */
		*(char**)((char*)opts + ((char*)o->ptr - (char*)&opt)) = value;
		break;
	case F_PFNC:
525
	case F_TFNC:
526
	case F_UFNC:
527 528 529 530
		/* call option parameter handler */
		( ( void(*)(options_t *, char*, unsigned) )o->ptr )(opts, value, o->param);
		break;
	case F_VFNC:
531
		( ( void(*)(options_t *) )o->ptr )(opts); /* call option handler */
532 533
		break;
	case F_PRNT:
534 535
		log_msg("%s", (char*)o->ptr);
		rsh_exit(0);
536 537 538
		break;
	default:
		assert(0); /* impossible option type */
539
	}
540 541 542
}

/**
543
 * Search for config file.
544
 *
545
 * @return the relative path to config file
546
 */
547
static const char* find_conf_file(void)
548
{
549 550 551 552 553
#ifndef SYSCONFDIR
# define SYSCONFDIR "/etc"
#endif
#define CONFIG_FILENAME "rhashrc"

554 555 556
	char *dir1, *path;

#ifndef _WIN32 /* Linux/Unix part */
557 558 559 560 561 562 563 564 565 566 567 568
	/* first check for $XDG_CONFIG_HOME/rhash/rhashrc file */
	if ( (dir1 = getenv("XDG_CONFIG_HOME")) ) {
		dir1 = make_path(dir1, "rhash");
		path = make_path(dir1, CONFIG_FILENAME);
		free(dir1);
		if (is_regular_file(path)) {
			rsh_vector_add_ptr(opt.mem, path);
			return (conf_opt.config_file = path);
		}
		free(path);
	}
	/* then check for $HOME/.rhashrc file */
569
	if ( (dir1 = getenv("HOME")) ) {
570
		path = make_path(dir1, ".rhashrc");
571
		if (is_regular_file(path)) {
572 573 574 575 576 577
			rsh_vector_add_ptr(opt.mem, path);
			return (conf_opt.config_file = path);
		}
		free(path);
	}
	/* then check for global config */
578
	path = SYSCONFDIR "/" CONFIG_FILENAME;
579
	if (is_regular_file(path)) {
580 581 582 583 584 585
		return (conf_opt.config_file = path);
	}

#else /* _WIN32 */

	/* first check for the %APPDATA%\RHash\rhashrc config */
586
	if ( (dir1 = getenv("APPDATA")) ) {
587
		dir1 = make_path(dir1, "RHash");
588
		path = make_path(dir1, CONFIG_FILENAME);
589
		free(dir1);
590
		if (is_regular_file(path)) {
591
			rsh_vector_add_ptr(opt.mem, path);
592 593
			return (conf_opt.config_file = path);
		}
594
		free(path);
595 596 597 598
	}

	/* then check for %HOMEDRIVE%%HOMEPATH%\rhashrc */
	/* note that %USERPROFILE% is generally not a user home dir */
599
	if ( (dir1 = getenv("HOMEDRIVE")) && (path = getenv("HOMEPATH"))) {
600
		dir1 = make_path(dir1, path);
601
		path = make_path(dir1, CONFIG_FILENAME);
602
		free(dir1);
603
		if (is_regular_file(path)) {
604
			rsh_vector_add_ptr(opt.mem, path);
605 606
			return (conf_opt.config_file = path);
		}
607 608 609 610 611
		free(path);
	}
	
	/* check for ${PROGRAM_DIR}\rhashrc */
	if (rhash_data.program_dir && (dir1 = w2c(rhash_data.program_dir))) {
612
		path = make_path(dir1, CONFIG_FILENAME);
613 614 615 616 617 618
		free(dir1);
		if (is_regular_file(path)) {
			rsh_vector_add_ptr(opt.mem, path);
			return (conf_opt.config_file = path);
		}
		free(path);
619 620 621 622
	}
#endif /* _WIN32 */

	return (conf_opt.config_file = NULL); /* config file not found */
623 624 625
}

/**
626
 * Parse config file of the program.
627
 *
628
 * @return 0 on success, -1 on fail
629
 */
630
static int read_config(void)
631
{
632 633
#define LINE_BUF_SIZE 2048
	char buf[LINE_BUF_SIZE];
634
	file_t file;
635 636 637 638 639 640 641 642
	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;

643
	if (!find_conf_file()) return 0;
644

645 646 647
	file_init(&file, conf_opt.config_file, FILE_OPT_DONT_FREE_PATH);
	fd = file_fopen(&file, FOpenRead);
	file_cleanup(&file);
648
	if (!fd) return -1;
649

650
	while (fgets(buf, LINE_BUF_SIZE, fd)) {
651 652 653 654 655
		size_t index;
		cmdline_opt_t* t;
		char* line = str_trim(buf);
		char  *name, *value;

656
		if (*line == 0 || IS_COMMENT(*line)) continue;
657 658 659

		/* search for '=' */
		index = strcspn(line, "=");
660
		if (line[index] == 0) {
661
			log_warning(_("%s: can't parse line \"%s\"\n"), conf_opt.config_file, line);
662 663 664 665 666
			continue;
		}
		line[index] = 0;
		name = str_trim(line);

667 668
		for (t = cmdline_opt; t->type; t++) {
			if (strcmp(name, t->long_name) == 0) {
669 670 671 672
				break;
			}
		}

673
		if (!t->type) {
674
			log_warning(_("%s: unknown option \"%s\"\n"), conf_opt.config_file, line);
675 676 677 678 679 680
			continue;
		}

		value = str_trim(line + index + 1);

		/* process a long option */
681
		if (is_param_required(t->type)) {
682 683
			rsh_vector_add_ptr(opt.mem, (value = rsh_strdup(value)));;
		} else {
684
			/* possible boolean values for a config file variable */
685
			static const char* strings[] = { "on", "yes", "true", 0 };
686
			const char** cmp;
687 688
			for (cmp = strings; *cmp && strcmp(value, *cmp); cmp++);
			if (*cmp == 0) continue;
689 690 691 692 693 694 695 696 697 698
		}

		option.name = name;
		option.parameter = value;
		option.o = t;
		apply_option(&conf_opt, &option);
	}
	res = fclose(fd);

#ifdef _WIN32
699
	if ( (opt.flags & OPT_ENCODING) == 0 ) opt.flags |= (conf_opt.flags & OPT_ENCODING);
700 701
#endif
	return (res == 0 ? 0 : -1);
702 703 704
}

/**
705 706
 * Find long option info, by it's name and retrieve its parameter if required.
 * Error is reported for unknown options.
707
 *
708
 * @param option structure to receive the parsed option info
709
 * @param parg pointer to a command line argument
710
 */
711
static void parse_long_option(parsed_option_t* option, rsh_tchar ***parg)
712
{
713
	size_t length;
714
	rsh_tchar* eq_sign;
715 716
	cmdline_opt_t *t;
	char* name;
717 718

#ifdef _WIN32
719
	rsh_tchar* wname = **parg; /* "--<option name>" */
720 721 722 723 724 725 726
	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);
727
	if (length < 30) {
728
		size_t i = 0;
729 730
		for (; i < length; i++) {
			if (((unsigned)wname[i]) <= 128) name[i] = (char)wname[i];
731 732 733 734 735 736
			else {
				fail = 1;
				break;
			}
		}
		name[i] = '\0';
737

738 739 740 741
		name += 2; /* skip  "--" */
		length -= 2;
	} else fail = 1;

742
	if (fail) fail_on_unknow_option(w2c(**parg));
743 744 745 746 747
#else
	option->name = **parg;
	name =  **parg + 2; /* skip "--" */
	length = ((eq_sign = strchr(name, '=')) ? (size_t)(eq_sign - name) : strlen(name));
	name[length] = '\0';
748
#endif
749
	/* search for the option by its name */
750
	for (t = cmdline_opt; t->type && (strncmp(name, t->long_name, length) != 0 ||
751 752
		strlen(t->long_name) != length); t++) {
	}
753
	if (!t->type) {
754 755 756 757
		fail_on_unknow_option(option->name); /* report error and exit */
	}

	option->o = t; /* store the option found */
758
	if (is_param_required(t->type)) {
759 760 761 762
		/* store parameter without a code page conversion */
		option->parameter = (eq_sign ? eq_sign + 1 : *(++(*parg)));
	}
}
763

764 765 766
/**
 * Parsed program command line.
 */
767 768 769
struct parsed_cmd_line_t
{
	blocks_vector_t options; /* array of parsed options */
770
	int  argc;
771
	char **argv;
772
#ifdef _WIN32
773
	rsh_tchar** warg; /* program arguments in Unicode */
774
#endif
775
};
776

777 778 779 780 781 782 783 784 785 786 787 788
/**
 * Allocate parsed option.
 *
 * @param cmd_line the command line to store the parsed option into.
 * @return allocated parsed option
 */
static parsed_option_t* new_option(struct parsed_cmd_line_t* cmd_line)
{
	rsh_blocks_vector_add_empty(&cmd_line->options, 16, sizeof(parsed_option_t));
	return rsh_blocks_vector_get_item(&cmd_line->options, cmd_line->options.size - 1, 16, parsed_option_t);
}

789
/**
790 791 792
 * Parse command line arguments.
 *
 * @param cmd_line structure to store parsed options data
793
 */
794
static void parse_cmdline_options(struct parsed_cmd_line_t* cmd_line)
795
{
796
	int argc;
797
	int b_opt_end = 0;
798
	rsh_tchar **parg, **end_arg;
799
	parsed_option_t *next_opt;
800

801 802
#ifdef _WIN32
	parg = cmd_line->warg = CommandLineToArgvW(GetCommandLineW(), &argc);
803
	if ( NULL == parg || argc < 1) {
804
		die(_("CommandLineToArgvW failed\n"));
805 806
	}
#else
807
	argc = cmd_line->argc;
808 809
	parg = cmd_line->argv;
#endif
810

811
	/* allocate array for files */
812
	end_arg = parg + argc;
813 814

	/* loop by program arguments */
815
	for (parg++; parg < end_arg; parg++) {
816
		/* if argument is not an option */
817
		if ((*parg)[0] != RSH_T('-') || (*parg)[1] == 0 || b_opt_end) {
818
			/* it's a file, note that '-' is interpreted as stdin */
819 820 821 822 823
			next_opt = new_option(cmd_line);
			next_opt->name = "";
			next_opt->o = &cmdline_file;
			next_opt->parameter = *parg;
		} else if ((*parg)[1] == L'-' && (*parg)[2] == 0) {
824 825
			b_opt_end = 1; /* string "--" means end of options */
			continue;
826 827
		} else if ((*parg)[1] == RSH_T('-')) {
			next_opt = new_option(cmd_line);
828 829 830
			parse_long_option(next_opt, &parg);

			/* process encoding and -o/-l options early */
831
			if (is_output_modifier(next_opt->o->type)) {
832 833
				apply_option(&opt, next_opt);
			}
834
		} else if ((*parg)[1] != 0) {
835
			/* found '-'<some string> */
836
			rsh_tchar* ptr;
837

838 839
			/* parse short options. A string of several characters is interpreted
			 * as separate short options */
840
			for (ptr = *parg + 1; *ptr; ptr++) {
841 842
				cmdline_opt_t *t;
				char ch = (char)*ptr;
843

844
#ifdef _WIN32
845
				if (((unsigned)*ptr) >= 128) {
846 847 848 849
					ptr[1] = 0;
					fail_on_unknow_option(w2c(ptr));
				}
#endif
850
				next_opt = new_option(cmd_line);
851 852 853
				next_opt->buf[0] = '-', next_opt->buf[1] = ch, next_opt->buf[2] = '\0';
				next_opt->name = next_opt->buf;
				next_opt->parameter = NULL;
854

855
				/* search for the short option */
856 857
				for (t = cmdline_opt; t->type && ch != t->short1 && ch != t->short2; t++);
				if (!t->type) fail_on_unknow_option(next_opt->buf);
858
				next_opt->o = t;
859
				if (is_param_required(t->type)) {
860
					next_opt->parameter = (ptr[1] ? ptr + 1 : *(++parg));
861
					if (!next_opt->parameter) {
862
						/* note: need to check for parameter here, for early -o/-l options processing */
863
						log_error(_("argument is required for option %s\n"), next_opt->name);
864 865 866 867 868
						rsh_exit(2);
					}
				}

				/* process encoding and -o/-l options early */
869
				if (is_output_modifier(t->type)) {
870 871
					apply_option(&opt, next_opt);
				}
872
				if (next_opt->parameter) break;  /* a parameter ends the short options string */
873 874 875 876
			}
		}

	} /* for */
877 878 879
}

/**
880 881
 * Apply all parsed command line options: set binary flags, store strings,
 * and do complex options handling by calling callbacks.
882
 *
883
 * @param cmd_line the parsed options information
884
 */
885
static void apply_cmdline_options(struct parsed_cmd_line_t *cmd_line)
886
{
887 888
	size_t count = cmd_line->options.size;
	size_t i;
889
	for (i = 0; i < count; i++) {
890 891 892
		parsed_option_t* o = (parsed_option_t*)rsh_blocks_vector_get_ptr(
			&cmd_line->options, i, 16, sizeof(parsed_option_t));

893
		/* process the option, if it was not applied early */
894
		if (!is_output_modifier(o->o->type)) {
895 896
			apply_option(&opt, o);
		}
897 898
	}

899
	/* if no formatting options were specified at the command line */
900 901
	if (!opt.printf_str && !opt.template_file && !opt.sum_flags && !opt.fmt) {
		/* copy the format from config */
902
		opt.printf_str = conf_opt.printf_str;
903 904 905
		opt.template_file = conf_opt.template_file;
	}

906 907 908
	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;
909 910
	}

911
	if (!opt.mode)  opt.mode = conf_opt.mode;
912 913
	opt.flags |= conf_opt.flags; /* copy all non-sum options */

914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934
	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.flags & OPT_EMBED_CRC) opt.sum_flags |= RHASH_CRC32;
	if (opt.openssl_mask == 0) opt.openssl_mask = conf_opt.openssl_mask;
935 936 937
	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;
	opt.search_data->max_depth = opt.find_max_depth;
938 939

	/* set defaults */
940
	if (opt.embed_crc_delimiter == 0) opt.embed_crc_delimiter = " ";
941 942 943 944 945 946 947 948 949
}

/**
 * Try to detect hash sums options from program name.
 *
 * @param progName the program name
 */
static void set_default_sums_flags(const char* progName)
{
950 951
	char *buf;
	int res = 0;
952

953 954
	/* remove directory name from path */
	const char* p = strrchr(progName, '/');
955
	if (p) progName = p+1;
956
#ifdef _WIN32
957
	p = strrchr(progName, '\\');
958
	if (p) progName = p+1;
959 960
#endif

961 962 963
	/* convert progName to lowercase */
	buf = str_tolower(progName);

964 965
	if (strstr(buf, "crc32c")) res |= RHASH_CRC32C;
	else if (strstr(buf, "crc32")) res |= RHASH_CRC32;
966 967 968 969 970 971 972 973 974 975 976 977 978 979 980
	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;
981 982 983 984
	if (strstr(buf, "gost12-256"))  res |= RHASH_GOST12_256;
	if (strstr(buf, "gost12-512"))  res |= RHASH_GOST12_512;
	if (strstr(buf, "gost94-cryptopro"))  res |= RHASH_GOST94_CRYPTOPRO;
	else if (strstr(buf, "gost94"))  res |= RHASH_GOST94;
985 986 987 988 989 990 991 992 993 994 995 996
	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;
997 998 999 1000

	free(buf);

	/* change program flags only if opt.sum_flags was not set */
1001
	if (!opt.sum_flags) {
1002 1003
		opt.sum_flags = (res ? res : (opt.fmt == FMT_MAGNET ? RHASH_TTH | RHASH_ED2K | RHASH_AICH :
			(!(opt.mode & MODE_CHECK) ? RHASH_CRC32 : 0)));
1004
	}
1005 1006 1007
}

/**
1008
 * Destroy a parsed options object.
1009
 *
1010
 * @param o pointer to the options object to destroy.
1011
 */
1012
void options_destroy(struct options_t* o)
1013
{
1014
	file_mask_free(o->files_accept);
1015
	file_mask_free(o->files_exclude);
1016
	file_mask_free(o->crc_accept);
1017
	rsh_vector_free(o->bt_announce);
1018
	rsh_vector_free(o->mem);
1019
	file_search_data_free(o->search_data);
1020 1021 1022
}

/**
1023
 * Check that options do not conflict with each other.
1024
 * Also make some final options processing steps.
1025
 */
1026
static void make_final_options_checks(void)
1027
{
1028
	unsigned ff; /* formatting flags */
1029

1030
	if ((opt.flags & OPT_VERBOSE) && conf_opt.config_file) {
1031
		/* note that the first log_msg call shall be made after setup_output() */
1032
		log_msg(_("Config file: %s\n"), (conf_opt.config_file ? conf_opt.config_file : _("None")));
1033 1034
	}

1035 1036
	if (opt.bt_batch_file) opt.mode |= MODE_TORRENT;
	if (opt.mode & MODE_TORRENT) opt.sum_flags |= RHASH_BTIH;
1037

1038
	/* check that no more than one program mode specified */
1039
	if (opt.mode & (opt.mode - 1)) {
1040
		die(_("incompatible program modes\n"));
1041 1042
	}

1043
	ff = (opt.printf_str ? 1 : 0) | (opt.template_file ? 2 : 0) | (opt.fmt ? 4 : 0);
1044
	if ((opt.fmt & (opt.fmt - 1)) || (ff & (ff - 1))) {
1045
		die(_("too many formatting options\n"));
1046 1047
	}

1048
	if (!opt.crc_accept) opt.crc_accept = file_mask_new_from_list(".sfv");
1049

1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060
	if (opt.openssl_mask) rhash_set_openssl_mask(opt.openssl_mask);
}

static struct parsed_cmd_line_t cmd_line;

static void cmd_line_destroy(void)
{
	rsh_blocks_vector_destroy(&cmd_line.options);
#ifdef _WIN32
	LocalFree(cmd_line.warg);
#endif
1061 1062 1063 1064 1065 1066 1067
}

/**
 * Parse command line options.
 *
 * @param argv program arguments
 */
1068
void read_options(int argc, char *argv[])
1069
{
1070
	opt.mem = rsh_vector_new_simple();
1071
	opt.search_data = file_search_data_new();
1072
	opt.find_max_depth = -1;
1073

1074 1075 1076 1077
	/* initialize cmd_line */
	memset(&cmd_line, 0, sizeof(cmd_line));
	rsh_blocks_vector_init(&cmd_line.options);
	cmd_line.argv = argv;
1078
	cmd_line.argc = argc;
1079
	rsh_install_exit_handler(cmd_line_destroy);
1080

1081 1082 1083
	/* parse command line and apply encoding options */
	parse_cmdline_options(&cmd_line);
	read_config();
1084

1085
	/* setup the program output */
1086
	IF_WINDOWS(setup_console());
1087
	setup_output();
1088

1089
	apply_cmdline_options(&cmd_line); /* process the rest of command options */
1090

1091 1092 1093
	/* options were processed, so we don't need them any more */
	rsh_remove_exit_handler();
	cmd_line_destroy();
1094

1095 1096
	make_final_options_checks();
	set_default_sums_flags(argv[0]); /* detect default hashes from program name */
1097
}