+/*
+ * Copyright (C) 2016 Andre Noll <maan@tuebingen.mpg.de>
+ *
+ * Licensed under the LGPL v3, see http://www.gnu.org/licenses/lgpl-3.0.html
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <errno.h>
+#include <limits.h>
+
+#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 formated 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)
+ goto success;
+ if (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;
+ idx = multiple? lor->given : 0;
+ if (lor->given == 0 || multiple) {
+ ret = xrealloc(&lor->value,
+ (lor->given + 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 (lor->given > 0 && !multiple)
+ free(lor->value[idx].string_val);
+ 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;
+ if (lpr->num_inputs < min_argc) {
+ xasprintf(errctx, "at least %u non-option args required, "
+ "%u given", 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, "at most %u non-option args allowed, "
+ "%u given", max_argc, 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);
+ 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 >= 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;
+}