summaryrefslogtreecommitdiffstats
path: root/arguments.c
diff options
context:
space:
mode:
Diffstat (limited to 'arguments.c')
-rw-r--r--arguments.c906
1 files changed, 906 insertions, 0 deletions
diff --git a/arguments.c b/arguments.c
new file mode 100644
index 0000000..d0dc2d4
--- /dev/null
+++ b/arguments.c
@@ -0,0 +1,906 @@
+/* $OpenBSD$ */
+
+/*
+ * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "tmux.h"
+
+/*
+ * Manipulate command arguments.
+ */
+
+/* List of argument values. */
+TAILQ_HEAD(args_values, args_value);
+
+/* Single arguments flag. */
+struct args_entry {
+ u_char flag;
+ struct args_values values;
+ u_int count;
+ RB_ENTRY(args_entry) entry;
+};
+
+/* Parsed argument flags and values. */
+struct args {
+ struct args_tree tree;
+ u_int count;
+ struct args_value *values;
+};
+
+/* Prepared command state. */
+struct args_command_state {
+ struct cmd_list *cmdlist;
+ char *cmd;
+ struct cmd_parse_input pi;
+};
+
+static struct args_entry *args_find(struct args *, u_char);
+
+static int args_cmp(struct args_entry *, struct args_entry *);
+RB_GENERATE_STATIC(args_tree, args_entry, entry, args_cmp);
+
+/* Arguments tree comparison function. */
+static int
+args_cmp(struct args_entry *a1, struct args_entry *a2)
+{
+ return (a1->flag - a2->flag);
+}
+
+/* Find a flag in the arguments tree. */
+static struct args_entry *
+args_find(struct args *args, u_char flag)
+{
+ struct args_entry entry;
+
+ entry.flag = flag;
+ return (RB_FIND(args_tree, &args->tree, &entry));
+}
+
+/* Copy value. */
+static void
+args_copy_value(struct args_value *to, struct args_value *from)
+{
+ to->type = from->type;
+ switch (from->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_COMMANDS:
+ to->cmdlist = from->cmdlist;
+ to->cmdlist->references++;
+ break;
+ case ARGS_STRING:
+ to->string = xstrdup(from->string);
+ break;
+ }
+}
+
+/* Get value as string. */
+static const char *
+args_value_as_string(struct args_value *value)
+{
+ switch (value->type) {
+ case ARGS_NONE:
+ return ("");
+ case ARGS_COMMANDS:
+ if (value->cached == NULL)
+ value->cached = cmd_list_print(value->cmdlist, 0);
+ return (value->cached);
+ case ARGS_STRING:
+ return (value->string);
+ }
+ fatalx("unexpected argument type");
+}
+
+/* Create an empty arguments set. */
+struct args *
+args_create(void)
+{
+ struct args *args;
+
+ args = xcalloc(1, sizeof *args);
+ RB_INIT(&args->tree);
+ return (args);
+}
+
+/* Parse arguments into a new argument set. */
+struct args *
+args_parse(const struct args_parse *parse, struct args_value *values,
+ u_int count, char **cause)
+{
+ struct args *args;
+ u_int i;
+ enum args_parse_type type;
+ struct args_value *value, *new;
+ u_char flag;
+ const char *found, *string, *s;
+ int optional_argument;
+
+ if (count == 0)
+ return (args_create());
+
+ args = args_create();
+ for (i = 1; i < count; /* nothing */) {
+ value = &values[i];
+ if (value->type != ARGS_STRING)
+ break;
+
+ string = value->string;
+ if (*string++ != '-' || *string == '\0')
+ break;
+ i++;
+ if (string[0] == '-' && string[1] == '\0')
+ break;
+
+ for (;;) {
+ flag = *string++;
+ if (flag == '\0')
+ break;
+ if (flag == '?') {
+ args_free(args);
+ return (NULL);
+ }
+ if (!isalnum(flag)) {
+ xasprintf(cause, "invalid flag -%c", flag);
+ args_free(args);
+ return (NULL);
+ }
+ found = strchr(parse->template, flag);
+ if (found == NULL) {
+ xasprintf(cause, "unknown flag -%c", flag);
+ args_free(args);
+ return (NULL);
+ }
+ if (*++found != ':') {
+ log_debug("%s: -%c", __func__, flag);
+ args_set(args, flag, NULL);
+ continue;
+ }
+ if (*found == ':') {
+ optional_argument = 1;
+ found++;
+ }
+ new = xcalloc(1, sizeof *new);
+ if (*string != '\0') {
+ new->type = ARGS_STRING;
+ new->string = xstrdup(string);
+ } else {
+ if (i == count) {
+ if (optional_argument) {
+ log_debug("%s: -%c", __func__,
+ flag);
+ args_set(args, flag, NULL);
+ continue;
+ }
+ xasprintf(cause,
+ "-%c expects an argument",
+ flag);
+ args_free(args);
+ return (NULL);
+ }
+ if (values[i].type != ARGS_STRING) {
+ xasprintf(cause,
+ "-%c argument must be a string",
+ flag);
+ args_free(args);
+ return (NULL);
+ }
+ args_copy_value(new, &values[i++]);
+ }
+ s = args_value_as_string(new);
+ log_debug("%s: -%c = %s", __func__, flag, s);
+ args_set(args, flag, new);
+ break;
+ }
+ }
+ log_debug("%s: flags end at %u of %u", __func__, i, count);
+ if (i != count) {
+ for (/* nothing */; i < count; i++) {
+ value = &values[i];
+
+ s = args_value_as_string(value);
+ log_debug("%s: %u = %s (type %d)", __func__, i, s,
+ value->type);
+
+ if (parse->cb != NULL) {
+ type = parse->cb(args, args->count, cause);
+ if (type == ARGS_PARSE_INVALID) {
+ args_free(args);
+ return (NULL);
+ }
+ } else
+ type = ARGS_PARSE_STRING;
+
+ args->values = xrecallocarray(args->values,
+ args->count, args->count + 1, sizeof *args->values);
+ new = &args->values[args->count++];
+
+ switch (type) {
+ case ARGS_PARSE_INVALID:
+ fatalx("unexpected argument type");
+ case ARGS_PARSE_STRING:
+ if (value->type != ARGS_STRING) {
+ xasprintf(cause,
+ "argument %u must be \"string\"",
+ args->count);
+ args_free(args);
+ return (NULL);
+ }
+ args_copy_value(new, value);
+ break;
+ case ARGS_PARSE_COMMANDS_OR_STRING:
+ args_copy_value(new, value);
+ break;
+ case ARGS_PARSE_COMMANDS:
+ if (value->type != ARGS_COMMANDS) {
+ xasprintf(cause,
+ "argument %u must be { commands }",
+ args->count);
+ args_free(args);
+ return (NULL);
+ }
+ args_copy_value(new, value);
+ break;
+ }
+ }
+ }
+
+ if (parse->lower != -1 && args->count < (u_int)parse->lower) {
+ xasprintf(cause,
+ "too few arguments (need at least %u)",
+ parse->lower);
+ args_free(args);
+ return (NULL);
+ }
+ if (parse->upper != -1 && args->count > (u_int)parse->upper) {
+ xasprintf(cause,
+ "too many arguments (need at most %u)",
+ parse->upper);
+ args_free(args);
+ return (NULL);
+ }
+ return (args);
+}
+
+/* Copy and expand a value. */
+static void
+args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
+ char **argv)
+{
+ char *s, *expanded;
+ int i;
+
+ to->type = from->type;
+ switch (from->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_STRING:
+ expanded = xstrdup(from->string);
+ for (i = 0; i < argc; i++) {
+ s = cmd_template_replace(expanded, argv[i], i + 1);
+ free(expanded);
+ expanded = s;
+ }
+ to->string = expanded;
+ break;
+ case ARGS_COMMANDS:
+ to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
+ break;
+ }
+}
+
+/* Copy an arguments set. */
+struct args *
+args_copy(struct args *args, int argc, char **argv)
+{
+ struct args *new_args;
+ struct args_entry *entry;
+ struct args_value *value, *new_value;
+ u_int i;
+
+ cmd_log_argv(argc, argv, "%s", __func__);
+
+ new_args = args_create();
+ RB_FOREACH(entry, args_tree, &args->tree) {
+ if (TAILQ_EMPTY(&entry->values)) {
+ for (i = 0; i < entry->count; i++)
+ args_set(new_args, entry->flag, NULL);
+ continue;
+ }
+ TAILQ_FOREACH(value, &entry->values, entry) {
+ new_value = xcalloc(1, sizeof *new_value);
+ args_copy_copy_value(new_value, value, argc, argv);
+ args_set(new_args, entry->flag, new_value);
+ }
+ }
+ if (args->count == 0)
+ return (new_args);
+ new_args->count = args->count;
+ new_args->values = xcalloc(args->count, sizeof *new_args->values);
+ for (i = 0; i < args->count; i++) {
+ new_value = &new_args->values[i];
+ args_copy_copy_value(new_value, &args->values[i], argc, argv);
+ }
+ return (new_args);
+}
+
+/* Free a value. */
+void
+args_free_value(struct args_value *value)
+{
+ switch (value->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_STRING:
+ free(value->string);
+ break;
+ case ARGS_COMMANDS:
+ cmd_list_free(value->cmdlist);
+ break;
+ }
+ free(value->cached);
+}
+
+/* Free values. */
+void
+args_free_values(struct args_value *values, u_int count)
+{
+ u_int i;
+
+ for (i = 0; i < count; i++)
+ args_free_value(&values[i]);
+}
+
+/* Free an arguments set. */
+void
+args_free(struct args *args)
+{
+ struct args_entry *entry;
+ struct args_entry *entry1;
+ struct args_value *value;
+ struct args_value *value1;
+
+ args_free_values(args->values, args->count);
+ free(args->values);
+
+ RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) {
+ RB_REMOVE(args_tree, &args->tree, entry);
+ TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) {
+ TAILQ_REMOVE(&entry->values, value, entry);
+ args_free_value(value);
+ free(value);
+ }
+ free(entry);
+ }
+
+ free(args);
+}
+
+/* Convert arguments to vector. */
+void
+args_to_vector(struct args *args, int *argc, char ***argv)
+{
+ char *s;
+ u_int i;
+
+ *argc = 0;
+ *argv = NULL;
+
+ for (i = 0; i < args->count; i++) {
+ switch (args->values[i].type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_STRING:
+ cmd_append_argv(argc, argv, args->values[i].string);
+ break;
+ case ARGS_COMMANDS:
+ s = cmd_list_print(args->values[i].cmdlist, 0);
+ cmd_append_argv(argc, argv, s);
+ free(s);
+ break;
+ }
+ }
+}
+
+/* Convert arguments from vector. */
+struct args_value *
+args_from_vector(int argc, char **argv)
+{
+ struct args_value *values;
+ int i;
+
+ values = xcalloc(argc, sizeof *values);
+ for (i = 0; i < argc; i++) {
+ values[i].type = ARGS_STRING;
+ values[i].string = xstrdup(argv[i]);
+ }
+ return (values);
+}
+
+/* Add to string. */
+static void printflike(3, 4)
+args_print_add(char **buf, size_t *len, const char *fmt, ...)
+{
+ va_list ap;
+ char *s;
+ size_t slen;
+
+ va_start(ap, fmt);
+ slen = xvasprintf(&s, fmt, ap);
+ va_end(ap);
+
+ *len += slen;
+ *buf = xrealloc(*buf, *len);
+
+ strlcat(*buf, s, *len);
+ free(s);
+}
+
+/* Add value to string. */
+static void
+args_print_add_value(char **buf, size_t *len, struct args_value *value)
+{
+ char *expanded = NULL;
+
+ if (**buf != '\0')
+ args_print_add(buf, len, " ");
+
+ switch (value->type) {
+ case ARGS_NONE:
+ break;
+ case ARGS_COMMANDS:
+ expanded = cmd_list_print(value->cmdlist, 0);
+ args_print_add(buf, len, "{ %s }", expanded);
+ break;
+ case ARGS_STRING:
+ expanded = args_escape(value->string);
+ args_print_add(buf, len, "%s", expanded);
+ break;
+ }
+ free(expanded);
+}
+
+/* Print a set of arguments. */
+char *
+args_print(struct args *args)
+{
+ size_t len;
+ char *buf;
+ u_int i, j;
+ struct args_entry *entry;
+ struct args_value *value;
+
+ len = 1;
+ buf = xcalloc(1, len);
+
+ /* Process the flags first. */
+ RB_FOREACH(entry, args_tree, &args->tree) {
+ if (!TAILQ_EMPTY(&entry->values))
+ continue;
+
+ if (*buf == '\0')
+ args_print_add(&buf, &len, "-");
+ for (j = 0; j < entry->count; j++)
+ args_print_add(&buf, &len, "%c", entry->flag);
+ }
+
+ /* Then the flags with arguments. */
+ RB_FOREACH(entry, args_tree, &args->tree) {
+ TAILQ_FOREACH(value, &entry->values, entry) {
+ if (*buf != '\0')
+ args_print_add(&buf, &len, " -%c", entry->flag);
+ else
+ args_print_add(&buf, &len, "-%c", entry->flag);
+ args_print_add_value(&buf, &len, value);
+ }
+ }
+
+ /* And finally the argument vector. */
+ for (i = 0; i < args->count; i++)
+ args_print_add_value(&buf, &len, &args->values[i]);
+
+ return (buf);
+}
+
+/* Escape an argument. */
+char *
+args_escape(const char *s)
+{
+ static const char dquoted[] = " #';${}%";
+ static const char squoted[] = " \"";
+ char *escaped, *result;
+ int flags, quotes = 0;
+
+ if (*s == '\0') {
+ xasprintf(&result, "''");
+ return (result);
+ }
+ if (s[strcspn(s, dquoted)] != '\0')
+ quotes = '"';
+ else if (s[strcspn(s, squoted)] != '\0')
+ quotes = '\'';
+
+ if (s[0] != ' ' &&
+ s[1] == '\0' &&
+ (quotes != 0 || s[0] == '~')) {
+ xasprintf(&escaped, "\\%c", s[0]);
+ return (escaped);
+ }
+
+ flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
+ if (quotes == '"')
+ flags |= VIS_DQ;
+ utf8_stravis(&escaped, s, flags);
+
+ if (quotes == '\'')
+ xasprintf(&result, "'%s'", escaped);
+ else if (quotes == '"') {
+ if (*escaped == '~')
+ xasprintf(&result, "\"\\%s\"", escaped);
+ else
+ xasprintf(&result, "\"%s\"", escaped);
+ } else {
+ if (*escaped == '~')
+ xasprintf(&result, "\\%s", escaped);
+ else
+ result = xstrdup(escaped);
+ }
+ free(escaped);
+ return (result);
+}
+
+/* Return if an argument is present. */
+int
+args_has(struct args *args, u_char flag)
+{
+ struct args_entry *entry;
+
+ entry = args_find(args, flag);
+ if (entry == NULL)
+ return (0);
+ return (entry->count);
+}
+
+/* Set argument value in the arguments tree. */
+void
+args_set(struct args *args, u_char flag, struct args_value *value)
+{
+ struct args_entry *entry;
+
+ entry = args_find(args, flag);
+ if (entry == NULL) {
+ entry = xcalloc(1, sizeof *entry);
+ entry->flag = flag;
+ entry->count = 1;
+ TAILQ_INIT(&entry->values);
+ RB_INSERT(args_tree, &args->tree, entry);
+ } else
+ entry->count++;
+ if (value != NULL && value->type != ARGS_NONE)
+ TAILQ_INSERT_TAIL(&entry->values, value, entry);
+}
+
+/* Get argument value. Will be NULL if it isn't present. */
+const char *
+args_get(struct args *args, u_char flag)
+{
+ struct args_entry *entry;
+
+ if ((entry = args_find(args, flag)) == NULL)
+ return (NULL);
+ if (TAILQ_EMPTY(&entry->values))
+ return (NULL);
+ return (TAILQ_LAST(&entry->values, args_values)->string);
+}
+
+/* Get first argument. */
+u_char
+args_first(struct args *args, struct args_entry **entry)
+{
+ *entry = RB_MIN(args_tree, &args->tree);
+ if (*entry == NULL)
+ return (0);
+ return ((*entry)->flag);
+}
+
+/* Get next argument. */
+u_char
+args_next(struct args_entry **entry)
+{
+ *entry = RB_NEXT(args_tree, &args->tree, *entry);
+ if (*entry == NULL)
+ return (0);
+ return ((*entry)->flag);
+}
+
+/* Get argument count. */
+u_int
+args_count(struct args *args)
+{
+ return (args->count);
+}
+
+/* Get argument values. */
+struct args_value *
+args_values(struct args *args)
+{
+ return (args->values);
+}
+
+/* Get argument value. */
+struct args_value *
+args_value(struct args *args, u_int idx)
+{
+ if (idx >= args->count)
+ return (NULL);
+ return (&args->values[idx]);
+}
+
+/* Return argument as string. */
+const char *
+args_string(struct args *args, u_int idx)
+{
+ if (idx >= args->count)
+ return (NULL);
+ return (args_value_as_string(&args->values[idx]));
+}
+
+/* Make a command now. */
+struct cmd_list *
+args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
+ int expand)
+{
+ struct args_command_state *state;
+ char *error;
+ struct cmd_list *cmdlist;
+
+ state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
+ cmdlist = args_make_commands(state, 0, NULL, &error);
+ if (cmdlist == NULL) {
+ cmdq_error(item, "%s", error);
+ free(error);
+ }
+ else
+ cmdlist->references++;
+ args_make_commands_free(state);
+ return (cmdlist);
+}
+
+/* Save bits to make a command later. */
+struct args_command_state *
+args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
+ const char *default_command, int wait, int expand)
+{
+ struct args *args = cmd_get_args(self);
+ struct cmd_find_state *target = cmdq_get_target(item);
+ struct client *tc = cmdq_get_target_client(item);
+ struct args_value *value;
+ struct args_command_state *state;
+ const char *cmd;
+
+ state = xcalloc(1, sizeof *state);
+
+ if (idx < args->count) {
+ value = &args->values[idx];
+ if (value->type == ARGS_COMMANDS) {
+ state->cmdlist = value->cmdlist;
+ state->cmdlist->references++;
+ return (state);
+ }
+ cmd = value->string;
+ } else {
+ if (default_command == NULL)
+ fatalx("argument out of range");
+ cmd = default_command;
+ }
+
+
+ if (expand)
+ state->cmd = format_single_from_target(item, cmd);
+ else
+ state->cmd = xstrdup(cmd);
+ log_debug("%s: %s", __func__, state->cmd);
+
+ if (wait)
+ state->pi.item = item;
+ cmd_get_source(self, &state->pi.file, &state->pi.line);
+ state->pi.c = tc;
+ if (state->pi.c != NULL)
+ state->pi.c->references++;
+ cmd_find_copy_state(&state->pi.fs, target);
+
+ return (state);
+}
+
+/* Return argument as command. */
+struct cmd_list *
+args_make_commands(struct args_command_state *state, int argc, char **argv,
+ char **error)
+{
+ struct cmd_parse_result *pr;
+ char *cmd, *new_cmd;
+ int i;
+
+ if (state->cmdlist != NULL) {
+ if (argc == 0)
+ return (state->cmdlist);
+ return (cmd_list_copy(state->cmdlist, argc, argv));
+ }
+
+ cmd = xstrdup(state->cmd);
+ for (i = 0; i < argc; i++) {
+ new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
+ log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
+ free(cmd);
+ cmd = new_cmd;
+ }
+ log_debug("%s: %s", __func__, cmd);
+
+ pr = cmd_parse_from_string(cmd, &state->pi);
+ free(cmd);
+ switch (pr->status) {
+ case CMD_PARSE_ERROR:
+ *error = pr->error;
+ return (NULL);
+ case CMD_PARSE_SUCCESS:
+ return (pr->cmdlist);
+ }
+ fatalx("invalid parse return state");
+}
+
+/* Free commands state. */
+void
+args_make_commands_free(struct args_command_state *state)
+{
+ if (state->cmdlist != NULL)
+ cmd_list_free(state->cmdlist);
+ if (state->pi.c != NULL)
+ server_client_unref(state->pi.c);
+ free(state->cmd);
+ free(state);
+}
+
+/* Get prepared command. */
+char *
+args_make_commands_get_command(struct args_command_state *state)
+{
+ struct cmd *first;
+ int n;
+ char *s;
+
+ if (state->cmdlist != NULL) {
+ first = cmd_list_first(state->cmdlist);
+ if (first == NULL)
+ return (xstrdup(""));
+ return (xstrdup(cmd_get_entry(first)->name));
+ }
+ n = strcspn(state->cmd, " ,");
+ xasprintf(&s, "%.*s", n, state->cmd);
+ return (s);
+}
+
+/* Get first value in argument. */
+struct args_value *
+args_first_value(struct args *args, u_char flag)
+{
+ struct args_entry *entry;
+
+ if ((entry = args_find(args, flag)) == NULL)
+ return (NULL);
+ return (TAILQ_FIRST(&entry->values));
+}
+
+/* Get next value in argument. */
+struct args_value *
+args_next_value(struct args_value *value)
+{
+ return (TAILQ_NEXT(value, entry));
+}
+
+/* Convert an argument value to a number. */
+long long
+args_strtonum(struct args *args, u_char flag, long long minval,
+ long long maxval, char **cause)
+{
+ const char *errstr;
+ long long ll;
+ struct args_entry *entry;
+ struct args_value *value;
+
+ if ((entry = args_find(args, flag)) == NULL) {
+ *cause = xstrdup("missing");
+ return (0);
+ }
+ value = TAILQ_LAST(&entry->values, args_values);
+ if (value == NULL ||
+ value->type != ARGS_STRING ||
+ value->string == NULL) {
+ *cause = xstrdup("missing");
+ return (0);
+ }
+
+ ll = strtonum(value->string, minval, maxval, &errstr);
+ if (errstr != NULL) {
+ *cause = xstrdup(errstr);
+ return (0);
+ }
+
+ *cause = NULL;
+ return (ll);
+}
+
+/* Convert an argument to a number which may be a percentage. */
+long long
+args_percentage(struct args *args, u_char flag, long long minval,
+ long long maxval, long long curval, char **cause)
+{
+ const char *value;
+ struct args_entry *entry;
+
+ if ((entry = args_find(args, flag)) == NULL) {
+ *cause = xstrdup("missing");
+ return (0);
+ }
+ value = TAILQ_LAST(&entry->values, args_values)->string;
+ return (args_string_percentage(value, minval, maxval, curval, cause));
+}
+
+/* Convert a string to a number which may be a percentage. */
+long long
+args_string_percentage(const char *value, long long minval, long long maxval,
+ long long curval, char **cause)
+{
+ const char *errstr;
+ long long ll;
+ size_t valuelen = strlen(value);
+ char *copy;
+
+ if (value[valuelen - 1] == '%') {
+ copy = xstrdup(value);
+ copy[valuelen - 1] = '\0';
+
+ ll = strtonum(copy, 0, 100, &errstr);
+ free(copy);
+ if (errstr != NULL) {
+ *cause = xstrdup(errstr);
+ return (0);
+ }
+ ll = (curval * ll) / 100;
+ if (ll < minval) {
+ *cause = xstrdup("too small");
+ return (0);
+ }
+ if (ll > maxval) {
+ *cause = xstrdup("too large");
+ return (0);
+ }
+ } else {
+ ll = strtonum(value, minval, maxval, &errstr);
+ if (errstr != NULL) {
+ *cause = xstrdup(errstr);
+ return (0);
+ }
+ }
+
+ *cause = NULL;
+ return (ll);
+}