From c656dceb3f04c2e3da7af40824c97eff5119a0b9 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Fri, 2 Nov 2018 11:57:57 +1030 Subject: [PATCH] opt: add new parse_early_args_incomplete. If we have plugins, and those can register args, we have a problem finding the plugin dir! So, do a best-effort incomplete parse. Note that this can screw up in theory if we have "--unknown --foo" since we don't know if unknown takes an argument (in which case, ignore --foo) or not. Signed-off-by: Rusty Russell --- ccan/opt/opt.c | 21 +++++++++++++--- ccan/opt/opt.h | 24 ++++++++++++++++++ ccan/opt/parse.c | 15 ++++++++--- ccan/opt/private.h | 2 +- ccan/opt/test/run-early_incomplete.c | 37 ++++++++++++++++++++++++++++ ccan/opt/test/utils.c | 23 +++++++++++++++++ ccan/opt/test/utils.h | 1 + 7 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 ccan/opt/test/run-early_incomplete.c diff --git a/ccan/opt/opt.c b/ccan/opt/opt.c index 09b29bca..0514dc87 100644 --- a/ccan/opt/opt.c +++ b/ccan/opt/opt.c @@ -207,14 +207,15 @@ bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)) /* This helps opt_usage. */ opt_argv0 = argv[0]; - while ((ret = parse_one(argc, argv, 0, &offset, errlog)) == 1); + while ((ret = parse_one(argc, argv, 0, &offset, errlog, false)) == 1); /* parse_one returns 0 on finish, -1 on error */ return (ret == 0); } -bool opt_early_parse(int argc, char *argv[], - void (*errlog)(const char *fmt, ...)) +static bool early_parse(int argc, char *argv[], + void (*errlog)(const char *fmt, ...), + bool ignore_unknown) { int ret; unsigned off = 0; @@ -226,7 +227,7 @@ bool opt_early_parse(int argc, char *argv[], /* This helps opt_usage. */ opt_argv0 = argv[0]; - while ((ret = parse_one(&argc, tmpargv, OPT_EARLY, &off, errlog)) == 1); + while ((ret = parse_one(&argc, tmpargv, OPT_EARLY, &off, errlog, ignore_unknown)) == 1); opt_alloc.free(tmpargv); @@ -234,6 +235,18 @@ bool opt_early_parse(int argc, char *argv[], return (ret == 0); } +bool opt_early_parse(int argc, char *argv[], + void (*errlog)(const char *fmt, ...)) +{ + return early_parse(argc, argv, errlog, false); +} + +bool opt_early_parse_incomplete(int argc, char *argv[], + void (*errlog)(const char *fmt, ...)) +{ + return early_parse(argc, argv, errlog, true); +} + void opt_free_table(void) { opt_alloc.free(opt_table); diff --git a/ccan/opt/opt.h b/ccan/opt/opt.h index 0d508cf6..c642ec6f 100644 --- a/ccan/opt/opt.h +++ b/ccan/opt/opt.h @@ -286,6 +286,30 @@ bool opt_parse(int *argc, char *argv[], void (*errlog)(const char *fmt, ...)); bool opt_early_parse(int argc, char *argv[], void (*errlog)(const char *fmt, ...)); +/** + * opt_early_parse_incomplete - parse early arguments, ignoring unknown ones. + * @argc: argc + * @argv: argv array. + * @errlog: the function to print errors + * + * If you have plugins, you might need to do early parsing (eg. to find the + * plugin directory) but you don't know what options the plugins will want. + * + * Thus, this function is just like opt_early_parse, but ignores unknown options. + * + * Example: + * if (!opt_early_parse_incomplete(argc, argv, opt_log_stderr)) { + * printf("You screwed up, aborting!\n"); + * exit(1); + * } + * + * See Also: + * opt_early_parse() + */ +bool opt_early_parse_incomplete(int argc, char *argv[], + void (*errlog)(const char *fmt, ...)); + + /** * opt_free_table - reset the opt library. * diff --git a/ccan/opt/parse.c b/ccan/opt/parse.c index 94d75ad1..d227f7bc 100644 --- a/ccan/opt/parse.c +++ b/ccan/opt/parse.c @@ -30,7 +30,7 @@ static void consume_option(int *argc, char *argv[], unsigned optnum) /* Returns 1 if argument consumed, 0 if all done, -1 on error. */ int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset, - void (*errlog)(const char *fmt, ...)) + void (*errlog)(const char *fmt, ...), bool unknown_ok) { unsigned i, arg, len; const char *o, *optarg = NULL; @@ -67,10 +67,13 @@ int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset, continue; break; } - if (!o) + if (!o) { + if (unknown_ok) + goto ok; return parse_err(errlog, argv[0], argv[arg], strlen(argv[arg]), "unrecognized option"); + } /* For error messages, we include the leading '--' */ o -= 2; len += 2; @@ -82,10 +85,15 @@ int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset, (*offset)++; break; } - if (!o) + if (!o) { + if (unknown_ok) { + (*offset)++; + goto ok; + } return parse_err(errlog, argv[0], argv[arg], strlen(argv[arg]), "unrecognized option"); + } /* For error messages, we include the leading '-' */ o--; len = 2; @@ -120,6 +128,7 @@ int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset, return -1; } +ok: /* If no more letters in that short opt, reset offset. */ if (*offset && !argv[arg][*offset + 1]) *offset = 0; diff --git a/ccan/opt/private.h b/ccan/opt/private.h index 47b8c4e0..0621c82e 100644 --- a/ccan/opt/private.h +++ b/ccan/opt/private.h @@ -22,6 +22,6 @@ struct opt_alloc { extern struct opt_alloc opt_alloc; int parse_one(int *argc, char *argv[], enum opt_type is_early, unsigned *offset, - void (*errlog)(const char *fmt, ...)); + void (*errlog)(const char *fmt, ...), bool unknown_ok); #endif /* CCAN_OPT_PRIVATE_H */ diff --git a/ccan/opt/test/run-early_incomplete.c b/ccan/opt/test/run-early_incomplete.c new file mode 100644 index 00000000..4720cb78 --- /dev/null +++ b/ccan/opt/test/run-early_incomplete.c @@ -0,0 +1,37 @@ +/* With errlog == NULL, we never get a "failure". */ +#include +#include +#include +#include +#include +#include +#include "utils.h" + +int main(int argc, char *argv[]) +{ + plan_tests(8); + + /* Simple short args.*/ + opt_register_noarg("-a", test_noarg, NULL, "All"); + opt_register_early_noarg("-b|--blong", test_noarg, NULL, "All"); + + /* This is OK. */ + ok1(parse_early_args_incomplete(&argc, &argv, "-c", NULL)); + ok1(test_cb_called == 0); + + /* Skips letters correctly */ + ok1(parse_early_args_incomplete(&argc, &argv, "-ca", NULL)); + ok1(test_cb_called == 0); /* a is not an early arg! */ + + test_cb_called = 0; + ok1(parse_early_args_incomplete(&argc, &argv, "-bca", NULL)); + ok1(test_cb_called == 1); + + test_cb_called = 0; + ok1(parse_early_args_incomplete(&argc, &argv, "--unknown", "--also-unknown", "--blong", NULL)); + ok1(test_cb_called == 1); + + /* parse_args allocates argv */ + free(argv); + return exit_status(); +} diff --git a/ccan/opt/test/utils.c b/ccan/opt/test/utils.c index a9bedf2e..2ff04884 100644 --- a/ccan/opt/test/utils.c +++ b/ccan/opt/test/utils.c @@ -103,6 +103,29 @@ bool parse_early_args(int *argc, char ***argv, ...) return opt_early_parse(*argc, *argv, save_err_output); } +bool parse_early_args_incomplete(int *argc, char ***argv, ...) +{ + char **a; + va_list ap; + + va_start(ap, argv); + *argc = 1; + a = malloc(sizeof(*a) * (*argc + 1)); + a[0] = (*argv)[0]; + while ((a[*argc] = va_arg(ap, char *)) != NULL) { + (*argc)++; + a = realloc(a, sizeof(*a) * (*argc + 1)); + } + + if (allocated) + free(*argv); + + *argv = a; + allocated = true; + + return opt_early_parse_incomplete(*argc, *argv, save_err_output); +} + struct opt_table short_table[] = { /* Short opts, different args. */ OPT_WITHOUT_ARG("-a", test_noarg, "a", "Description of a"), diff --git a/ccan/opt/test/utils.h b/ccan/opt/test/utils.h index 1c3658d7..12cf0b75 100644 --- a/ccan/opt/test/utils.h +++ b/ccan/opt/test/utils.h @@ -5,6 +5,7 @@ bool parse_args(int *argc, char ***argv, ...); bool parse_early_args(int *argc, char ***argv, ...); +bool parse_early_args_incomplete(int *argc, char ***argv, ...); extern char *err_output; void save_err_output(const char *fmt, ...); void reset_options(void); -- 2.39.2