/* * Copyright (C) 2016 Andre Noll * * Licensed under the LGPL v3, see https://www.gnu.org/licenses/lgpl-3.0.html */ #include #include #include #include #include #include #include #include #include #include "lopsub-internal.h" #include "lopsub.h" /* For detecting version mismatches, see lopsub-internal.h. */ const unsigned LLS_ABI_VERSION_VAR = 0; #define FOR_EACH_OPTION(_i, _opts) \ for (_i = 0; (_opts) && (_opts)[(_i)].name; (_i)++) #define FOR_EACH_OPTION_IN_COMMAND(_opt, _cmd) \ for ( \ (_opt) = (_cmd)->options; \ (_opt) && (_opt) < (_cmd)->options + (_cmd)->num_options; \ (opt)++ \ ) /* The result of parsing one option and its arguments. */ struct lls_arg { int idx; /* index into either argv[] or the lls_option array. */ const char *arg; /* NULL if option has no argument. */ }; /* * This structure, and the exchange_args(), decode_option() and parse_option() * functions below are inspired by the glibc implementation of getopt.c, * Copyright (C) 1987-2015 Free Software Foundation, Inc. */ struct lls_data { const struct lls_option *opts; int argc; char *const *argv; int optind; /* index into argv[] which we are parsing. */ const char *next_char; /* * These describe the part of argv[] that contains non-options that * have been skipped. first_nonopt is the index in argv[] of the first * of them, last_nonopt is the index after the last of them. Initially * both indices are zero. */ int first_nonopt; int last_nonopt; }; const struct lls_command *lls_cmd(unsigned cmd_num, const struct lls_suite *suite) { if (cmd_num > suite->num_subcommands) return NULL; return suite->commands + cmd_num; } const char *lls_command_name(const struct lls_command *cmd) { return cmd->name; } const void *lls_user_data(const struct lls_command *cmd) { return cmd->user_data; } const struct lls_option *lls_opt(unsigned opt_num, const struct lls_command *cmd) { return cmd->options + opt_num; } const struct lls_opt_result *lls_opt_result(unsigned opt_num, const struct lls_parse_result *lpr) { return lpr->opt_result + opt_num; } unsigned lls_opt_given(const struct lls_opt_result *r) { return r->given; } const char *lls_enum_string_val(unsigned idx, const struct lls_option *opt) { return opt->values[idx].string_val; } const char *lls_string_val(unsigned idx, const struct lls_opt_result *r) { return r->value[idx].string_val; } int32_t lls_int32_val(unsigned idx, const struct lls_opt_result *r) { return r->value[idx].int32_val; } uint32_t lls_uint32_val(unsigned idx, const struct lls_opt_result *r) { return r->value[idx].uint32_val; } int64_t lls_int64_val(unsigned idx, const struct lls_opt_result *r) { return r->value[idx].int64_val; } uint64_t lls_uint64_val(unsigned idx, const struct lls_opt_result *r) { return r->value[idx].uint64_val; } unsigned lls_num_inputs(const struct lls_parse_result *lpr) { return lpr->num_inputs; } const char *lls_purpose(const struct lls_command *cmd) { return cmd->purpose; } const char *lls_input(unsigned input_num, const struct lls_parse_result *lpr) { return lpr->inputs[input_num]; } const char *lls_strerror(int lss_errno) { #define LLS_ERROR(_n, _s) _s, static const char * const error_string[] = {LLS_ERRORS NULL}; #undef LLS_ERROR return error_string[lss_errno]; } static int xrealloc(void *p, size_t size) { void **pp = p, *newp = realloc(*pp, size); if (!newp) return -E_LLS_NOMEM; *pp = newp; return 0; } /* Print a formatted message to a dynamically allocated string. */ __attribute__ ((format (printf, 2, 0))) static int xvasprintf(char **result, const char *fmt, va_list ap) { int ret; size_t size = 150; va_list aq; if (!result) return 0; if (*result) free(*result); *result = malloc(size + 1); if (!*result) return -E_LLS_NOMEM; va_copy(aq, ap); ret = vsnprintf(*result, size, fmt, aq); va_end(aq); assert(ret >= 0); if (ret < size) /* OK */ return ret; size = ret + 1; ret = xrealloc(result, size); if (ret < 0) { free(*result); *result = NULL; return ret; } va_copy(aq, ap); ret = vsnprintf(*result, size, fmt, aq); va_end(aq); assert(ret >= 0 && ret < size); return ret; } /* Print to a dynamically allocated string, variable number of arguments. */ __attribute__ ((format (printf, 2, 3))) static int xasprintf(char **result, const char *fmt, ...) { va_list ap; unsigned ret; va_start(ap, fmt); ret = xvasprintf(result, fmt, ap); va_end(ap); return ret; } static inline unsigned num_vals_in_parse_result(const struct lls_command *cmd, int opt_num, const struct lls_parse_result *lpr) { const struct lls_option *opt = cmd->options + opt_num; struct lls_opt_result *lor = lpr->opt_result + opt_num; if (opt->arg_info == LLS_NO_ARGUMENT) return 0; if (lor->given == 0) return 1; /* for the default value */ if (!(opt->flags & LLS_MULTIPLE)) return 1; return lor->given; } union atoi_result { int32_t int32; uint32_t uint32; int64_t int64; uint64_t uint64; }; enum atoi_mode {ATOI_INT32, ATOI_UINT32, ATOI_INT64, ATOI_UINT64}; /* * Convert a string to a 32 or 64 bit signed or unsigned integer value. * * For conversions to unsigned integers, negative values are considered valid * input and are silently converted. */ static int lls_atoi(const char *str, enum atoi_mode mode, union atoi_result *value) { char *endptr; union atoi_result result; memset(value, 0, sizeof(*value)); errno = 0; /* To distinguish success/failure after call */ /* * We pass zero as the base to strtoll(3) and strtoull(3) to let the * function recognize an optional base prefix like "0x". */ if (mode == ATOI_UINT64) { unsigned long long tmp = strtoull(str, &endptr, 0); if (errno == ERANGE && tmp == ULLONG_MAX) return -E_LLS_OVERFLOW; result.uint64 = tmp; } else { /* parse as signed 64 bit and check range */ long long tmp = strtoll(str, &endptr, 0); if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN)) return -E_LLS_OVERFLOW; switch (mode) { case ATOI_INT64: /* no additional range check necessary */ result.int64 = tmp; break; case ATOI_INT32: if (tmp < INT_MIN || tmp > INT_MAX) return -E_LLS_OVERFLOW; result.int32 = tmp; break; case ATOI_UINT32: if (tmp > UINT_MAX) return -E_LLS_OVERFLOW; result.uint32 = tmp; break; default: assert(0); } } /* * If there were no digits at all, strtol() and friends store the * original value of str in *endptr. */ if (endptr == str) return -E_LLS_NO_DIGITS; /* * The implementation may also set errno (and return 0) in case no * conversion was performed. */ if (errno != 0) return -E_LLS_NO_DIGITS; if (*endptr != '\0') /* Further characters after number */ return -E_LLS_TRAILING_GARBAGE; *value = result; return 1; } static int atoi32(const char *str, int32_t *result) { union atoi_result ar; int ret = lls_atoi(str, ATOI_INT32, &ar); *result = ar.int32; return ret; } static int atou32(const char *str, uint32_t *result) { union atoi_result ar; int ret = lls_atoi(str, ATOI_UINT32, &ar); *result = ar.uint32; return ret; } static int atoi64(const char *str, int64_t *result) { union atoi_result ar; int ret = lls_atoi(str, ATOI_INT64, &ar); *result = ar.int64; return ret; } static int atou64(const char *str, uint64_t *result) { union atoi_result ar; int ret = lls_atoi(str, ATOI_UINT64, &ar); *result = ar.uint64; return ret; } static void free_opt_result(int opt_num, struct lls_parse_result *lpr, const struct lls_command *cmd) { const struct lls_option *opt = cmd->options + opt_num; if (opt->arg_type == LLS_STRING && !opt->values) { unsigned num_vals = num_vals_in_parse_result(cmd, opt_num, lpr); int j; for (j = 0; j < num_vals; j++) if (lpr->opt_result[opt_num].value) free(lpr->opt_result[opt_num].value[j].string_val); } if (opt->arg_info != LLS_NO_ARGUMENT) free(lpr->opt_result[opt_num].value); } void lls_free_parse_result(struct lls_parse_result *lpr, const struct lls_command *cmd) { int i; if (!lpr) return; if (lpr->inputs) for (i = 0; i < lpr->num_inputs; i++) free(lpr->inputs[i]); free(lpr->inputs); if (lpr->opt_result) FOR_EACH_OPTION(i, cmd->options) free_opt_result(i, lpr, cmd); free(lpr->opt_result); free(lpr); } static struct lls_data *init_lls_data(const struct lls_option *opts, int argc, char *const *argv) { struct lls_data *d = malloc(sizeof(*d)); if (!d) return NULL; d->optind = 0; /* start with an empty non-option list */ d->first_nonopt = d->last_nonopt = d->optind; d->opts = opts; d->argc = argc; d->argv = argv; d->next_char = NULL; return d; } /* * Exchange two adjacent subsets of argv[]. * * One subset is given by indices {first_nonopt, ..., last_nonopt - 1}. It * contains all the non-options that have been skipped so far. The other subset * corresponds to indices {last_nonopt, ... optind - 1} which contains all the * options processed since those non-options were skipped. * * Before the function returns, ->first_nonopt and ->last_nonopt are updated to * describe the new set of non-options in argv[]. */ static void exchange_args(struct lls_data *d) { int bottom = d->first_nonopt; int middle = d->last_nonopt; int top = d->optind; char **argv = (char **)d->argv; /* * Exchange the shorter segment with the far end of the longer segment. * That puts the shorter segment into the right place. It leaves the * longer segment in the right place overall, but it consists of two * parts that need to be swapped next. */ while (top > middle && middle > bottom) { if (top - middle > middle - bottom) { /* Bottom segment is the short one. */ int i, len = middle - bottom; /* Swap it with the top part of the top segment. */ for (i = 0; i < len; i++) { char *tmp = argv[bottom + i]; argv[bottom + i] = argv[top - (middle - bottom) + i]; argv[top - (middle - bottom) + i] = tmp; } /* Exclude the moved bottom segment from further swapping. */ top -= len; } else { /* Top segment is the short one. */ int i, len = top - middle; /* Swap it with the bottom part of the bottom segment. */ for (i = 0; i < len; i++) { char *tmp = argv[bottom + i]; argv[bottom + i] = argv[middle + i]; argv[middle + i] = tmp; } /* Exclude the moved top segment from further swapping. */ bottom += len; } } /* Update records for the slots the non-options now occupy. */ d->first_nonopt += d->optind - d->last_nonopt; d->last_nonopt = d->optind; } /* whether arg points to an option argument */ static inline bool is_option(const char *arg) { return arg[0] == '-' && arg[1] != '\0'; } static void check_errctx(char **errctx, int ret) { if (!errctx) return; if (ret >= 0) assert(!*errctx); /* memory leak/uninitialized pointer */ else if (ret != -E_LLS_NOMEM) assert(*errctx); /* we must provide an error message */ } /* * Decode the current option. On success, set result->idx to the index in the * ->options array which was decoded successfully. On failure, result->idx is * the index in argv[] which could not be parsed. */ static int decode_option(struct lls_data *d, struct lls_arg *result, char **errctx) { const char *word = d->argv[d->optind], *cur, *end; size_t len; int i; const struct lls_option *match = NULL; bool ambig = false, shortopt; assert(word[0] != '\0'); shortopt = word[1] != '-'; result->idx = d->optind; result->arg = word; if (d->next_char) cur = d->next_char; else cur = word + 1 + !shortopt; /* skip dash(es) */ for (end = cur; *end && *end != '='; end++) ; /* nothing */ len = end - cur; /* test all options for exact or abbreviated matches */ FOR_EACH_OPTION(i, d->opts) { const struct lls_option *opt = d->opts + i; if (opt->flags & LLS_IGNORED) continue; if (shortopt) { if (*cur != opt->short_opt) continue; match = opt; d->next_char = cur + 1; if (d->next_char[0] == '\0' || d->next_char[0] == '=') d->next_char = NULL; break; } if (strncmp(opt->name, cur, len) != 0) continue; if (strlen(opt->name) == len) { /* exact match */ match = opt; break; } if (match) { /* second non-exact match */ ambig = true; break; } /* first non-exact match */ match = opt; } if (!match) { /* option not found */ xasprintf(errctx, "error token: %s", cur); return -E_LLS_BAD_OPTION; } if (ambig) { xasprintf(errctx, "%s", word); return -E_LLS_AMBIG_OPTION; } if (d->next_char) { if (match->arg_info == LLS_REQUIRED_ARGUMENT) { xasprintf(errctx, "--%s", match->name); return -E_LLS_NO_ARG_GIVEN; } result->arg = NULL; goto success; } d->optind++; if (*end == '=') { if (match->arg_info == LLS_NO_ARGUMENT) { xasprintf(errctx, "--%s", match->name); return -E_LLS_ARG_GIVEN; } result->arg = end + 1; } else if (match->arg_info == LLS_REQUIRED_ARGUMENT) { if (d->optind >= d->argc) { xasprintf(errctx, "--%s", match->name); return -E_LLS_NO_ARG_GIVEN; } result->arg = d->argv[d->optind++]; } else result->arg = NULL; success: result->idx = match - d->opts; return 1; } /* * Parse one option, including its argument (if any). * * We permute the contents of ARGV as we scan, so that eventually all the * non-options are at the end. This allows options to be given in any order. * * Returns zero on end-of-argv, negative on errors, one if an option was parsed * successfully. The structure pointed to by result is initialized as follows: * * end-of-args case: ->idx is the index of first non-option in argv[], ->arg is * argv[result->idx]. * * error case: ->idx is the index of the first problematic option in argv. ->arg is * argv[result->idx] as in the end-of-args case. * * success case: ->idx is the index into the option array which corresponds to * the option that was parsed successfully, ->arg its argument, or NULL if no * argument was given. * * After this function returned non-positive, it must not be called again. */ static int parse_option(struct lls_data *d, struct lls_arg *result, char **errctx) { assert(d->last_nonopt <= d->optind); assert(d->first_nonopt <= d->optind); if (d->next_char) return decode_option(d, result, errctx); /* * If we have just processed some options following some non-options, * exchange them so that the options come first. */ if (d->first_nonopt != d->last_nonopt && d->last_nonopt != d->optind) exchange_args(d); else if (d->last_nonopt != d->optind) d->first_nonopt = d->optind; /* * Skip any additional non-options and extend the range of non-options * previously skipped. */ while (d->optind < d->argc && !is_option(d->argv[d->optind])) d->optind++; d->last_nonopt = d->optind; /* * The special argument `--' forces an end of option-scanning. We skip * it like a null option, then exchange it with previous non-options as * if it were an option. Then we skip everything else like a non-option. */ if (d->optind != d->argc && !strcmp(d->argv[d->optind], "--")) { d->optind++; if (d->first_nonopt != d->last_nonopt && d->last_nonopt != d->optind) exchange_args(d); else if (d->first_nonopt == d->last_nonopt) d->first_nonopt = d->optind; d->last_nonopt = d->argc; d->optind = d->argc; } /* * If we have done all the argv elements, stop the scan and back over * any non-options that we skipped and permuted. */ if (d->optind == d->argc) { /* * Set the index to point at the non-options that we * previously skipped. */ result->idx = d->first_nonopt; result->arg = d->argv[result->idx]; return 0; } assert(is_option(d->argv[d->optind])); return decode_option(d, result, errctx); } static int check_enum_arg(const char *arg, const struct lls_option *opt, char **errctx) { int i; char *val; for (i = 0; (val = opt->values[i].string_val); i++) if (!strcmp(arg, val)) return i; xasprintf(errctx, "arg: %s, option: %s", arg, opt->name); return -E_LLS_ENUM; } /* * Increase the "given" count and store argument if the option takes one. * Allocates or reallocates the ->value array of struct lls_opt_result in lpr. */ static int lls_parse_arg(struct lls_arg *la, const struct lls_option *opts, struct lls_parse_result *lpr, char **errctx) { const struct lls_option *opt = opts + la->idx; struct lls_opt_result *lor = lpr->opt_result + la->idx; bool multiple; int idx, ret; if (la->arg && opt->arg_info == LLS_NO_ARGUMENT) { xasprintf(errctx, "arg: %s, option: %s", la->arg, opt->name); return -E_LLS_ARG_GIVEN; } multiple = opt->flags & LLS_MULTIPLE; if (opt->arg_type == LLS_STRING && !opt->values && !multiple) { if (lor->value) { /* discard previous value */ free(lor->value[0].string_val); free(lor->value); lor->value = NULL; } } if (!la->arg) goto success; idx = multiple? lor->given : 0; if (!lor->value || multiple) { ret = xrealloc(&lor->value, (idx + 1) * sizeof(*lor->value)); if (ret < 0) { xasprintf(errctx, "option value array for --%s", opt->name); return ret; } } switch (opt->arg_type) { case LLS_STRING: if (opt->values) { ret = check_enum_arg(la->arg, opt, errctx); if (ret < 0) return ret; lor->value[idx].uint32_val = ret; } else { lor->value[idx].string_val = strdup(la->arg); if (!lor->value[idx].string_val) { xasprintf(errctx, "string value for %s", opt->name); return -E_LLS_NOMEM; } } break; case LLS_INT32: ret = atoi32(la->arg, &lor->value[idx].int32_val); if (ret < 0) goto atoi_error; break; case LLS_UINT32: ret = atou32(la->arg, &lor->value[idx].uint32_val); if (ret < 0) goto atoi_error; break; case LLS_INT64: ret = atoi64(la->arg, &lor->value[idx].int64_val); if (ret < 0) goto atoi_error; break; case LLS_UINT64: ret = atou64(la->arg, &lor->value[idx].uint64_val); if (ret < 0) goto atoi_error; break; default: assert(false); } success: lor->given++; return 1; atoi_error: assert(ret < 0); xasprintf(errctx, "conversion error for argument \"%s\" to option --%s", la->arg, opt->name); return ret; } static int copy_val(union lls_val *dst, const union lls_val *src, const struct lls_option *opt, char **errctx) { if (opt->arg_type != LLS_STRING || opt->values) { *dst = *src; return 0; } if (!src->string_val) { dst->string_val = NULL; return 0; } dst->string_val = strdup(src->string_val); if (!dst->string_val) { xasprintf(errctx, "copy value for --%s", opt->name); return -E_LLS_NOMEM; } return 1; } int lls_check_arg_count(const struct lls_parse_result *lpr, int min_argc, int max_argc, char **errctx) { if (errctx) *errctx = NULL; assert(min_argc <= max_argc); if (lpr->num_inputs < min_argc) { xasprintf(errctx, "%s %u non-option args required, %u given", min_argc < max_argc? "at least" : "exactly", min_argc, lpr->num_inputs); return -E_LLS_BAD_ARG_COUNT; } if (lpr->num_inputs > max_argc) { if (max_argc == 0) xasprintf(errctx, "no non-option args allowed, " "%u given", lpr->num_inputs); else xasprintf(errctx, "%s %u non-option args %s, %u given", min_argc < max_argc? "at most" : "exactly", max_argc, min_argc < max_argc? "allowed" : "required", lpr->num_inputs); return -E_LLS_BAD_ARG_COUNT; } return 1; } /* * Unlike getopt(3) this implementation can not resume the scan where it left * off. */ int lls_parse(int argc, char **argv, const struct lls_command *cmd, struct lls_parse_result **lprp, char **errctx) { const struct lls_option *opts = cmd->options; struct lls_data *d = NULL; int i, ret; struct lls_arg la; struct lls_parse_result *lpr; if (errctx) *errctx = NULL; lpr = calloc(1, sizeof(*lpr)); if (!lpr) { xasprintf(errctx, "log parse result"); ret = -E_LLS_NOMEM; goto out; } d = init_lls_data(opts, argc, argv); if (!d) { xasprintf(errctx, "init_lls_data()"); ret = -E_LLS_NOMEM; goto out; } if (cmd->num_options == 0) { la.idx = 0; lpr->opt_result = NULL; } else { lpr->opt_result = calloc(cmd->num_options, sizeof(*lpr->opt_result)); if (!lpr->opt_result) { xasprintf(errctx, "option result array for %s", cmd->name); ret = -E_LLS_NOMEM; goto out; } for (;;) { ret = parse_option(d, &la, errctx); if (ret < 0) goto out; if (ret == 0) break; ret = lls_parse_arg(&la, opts, lpr, errctx); if (ret < 0) goto out; } } lpr->num_inputs = argc - la.idx - 1; if (!cmd->non_opts_name) { ret = lls_check_arg_count(lpr, 0, 0, errctx); if (ret < 0) { /* needed for lls_free_parse_result() */ lpr->inputs = NULL; goto out; } } /* We always make a copy of the elements of argv[] */ lpr->inputs = malloc((lpr->num_inputs + 1) * sizeof(char *)); if (!lpr->inputs) { xasprintf(errctx, "inputs array for %s", cmd->name); ret = -E_LLS_NOMEM; goto out; } for (i = 0; i < lpr->num_inputs; i++) { char *arg = argv[i + la.idx + 1]; lpr->inputs[i] = strdup(arg); if (lpr->inputs[i]) continue; xasprintf(errctx, "option #%d (%s) of %s", i, arg, cmd->name); ret = -E_LLS_NOMEM; goto out; } lpr->inputs[lpr->num_inputs] = NULL; /* initialize default values */ FOR_EACH_OPTION(i, opts) { const struct lls_option *opt = opts + i; struct lls_opt_result *lor = lpr->opt_result + i; bool required = opt->flags & LLS_REQUIRED; bool has_arg = opt->arg_info != LLS_NO_ARGUMENT; if (lor->given == 0 && required) { xasprintf(errctx, "--%s", opt->name); ret = -E_LLS_OPT_MANDATORY; goto out; } if (lor->value) continue; if (!has_arg) continue; /* * allocate space for the default value, even if there is no * default given in the .suite file */ lor->value = malloc(sizeof(*lor->value)); if (!lor->value) { xasprintf(errctx, "value array for --%s", opt->name); ret = -E_LLS_NOMEM; goto out; } ret = copy_val(lor->value, &opt->default_val, opt, errctx); if (ret < 0) goto out; } ret = 1; out: free(d); check_errctx(errctx, ret); if (ret < 0) { lls_free_parse_result(lpr, cmd); *lprp = NULL; } else *lprp = lpr; return ret; } #define MAX_OPTION_LEN 30 #define HELP_INDENT 6 static const char space[MAX_OPTION_LEN + 1] = " "; static int short_option_help(const struct lls_option *opt, char **result) { int ret = 0; char *opt_names = NULL; bool overlong, has_short = opt->short_opt; const char *typestr; *result = NULL; if (opt->flags & LLS_IGNORED) return xasprintf(result, "%s", opt->summary); if (opt->arg_info == LLS_NO_ARGUMENT) typestr = ""; else typestr = opt->typestr? opt->typestr : "val"; ret = xasprintf(&opt_names, "%s%c%s" " --%s" "%s%s%s%s%s" , has_short? " -" : " ", has_short? opt->short_opt : ' ', has_short? "," : " ", opt->name, opt->arg_info == LLS_OPTIONAL_ARGUMENT? "[" : "", opt->arg_info == LLS_NO_ARGUMENT? "" : "=<", typestr, opt->arg_info == LLS_NO_ARGUMENT? "" : ">", opt->arg_info == LLS_OPTIONAL_ARGUMENT? "]" : "" ); if (ret < 0) return ret; overlong = ret >= MAX_OPTION_LEN; ret = xasprintf(result, "%s" "%s" "%s" "%s" , opt_names, overlong? "\n" : "", overlong? space : space + ret, opt->summary? opt->summary : "" ); free(opt_names); return ret; } static int format_default_val(const struct lls_option *opt, char **result) { const union lls_val *val = &opt->default_val; *result = NULL; if (opt->arg_info == LLS_NO_ARGUMENT) return 0; if (!(opt->flags & LLS_HAS_DEFAULT)) return 0; switch (opt->arg_type) { case LLS_STRING: if (opt->values) return 0; return xasprintf(result, "(string, default: %s)", val->string_val? val->string_val : "[NULL]"); case LLS_INT32: return xasprintf(result, "(int32, default: %" PRId32 ")", val->int32_val); case LLS_UINT32: return xasprintf(result, "(uint32, default: %" PRIu32 ")", val->uint32_val); case LLS_INT64: return xasprintf(result, "(int64, default: %" PRId64 ")", val->int64_val); case LLS_UINT64: return xasprintf(result, "(uint64, default: %" PRIu64 ")", val->uint64_val); default: assert(0); } return 1; } static int format_values(const struct lls_option *opt, char **result) { int i; uint32_t dflt_idx; const char *val, *pfx = "values: "; size_t len, line_len; const int indent_len = 6, max_len = 75, pfx_len = 8; char *p; *result = NULL; if (!opt->values) return 0; assert(opt->arg_type == LLS_STRING); dflt_idx = opt->default_val.uint32_val; line_len = indent_len + pfx_len; len = line_len; for (i = 0; (val = opt->values[i].string_val); i++) { size_t val_len = strlen(val); /* comma and space, and [] around default val */ int extra_len = 2 * (i != 0) + 2 * (i == dflt_idx); bool cr = line_len + val_len + extra_len > max_len; if (cr) { line_len = indent_len + pfx_len; len += 1 + indent_len + pfx_len; /* +1 for \n */ } len += val_len + extra_len; line_len += val_len + extra_len; } *result = malloc(len + 1); /* +1 for terminating zero byte */ if (!*result) return -E_LLS_NOMEM; p = *result + sprintf(*result, "%.*s%s", indent_len, space, pfx); line_len = p - *result; for (i = 0; (val = opt->values[i].string_val); i++) { size_t val_len = strlen(val); int extra_len = 2 * (i != 0) + 2 * (i == dflt_idx); bool cr = line_len + val_len + extra_len > max_len; p += sprintf(p, "%s" "%s" "%.*s" "%s%s%s", i == 0? "" : ", ", cr? "\n" : "", cr? pfx_len + indent_len : 0, cr? space : "", i == dflt_idx? "[" : "", val, i == dflt_idx? "]" : "" ); if (cr) line_len = indent_len + pfx_len; line_len += val_len + extra_len; } return 1; } static char *create_help_buf(const struct lls_command *cmd, bool long_help) { char *header, *option_help, *result; const struct lls_option *opt; int ret; const char *desc = (long_help && cmd->description)? cmd->description : ""; const char *closing = (long_help && cmd->closing)? cmd->closing : NULL; result = NULL; header = NULL; ret = xasprintf(&header, "%s - %s\n\n" "Usage: %s %s\n" "%s%s" , cmd->name, cmd->purpose, cmd->name, cmd->synopsis, desc, cmd->options? "\n" : "" ); if (ret < 0) return NULL; if (!cmd->options) return header; option_help = NULL; FOR_EACH_OPTION_IN_COMMAND(opt, cmd) { char *tmp, *soh, *loh = NULL, *dflt, *values; int indent = (opt->flags & LLS_IGNORED)? 0 : HELP_INDENT; ret = short_option_help(opt, &soh); if (ret < 0) goto out; if (long_help && opt->help) { const char *p, *q; for (p = opt->help; (q = strchr(p, '\n')); p = q + 1) { tmp = NULL; ret = xasprintf(&tmp, "%s%.*s%.*s", loh? loh : "\n", indent, space, (int)(q - p + 1), p); free(loh); if (ret < 0) { free(soh); goto out; } loh = tmp; } } ret = format_default_val(opt, &dflt); if (ret < 0) { free(soh); free(loh); goto out; } if (long_help) { ret = format_values(opt, &values); if (ret < 0) { free(dflt); free(soh); free(loh); goto out; } } else values = NULL; tmp = NULL; ret = xasprintf(&tmp, "%s" "%s" "%s%s%s" "%s%s" "%s\n", option_help? option_help : "", soh ? soh : "", dflt? "\n" : "", dflt? space : "", dflt? dflt : "", values? "\n" : "", values? values : "", loh? loh : "" ); free(values); free(dflt); free(soh); free(loh); if (ret < 0) goto out; free(option_help); option_help = tmp; } ret = xasprintf(&result, "%s%s%s%s", header, option_help, closing? "\n" : "", closing? closing : ""); out: free(header); free(option_help); return ret < 0? NULL : result; } char *lls_long_help(const struct lls_command *cmd) { return create_help_buf(cmd, true /* include help */); } char *lls_short_help(const struct lls_command *cmd) { return create_help_buf(cmd, false /* only options */); } static int partial_match(const char *arg, const char *name) { size_t arglen = strlen(arg); if (strncmp(arg, name, arglen) != 0) return 1; /* no match */ if (name[arglen] == '\0') return 0; /* exact match */ return -1; /* partial match */ } int lls_lookup_subcmd(const char *string, const struct lls_suite *suite, char **errctx) { int i, ret; if (errctx) *errctx = NULL; if (!string) { xasprintf(errctx, "nothing to look up"); return -E_LLS_BAD_SUBCMD; } ret = 0; /* no match so far */ for (i = 1; i <= suite->num_subcommands; i++) { switch (partial_match(string, suite->commands[i].name)) { case 1: /* no match */ continue; case 0: /* exact match */ return i; case -1: /* partial match */ if (ret > 0) { ret = -E_LLS_AMBIG_SUBCMD; goto fail; } ret = i; } } if (ret > 0) /* unique partial match */ return ret; ret = -E_LLS_BAD_SUBCMD; fail: xasprintf(errctx, "%s", string); return ret; } static size_t get_opt_result_pointer(const struct lls_option *opt, int val_num, struct lls_opt_result *lor, void **result) { union lls_val *val = lor->value + val_num; switch (opt->arg_type) { case LLS_INT32: *result = &val->int32_val; return 4; case LLS_UINT32: *result = &val->uint32_val; return 4; case LLS_INT64: *result = &val->int64_val; return 8; case LLS_UINT64: *result = &val->uint64_val; return 8; default: assert(0); } } /* never fails, returns number of bytes needed/written */ static size_t serialize_parse_result(const struct lls_parse_result *lpr, const struct lls_command *cmd, char *result) { int i, j; size_t nbytes; /* num_inputs */ if (result) memcpy(result, &lpr->num_inputs, 4); nbytes = 4; /* inputs */ for (i = 0; i < lpr->num_inputs; i++) { if (result) strcpy(result + nbytes, lpr->inputs[i]); nbytes += strlen(lpr->inputs[i]) + 1; } /* options */ FOR_EACH_OPTION(i, cmd->options) { const struct lls_option *opt = cmd->options + i; struct lls_opt_result *lor = lpr->opt_result + i; unsigned num_vals; if (result) memcpy(result + nbytes, &lor->given, 4); nbytes += 4; if (opt->arg_info == LLS_NO_ARGUMENT) continue; num_vals = num_vals_in_parse_result(cmd, i, lpr); if (opt->arg_type == LLS_STRING && !opt->values) { for (j = 0; j < num_vals; j++) { if (result) strcpy(result + nbytes, lor->value[j].string_val); nbytes += strlen(lor->value[j].string_val) + 1; } } else { for (j = 0; j < num_vals; j++) { size_t bytes; void *p; bytes = get_opt_result_pointer(opt, j, lor, &p); if (result) memcpy(result + nbytes, p, bytes); nbytes += bytes; } } } return nbytes; } int lls_serialize_parse_result(const struct lls_parse_result *lpr, const struct lls_command *cmd, char **result, size_t *nbytes) { size_t sz; int ret; if (!result || !*result) { /* need to compute needed space */ sz = serialize_parse_result(lpr, cmd, NULL); if (!result) { /* just report needed space */ ret = 0; goto out; } *result = malloc(sz); if (!*result) { sz = 0; ret = -E_LLS_NOMEM; goto out; } } /* serialize it */ sz = serialize_parse_result(lpr, cmd, *result); ret = 1; out: if (nbytes) *nbytes = sz; return ret; } int lls_deserialize_parse_result(const char *buf, const struct lls_command *cmd, struct lls_parse_result **lprp) { int i, j; const char *p = buf; struct lls_parse_result *lpr; *lprp = NULL; lpr = malloc(sizeof(*lpr)); if (!lpr) return -E_LLS_NOMEM; memcpy(&lpr->num_inputs, p, 4); p += 4; if (lpr->num_inputs > 0) { lpr->inputs = malloc(lpr->num_inputs * sizeof(char *)); if (!lpr->inputs) goto free_lpr; } else lpr->inputs = NULL; for (i = 0; i < lpr->num_inputs; i++) { lpr->inputs[i] = strdup(p); if (!lpr->inputs[i]) goto free_inputs; p += strlen(p) + 1; } lpr->opt_result = malloc(cmd->num_options * sizeof(*lpr->opt_result)); if (!lpr->opt_result) goto free_inputs; FOR_EACH_OPTION(i, cmd->options) { const struct lls_option *opt = cmd->options + i; struct lls_opt_result *lor = lpr->opt_result + i; uint32_t num_vals; memcpy(&lor->given, p, 4); p += 4; if (opt->arg_info == LLS_NO_ARGUMENT) continue; num_vals = num_vals_in_parse_result(cmd, i, lpr); lor->value = malloc(num_vals * sizeof(*lor->value)); if (!lor->value) goto free_options; if (opt->arg_type == LLS_STRING && !opt->values) { for (j = 0; j < num_vals; j++) { lor->value[j].string_val = strdup(p); if (!lor->value[j].string_val) { for (; j >= 0; j--) free(lor->value[j].string_val); free(lor->value); goto free_options; } p += strlen(lor->value[j].string_val) + 1; } } else { for (j = 0; j < num_vals; j++) { size_t bytes; void *q; bytes = get_opt_result_pointer(opt, j, lor, &q); memcpy(q, p, bytes); p += bytes; } } } *lprp = lpr; return 1; free_options: for (i--; i >= 0; i--) { const struct lls_option *opt = cmd->options + i; struct lls_opt_result *lor = lpr->opt_result + i; unsigned num_vals = (opt->flags & LLS_MULTIPLE)? lor->given : 1; for (j = 0; j < num_vals; j++) if (opt->arg_type == LLS_STRING && !opt->values) free(lor->value[j].string_val); free(lor->value); } free(lpr->opt_result); free_inputs: for (; i >= 0; i--) free(lpr->inputs[i]); free(lpr->inputs); free_lpr: free(lpr); return -E_LLS_NOMEM; } static int merge_option(int opt_num, const struct lls_parse_result *primary, const struct lls_parse_result *secondary, const struct lls_command *cmd, struct lls_parse_result *result, char **errctx) { int l, m, ret; const struct lls_option *opt = cmd->options + opt_num; struct lls_opt_result *lor1, *lor2, *lor; lor1 = primary->opt_result + opt_num; lor2 = secondary->opt_result + opt_num; lor = result->opt_result + opt_num; lor->given = lor1->given + lor2->given; if (opt->arg_info == LLS_NO_ARGUMENT) return 0; if (lor->given > 0 && (opt->flags & LLS_MULTIPLE)) { lor->value = malloc(lor->given * sizeof(*lor->value)); if (!lor->value) { xasprintf(errctx, "value array for option %s", opt->name); goto fail; } for (l = 0; l < lor1->given; l++) { ret = copy_val(lor->value + l, lor1->value + l, opt, errctx); if (ret < 0) goto free_primary_options; } for (m = 0; m < lor2->given; m++) { ret = copy_val(lor->value + l + m, lor2->value + m, opt, errctx); if (ret < 0) goto free_secondary_options; } return 1; } lor->value = malloc(sizeof(*lor->value)); /* one value only */ if (!lor->value) { xasprintf(errctx, "(single) value for option %s", opt->name); goto fail; } if (lor1->given) { ret = copy_val(lor->value, lor1->value, opt, errctx); if (ret < 0) goto free_value; } else if (lor2->given) { ret = copy_val(lor->value, lor2->value, opt, errctx); if (ret < 0) goto free_value; } else { ret = copy_val(lor->value, &opt->default_val, opt, errctx); if (ret < 0) goto free_value; } return 1; free_secondary_options: if (opt->arg_type == LLS_STRING && !opt->values) for (m--; m >= 0; m--) free(lor->value[l + m].string_val); free_primary_options: if (opt->arg_type == LLS_STRING && !opt->values) for (l--; l >= 0; l--) free(lor->value[l].string_val); free_value: free(lor->value); fail: return -E_LLS_NOMEM; } int lls_merge(const struct lls_parse_result *primary, const struct lls_parse_result *secondary, const struct lls_command *cmd, struct lls_parse_result **lprp, char **errctx) { int i, j, k, ret; unsigned num = primary->num_inputs + secondary->num_inputs; struct lls_parse_result *result; if (errctx) *errctx = NULL; result = malloc(sizeof(*result)); if (!result) { ret = -E_LLS_NOMEM; xasprintf(errctx, "parse result"); goto fail; } result->inputs = malloc((num + 1) * sizeof(char *)); if (!result->inputs) { ret = -E_LLS_NOMEM; xasprintf(errctx, "inputs array of size %u", num); goto free_parse_result; } for (i = 0; i < primary->num_inputs; i++) { result->inputs[i] = strdup(primary->inputs[i]); if (!result->inputs[i]) { ret = -E_LLS_NOMEM; xasprintf(errctx, "primary input #%d", i); goto free_primary_inputs; } } for (j = 0; j < secondary->num_inputs; j++) { result->inputs[i + j] = strdup(secondary->inputs[j]); if (!result->inputs[i + j]) { ret = -E_LLS_NOMEM; xasprintf(errctx, "secondary input #%d", i); goto free_secondary_inputs; } } result->inputs[i + j] = NULL; result->opt_result = malloc(cmd->num_options * sizeof(*result->opt_result)); if (!result->opt_result) goto free_secondary_inputs; FOR_EACH_OPTION(k, cmd->options) { ret = merge_option(k, primary, secondary, cmd, result, errctx); if (ret < 0) goto free_opt_results; } result->num_inputs = num; *lprp = result; ret = 1; goto out; free_opt_results: for (k--; k >= 0; k--) free_opt_result(k, result, cmd); free_secondary_inputs: for (j--; j >= 0; j--) free(result->inputs[i + j]); free_primary_inputs: for (i--; i >= 0; i--) free(result->inputs[i]); free(result->inputs); free_parse_result: free(result); fail: assert(ret < 0); *lprp = NULL; out: check_errctx(errctx, ret); return ret; } static bool is_default_val(const union lls_val *val, const struct lls_option *opt) { bool has_default = opt->flags & LLS_HAS_DEFAULT; bool has_arg = opt->arg_info != LLS_NO_ARGUMENT; const union lls_val *dflt; if (!has_arg) return false; if (!has_default) return false; dflt = &opt->default_val; switch (opt->arg_type) { case LLS_INT32: return val->int32_val == dflt->int32_val; case LLS_UINT32: return val->uint32_val == dflt->uint32_val; case LLS_INT64: return val->int64_val == dflt->int64_val; case LLS_UINT64: return val->uint64_val == dflt->uint64_val; case LLS_STRING: { const char *s1, *s2; if (opt->values) return val->uint32_val == dflt->uint32_val; s1 = val->string_val; s2 = dflt->string_val; if (!s1 && !s2) return true; if (!s1 || !s2) return false; return !strcmp(s1, s2); } default: assert(0); } } static char *append_opt_val(const union lls_val *val, const struct lls_option *opt, char *result) { char *line = NULL, *tmp = NULL; switch (opt->arg_type) { case LLS_INT32: xasprintf(&line, "%" PRId32, val->int32_val); break; case LLS_UINT32: xasprintf(&line, "%" PRIu32, val->uint32_val); break; case LLS_INT64: xasprintf(&line, "%" PRId64, val->int64_val); break; case LLS_UINT64: xasprintf(&line, "%" PRIu64, val->uint64_val); break; case LLS_STRING: { const char *s, *p; char *q; if (opt->values) s = lls_enum_string_val(val->uint32_val, opt); else { s = val->string_val; if (!s) return result; } line = malloc(2 * strlen(s) + 3); if (!line) { free(result); return NULL; } line[0] = '"'; for (p = s, q = line + 1; *p; p++, q++) { if (*p == '\\' || *p == '\n' || *p == '\t' || *p == '"') { *q = '\\'; q++; } *q = *p; } q[0] = '"'; q[1] = '\0'; break; } default: assert(0); } xasprintf(&tmp, "%s%s=%s\n", result? result : "", opt->name, line); free(line); free(result); return tmp; } char *lls_dump_parse_result(const struct lls_parse_result *lpr, const struct lls_command *cmd, bool non_default_only) { int i; char *result = NULL; FOR_EACH_OPTION(i, cmd->options) { const struct lls_option *opt = cmd->options + i; struct lls_opt_result *lor = lpr->opt_result + i; bool given = lor->given; int j, n; if (!given && non_default_only) continue; if (opt->arg_info == LLS_NO_ARGUMENT) { char *tmp = NULL; if (!given) continue; xasprintf(&tmp, "%s%s\n", result? result : "", opt->name); free(result); result = tmp; continue; } n = num_vals_in_parse_result(cmd, i, lpr); for (j = 0; j < n; j++) { union lls_val *val = lor->value + j; if (non_default_only && is_default_val(val, opt)) continue; result = append_opt_val(val, opt, result); } } if (!result) { /* empty dump */ result = malloc(1); if (result) result[0] = '\0'; } return result; }