From 90405eae1ccb827161527b026b6cff2c14ca966c Mon Sep 17 00:00:00 2001 From: Keuin Date: Sun, 29 May 2022 01:20:20 +0800 Subject: Parse cli arguments using optparse (https://github.com/skeeto/optparse). --- main.c | 139 ++++++++++++++------- optparse.h | 403 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 496 insertions(+), 46 deletions(-) create mode 100644 optparse.h diff --git a/main.c b/main.c index 27f37dd..55dc149 100644 --- a/main.c +++ b/main.c @@ -10,6 +10,11 @@ #include +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static + +#include "optparse.h" + /* because DES discards all LSBs of every byte, * the hex alphabet can be reduced by masking out LSB from all characters */ const char *keychars = "02468@BDF"; @@ -258,7 +263,7 @@ int thread_worker(thread_param *param) { return 0; } -void benchmark() { +void run_benchmark() { #define TEST_CT_LENGTH 1024u const int end = 1000000; atomic_bool found = false; @@ -298,35 +303,101 @@ void benchmark() { } } +void print_help(const char *program_name) { + printf( + "Usage: %s " + "[-o ] " + "[-j ] " + "[--benchmark]\n" + " -o --output \tsave the decrypted photo into a file\n" + " -j --jobs \thow many threads to use (default: 1)\n" + " -b --benchmark\trun a benchmark to measure the speed\n" + " -h --help\t\tprint this help menu and exit\n", + program_name + ); +} + int main(int argc, char *argv[]) { crack_result.plaintext = NULL; - if (argc == 1) { - USAGE: - printf("Usage: %s " - "[] " - "[-j ] " - "[--benchmark]\n" - "The decrypted image won't be saved if " - "save path is not specified.\n" - "threads: how many workers to run at the same time, " - "default: 1\n" - "--benchmark: if set, other parameters will be ignored\n", - argv[0]); - return 0; + /* arguments */ + char *plaintext_save_path = NULL; + int threads = 1; + bool benchmark = false; + + /* parse commandline arguments */ + const struct optparse_long options[] = { + {"output", 'o', OPTPARSE_REQUIRED}, + {"jobs", 'j', OPTPARSE_REQUIRED}, + {"benchmark", 'b', OPTPARSE_NONE}, + {"help", 'h', OPTPARSE_NONE}, + {0} + }; + + int opt; + struct optparse parse_state; + optparse_init(&parse_state, argv); + while ((opt = optparse_long( + &parse_state, options, NULL)) != -1) { + switch (opt) { + case 'o': + plaintext_save_path = parse_state.optarg; + break; + case 'j': + errno = 0; + char *end; + long val = strtol(parse_state.optarg, &end, 10); + if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || + (errno != 0 && val == 0) || + end == parse_state.optarg || + val <= 0) { + printf("Invalid parameter: thread count.\n"); + return 1; + } + threads = (int) val; + break; + case 'b': + benchmark = true; + break; + case 'h': + case '?': + if (opt == '?') { + printf("Illegal parameter: %s\n", + parse_state.errmsg); + } + print_help(argv[0]); + return 1; + default: + break; + } + } + + /* parse remaining arguments */ + char *ciphertext_file_path = NULL; + char *arg; + arg = optparse_arg(&parse_state); + if (arg != NULL) { + ciphertext_file_path = arg; + } + + arg = optparse_arg(&parse_state); + if (arg != NULL) { + printf("Redundant argument: %s\n", arg); + return 1; } + /* finish parsing arguments */ /* run benchmark and exit */ - for (int i = 1; i < argc; ++i) { - if (!strcmp(argv[i], "--benchmark")) { - benchmark(); - return 0; - } + if (benchmark) { + run_benchmark(); + return 0; } - const char *plaintext_save_path = - (argc >= 3 && strcmp(argv[2], "-j") != 0) ? (argv[2]) : NULL; - const char *ciphertext_file_path = argv[1]; + if (ciphertext_file_path == NULL) { + printf("Input file is not specified. " + "Use --help or -h to get help menu.\n"); + return 1; + } /* open file */ FILE *fp; @@ -378,30 +449,6 @@ int main(int argc, char *argv[]) { ciphertext = ciphertext_buf; ciphertext_len = ciphertext_length; - int threads = 1; - - /* read thread count from argv */ - for (int i = 1; i < argc; ++i) { - if (!strcmp(argv[i], "-j")) { - if (i == argc - 1) { - printf("-j requires an integer parameter.\n"); - goto USAGE; - } - errno = 0; - char *end; - long val = strtol(argv[i + 1], &end, 10); - if ((errno == ERANGE && (val == LONG_MAX || val == LONG_MIN)) || - (errno != 0 && val == 0) || - end == argv[i + 1] || - val <= 0) { - printf("Invalid thread count number.\n"); - goto USAGE; /* invalid integer */ - } - threads = (int) val; - break; - } - } - /* start searching */ printf("Searching key (using %d workers)...\n", threads); fflush(stdout); diff --git a/optparse.h b/optparse.h new file mode 100644 index 0000000..f96184a --- /dev/null +++ b/optparse.h @@ -0,0 +1,403 @@ +/* Optparse --- portable, reentrant, embeddable, getopt-like option parser + * + * This is free and unencumbered software released into the public domain. + * + * To get the implementation, define OPTPARSE_IMPLEMENTATION. + * Optionally define OPTPARSE_API to control the API's visibility + * and/or linkage (static, __attribute__, __declspec). + * + * The POSIX getopt() option parser has three fatal flaws. These flaws + * are solved by Optparse. + * + * 1) Parser state is stored entirely in global variables, some of + * which are static and inaccessible. This means only one thread can + * use getopt(). It also means it's not possible to recursively parse + * nested sub-arguments while in the middle of argument parsing. + * Optparse fixes this by storing all state on a local struct. + * + * 2) The POSIX standard provides no way to properly reset the parser. + * This means for portable code that getopt() is only good for one + * run, over one argv with one option string. It also means subcommand + * options cannot be processed with getopt(). Most implementations + * provide a method to reset the parser, but it's not portable. + * Optparse provides an optparse_arg() function for stepping over + * subcommands and continuing parsing of options with another option + * string. The Optparse struct itself can be passed around to + * subcommand handlers for additional subcommand option parsing. A + * full reset can be achieved by with an additional optparse_init(). + * + * 3) Error messages are printed to stderr. This can be disabled with + * opterr, but the messages themselves are still inaccessible. + * Optparse solves this by writing an error message in its errmsg + * field. The downside to Optparse is that this error message will + * always be in English rather than the current locale. + * + * Optparse should be familiar with anyone accustomed to getopt(), and + * it could be a nearly drop-in replacement. The option string is the + * same and the fields have the same names as the getopt() global + * variables (optarg, optind, optopt). + * + * Optparse also supports GNU-style long options with optparse_long(). + * The interface is slightly different and simpler than getopt_long(). + * + * By default, argv is permuted as it is parsed, moving non-option + * arguments to the end. This can be disabled by setting the `permute` + * field to 0 after initialization. + */ +#ifndef OPTPARSE_H +#define OPTPARSE_H + +#ifndef OPTPARSE_API +# define OPTPARSE_API +#endif + +struct optparse { + char **argv; + int permute; + int optind; + int optopt; + char *optarg; + char errmsg[64]; + int subopt; +}; + +enum optparse_argtype { + OPTPARSE_NONE, + OPTPARSE_REQUIRED, + OPTPARSE_OPTIONAL +}; + +struct optparse_long { + const char *longname; + int shortname; + enum optparse_argtype argtype; +}; + +/** + * Initializes the parser state. + */ +OPTPARSE_API +void optparse_init(struct optparse *options, char **argv); + +/** + * Read the next option in the argv array. + * @param optstring a getopt()-formatted option string. + * @return the next option character, -1 for done, or '?' for error + * + * Just like getopt(), a character followed by no colons means no + * argument. One colon means the option has a required argument. Two + * colons means the option takes an optional argument. + */ +OPTPARSE_API +int optparse(struct optparse *options, const char *optstring); + +/** + * Handles GNU-style long options in addition to getopt() options. + * This works a lot like GNU's getopt_long(). The last option in + * longopts must be all zeros, marking the end of the array. The + * longindex argument may be NULL. + */ +OPTPARSE_API +int optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex); + +/** + * Used for stepping over non-option arguments. + * @return the next non-option argument, or NULL for no more arguments + * + * Argument parsing can continue with optparse() after using this + * function. That would be used to parse the options for the + * subcommand returned by optparse_arg(). This function allows you to + * ignore the value of optind. + */ +OPTPARSE_API +char *optparse_arg(struct optparse *options); + +/* Implementation */ +#ifdef OPTPARSE_IMPLEMENTATION + +#define OPTPARSE_MSG_INVALID "invalid option" +#define OPTPARSE_MSG_MISSING "option requires an argument" +#define OPTPARSE_MSG_TOOMANY "option takes no arguments" + +static int +optparse_error(struct optparse *options, const char *msg, const char *data) +{ + unsigned p = 0; + const char *sep = " -- '"; + while (*msg) + options->errmsg[p++] = *msg++; + while (*sep) + options->errmsg[p++] = *sep++; + while (p < sizeof(options->errmsg) - 2 && *data) + options->errmsg[p++] = *data++; + options->errmsg[p++] = '\''; + options->errmsg[p++] = '\0'; + return '?'; +} + +OPTPARSE_API +void +optparse_init(struct optparse *options, char **argv) +{ + options->argv = argv; + options->permute = 1; + options->optind = 1; + options->subopt = 0; + options->optarg = 0; + options->errmsg[0] = '\0'; +} + +static int +optparse_is_dashdash(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; +} + +static int +optparse_is_shortopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; +} + +static int +optparse_is_longopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; +} + +static void +optparse_permute(struct optparse *options, int index) +{ + char *nonoption = options->argv[index]; + int i; + for (i = index; i < options->optind - 1; i++) + options->argv[i] = options->argv[i + 1]; + options->argv[options->optind - 1] = nonoption; +} + +static int +optparse_argtype(const char *optstring, char c) +{ + int count = OPTPARSE_NONE; + if (c == ':') + return -1; + for (; *optstring && c != *optstring; optstring++); + if (!*optstring) + return -1; + if (optstring[1] == ':') + count += optstring[2] == ':' ? 2 : 1; + return count; +} + +OPTPARSE_API +int +optparse(struct optparse *options, const char *optstring) +{ + int type; + char *next; + char *option = options->argv[options->optind]; + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (!optparse_is_shortopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse(options, optstring); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + option += options->subopt + 1; + options->optopt = option[0]; + type = optparse_argtype(optstring, option[0]); + next = options->argv[options->optind + 1]; + switch (type) { + case -1: { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optind++; + return optparse_error(options, OPTPARSE_MSG_INVALID, str); + } + case OPTPARSE_NONE: + if (option[1]) { + options->subopt++; + } else { + options->subopt = 0; + options->optind++; + } + return option[0]; + case OPTPARSE_REQUIRED: + options->subopt = 0; + options->optind++; + if (option[1]) { + options->optarg = option + 1; + } else if (next != 0) { + options->optarg = next; + options->optind++; + } else { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optarg = 0; + return optparse_error(options, OPTPARSE_MSG_MISSING, str); + } + return option[0]; + case OPTPARSE_OPTIONAL: + options->subopt = 0; + options->optind++; + if (option[1]) + options->optarg = option + 1; + else + options->optarg = 0; + return option[0]; + } + return 0; +} + +OPTPARSE_API +char * +optparse_arg(struct optparse *options) +{ + char *option = options->argv[options->optind]; + options->subopt = 0; + if (option != 0) + options->optind++; + return option; +} + +static int +optparse_longopts_end(const struct optparse_long *longopts, int i) +{ + return !longopts[i].longname && !longopts[i].shortname; +} + +static void +optparse_from_long(const struct optparse_long *longopts, char *optstring) +{ + char *p = optstring; + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + if (longopts[i].shortname && longopts[i].shortname < 127) { + int a; + *p++ = longopts[i].shortname; + for (a = 0; a < (int)longopts[i].argtype; a++) + *p++ = ':'; + } + } + *p = '\0'; +} + +/* Unlike strcmp(), handles options containing "=". */ +static int +optparse_longopts_match(const char *longname, const char *option) +{ + const char *a = option, *n = longname; + if (longname == 0) + return 0; + for (; *a && *n && *a != '='; a++, n++) + if (*a != *n) + return 0; + return *n == '\0' && (*a == '\0' || *a == '='); +} + +/* Return the part after "=", or NULL. */ +static char * +optparse_longopts_arg(char *option) +{ + for (; *option && *option != '='; option++); + if (*option == '=') + return option + 1; + else + return 0; +} + +static int +optparse_long_fallback(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int result; + char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ + optparse_from_long(longopts, optstring); + result = optparse(options, optstring); + if (longindex != 0) { + *longindex = -1; + if (result != -1) { + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) + if (longopts[i].shortname == options->optopt) + *longindex = i; + } + } + return result; +} + +OPTPARSE_API +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int i; + char *option = options->argv[options->optind]; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (optparse_is_shortopt(option)) { + return optparse_long_fallback(options, longopts, longindex); + } else if (!optparse_is_longopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse_long(options, longopts, longindex); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + + /* Parse as long option. */ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + option += 2; /* skip "--" */ + options->optind++; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + const char *name = longopts[i].longname; + if (optparse_longopts_match(name, option)) { + char *arg; + if (longindex) + *longindex = i; + options->optopt = longopts[i].shortname; + arg = optparse_longopts_arg(option); + if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { + return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); + } if (arg != 0) { + options->optarg = arg; + } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { + options->optarg = options->argv[options->optind]; + if (options->optarg == 0) + return optparse_error(options, OPTPARSE_MSG_MISSING, name); + else + options->optind++; + } + return options->optopt; + } + } + return optparse_error(options, OPTPARSE_MSG_INVALID, option); +} + +#endif /* OPTPARSE_IMPLEMENTATION */ +#endif /* OPTPARSE_H */ -- cgit v1.2.3