xref: /netbsd-src/external/bsd/tmux/dist/arguments.c (revision 890b6d91a44b7fcb2dfbcbc1e93463086e462d2d)
15494e770Schristos /* $OpenBSD$ */
2d530c4d0Sjmmv 
3d530c4d0Sjmmv /*
4ed4e6cd4Schristos  * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
5d530c4d0Sjmmv  *
6d530c4d0Sjmmv  * Permission to use, copy, modify, and distribute this software for any
7d530c4d0Sjmmv  * purpose with or without fee is hereby granted, provided that the above
8d530c4d0Sjmmv  * copyright notice and this permission notice appear in all copies.
9d530c4d0Sjmmv  *
10d530c4d0Sjmmv  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11d530c4d0Sjmmv  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d530c4d0Sjmmv  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13d530c4d0Sjmmv  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14d530c4d0Sjmmv  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15d530c4d0Sjmmv  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16d530c4d0Sjmmv  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17d530c4d0Sjmmv  */
18d530c4d0Sjmmv 
19d530c4d0Sjmmv #include <sys/types.h>
20d530c4d0Sjmmv 
216db26757Swiz #include <ctype.h>
22d530c4d0Sjmmv #include <stdlib.h>
23d530c4d0Sjmmv #include <string.h>
24928fc495Schristos #include <unistd.h>
25d530c4d0Sjmmv 
26d530c4d0Sjmmv #include "tmux.h"
27d530c4d0Sjmmv 
28928fc495Schristos /*
29928fc495Schristos  * Manipulate command arguments.
30928fc495Schristos  */
31928fc495Schristos 
326db26757Swiz /* List of argument values. */
336483eba0Schristos TAILQ_HEAD(args_values, args_value);
346483eba0Schristos 
356db26757Swiz /* Single arguments flag. */
365494e770Schristos struct args_entry {
375494e770Schristos 	u_char			 flag;
386483eba0Schristos 	struct args_values	 values;
396483eba0Schristos 	u_int			 count;
40c23f9150Swiz 
41c23f9150Swiz 	int			 flags;
42c23f9150Swiz #define ARGS_ENTRY_OPTIONAL_VALUE 0x1
43c23f9150Swiz 
445494e770Schristos 	RB_ENTRY(args_entry)	 entry;
455494e770Schristos };
465494e770Schristos 
476db26757Swiz /* Parsed argument flags and values. */
486db26757Swiz struct args {
496db26757Swiz 	struct args_tree	 tree;
506db26757Swiz 	u_int			 count;
516db26757Swiz 	struct args_value	*values;
526db26757Swiz };
536db26757Swiz 
546db26757Swiz /* Prepared command state. */
556db26757Swiz struct args_command_state {
566db26757Swiz 	struct cmd_list		*cmdlist;
576db26757Swiz 	char			*cmd;
586db26757Swiz 	struct cmd_parse_input	 pi;
596db26757Swiz };
606db26757Swiz 
614e179ddaSchristos static struct args_entry	*args_find(struct args *, u_char);
62928fc495Schristos 
634e179ddaSchristos static int	args_cmp(struct args_entry *, struct args_entry *);
644e179ddaSchristos RB_GENERATE_STATIC(args_tree, args_entry, entry, args_cmp);
65928fc495Schristos 
66928fc495Schristos /* Arguments tree comparison function. */
674e179ddaSchristos static int
68928fc495Schristos args_cmp(struct args_entry *a1, struct args_entry *a2)
69928fc495Schristos {
70928fc495Schristos 	return (a1->flag - a2->flag);
71928fc495Schristos }
72928fc495Schristos 
73928fc495Schristos /* Find a flag in the arguments tree. */
744e179ddaSchristos static struct args_entry *
759fb66d81Schristos args_find(struct args *args, u_char flag)
76928fc495Schristos {
77928fc495Schristos 	struct args_entry	entry;
78928fc495Schristos 
799fb66d81Schristos 	entry.flag = flag;
80928fc495Schristos 	return (RB_FIND(args_tree, &args->tree, &entry));
81928fc495Schristos }
82928fc495Schristos 
836db26757Swiz /* Copy value. */
846db26757Swiz static void
856db26757Swiz args_copy_value(struct args_value *to, struct args_value *from)
866db26757Swiz {
876db26757Swiz 	to->type = from->type;
886db26757Swiz 	switch (from->type) {
896db26757Swiz 	case ARGS_NONE:
906db26757Swiz 		break;
916db26757Swiz 	case ARGS_COMMANDS:
926db26757Swiz 		to->cmdlist = from->cmdlist;
936db26757Swiz 		to->cmdlist->references++;
946db26757Swiz 		break;
956db26757Swiz 	case ARGS_STRING:
966db26757Swiz 		to->string = xstrdup(from->string);
976db26757Swiz 		break;
986db26757Swiz 	}
996db26757Swiz }
1006db26757Swiz 
101c23f9150Swiz /* Type to string. */
102c23f9150Swiz static const char *
103c23f9150Swiz args_type_to_string (enum args_type type)
104c23f9150Swiz {
105c23f9150Swiz 	switch (type)
106c23f9150Swiz 	{
107c23f9150Swiz 	case ARGS_NONE:
108c23f9150Swiz 		return "NONE";
109c23f9150Swiz 	case ARGS_STRING:
110c23f9150Swiz 		return "STRING";
111c23f9150Swiz 	case ARGS_COMMANDS:
112c23f9150Swiz 		return "COMMANDS";
113c23f9150Swiz 	}
114c23f9150Swiz 	return "INVALID";
115c23f9150Swiz }
116c23f9150Swiz 
1176db26757Swiz /* Get value as string. */
1186db26757Swiz static const char *
1196db26757Swiz args_value_as_string(struct args_value *value)
1206db26757Swiz {
1216db26757Swiz 	switch (value->type) {
1226db26757Swiz 	case ARGS_NONE:
1236db26757Swiz 		return ("");
1246db26757Swiz 	case ARGS_COMMANDS:
1256db26757Swiz 		if (value->cached == NULL)
1266db26757Swiz 			value->cached = cmd_list_print(value->cmdlist, 0);
1276db26757Swiz 		return (value->cached);
1286db26757Swiz 	case ARGS_STRING:
1296db26757Swiz 		return (value->string);
1306db26757Swiz 	}
1316db26757Swiz 	fatalx("unexpected argument type");
1326db26757Swiz }
1336db26757Swiz 
1346db26757Swiz /* Create an empty arguments set. */
135d530c4d0Sjmmv struct args *
1366db26757Swiz args_create(void)
137d530c4d0Sjmmv {
138d530c4d0Sjmmv 	struct args	 *args;
139d530c4d0Sjmmv 
140d530c4d0Sjmmv 	args = xcalloc(1, sizeof *args);
1416db26757Swiz 	RB_INIT(&args->tree);
1426db26757Swiz 	return (args);
1436db26757Swiz }
144d530c4d0Sjmmv 
145c23f9150Swiz /* Parse a single flag. */
146c23f9150Swiz static int
147c23f9150Swiz args_parse_flag_argument(struct args_value *values, u_int count, char **cause,
148c23f9150Swiz     struct args *args, u_int *i, const char *string, int flag,
149c23f9150Swiz     int optional_argument)
150c23f9150Swiz {
151c23f9150Swiz 	struct args_value	*argument, *new;
152c23f9150Swiz 	const char		*s;
153c23f9150Swiz 
154c23f9150Swiz 	new = xcalloc(1, sizeof *new);
155c23f9150Swiz 	if (*string != '\0') {
156c23f9150Swiz 		new->type = ARGS_STRING;
157c23f9150Swiz 		new->string = xstrdup(string);
158c23f9150Swiz 		goto out;
159c23f9150Swiz 	}
160c23f9150Swiz 
161c23f9150Swiz 	if (*i == count)
162c23f9150Swiz 		argument = NULL;
163c23f9150Swiz 	else {
164c23f9150Swiz 		argument = &values[*i];
165c23f9150Swiz 		if (argument->type != ARGS_STRING) {
166c23f9150Swiz 			xasprintf(cause, "-%c argument must be a string", flag);
167*890b6d91Swiz 			args_free_value(new);
168*890b6d91Swiz 			free(new);
169c23f9150Swiz 			return (-1);
170c23f9150Swiz 		}
171c23f9150Swiz 	}
172c23f9150Swiz 	if (argument == NULL) {
173*890b6d91Swiz 		args_free_value(new);
174*890b6d91Swiz 		free(new);
175c23f9150Swiz 		if (optional_argument) {
176c23f9150Swiz 			log_debug("%s: -%c (optional)", __func__, flag);
177c23f9150Swiz 			args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE);
178c23f9150Swiz 			return (0); /* either - or end */
179c23f9150Swiz 		}
180c23f9150Swiz 		xasprintf(cause, "-%c expects an argument", flag);
181c23f9150Swiz 		return (-1);
182c23f9150Swiz 	}
183c23f9150Swiz 	args_copy_value(new, argument);
184c23f9150Swiz 	(*i)++;
185c23f9150Swiz 
186c23f9150Swiz out:
187c23f9150Swiz 	s = args_value_as_string(new);
188c23f9150Swiz 	log_debug("%s: -%c = %s", __func__, flag, s);
189c23f9150Swiz 	args_set(args, flag, new, 0);
190c23f9150Swiz 	return (0);
191c23f9150Swiz }
192c23f9150Swiz 
193c23f9150Swiz /* Parse flags argument. */
194c23f9150Swiz static int
195c23f9150Swiz args_parse_flags(const struct args_parse *parse, struct args_value *values,
196c23f9150Swiz     u_int count, char **cause, struct args *args, u_int *i)
197c23f9150Swiz {
198c23f9150Swiz 	struct args_value	*value;
199c23f9150Swiz 	u_char			 flag;
200c23f9150Swiz 	const char		*found, *string;
201c23f9150Swiz 	int			 optional_argument;
202c23f9150Swiz 
203c23f9150Swiz 	value = &values[*i];
204c23f9150Swiz 	if (value->type != ARGS_STRING)
205c23f9150Swiz 		return (1);
206c23f9150Swiz 
207c23f9150Swiz 	string = value->string;
208c23f9150Swiz 	log_debug("%s: next %s", __func__, string);
209c23f9150Swiz 	if (*string++ != '-' || *string == '\0')
210c23f9150Swiz 		return (1);
211c23f9150Swiz 	(*i)++;
212c23f9150Swiz 	if (string[0] == '-' && string[1] == '\0')
213c23f9150Swiz 		return (1);
214c23f9150Swiz 
215c23f9150Swiz 	for (;;) {
216c23f9150Swiz 		flag = *string++;
217c23f9150Swiz 		if (flag == '\0')
218c23f9150Swiz 			return (0);
219c23f9150Swiz 		if (flag == '?')
220c23f9150Swiz 			return (-1);
221c23f9150Swiz 		if (!isalnum(flag)) {
222c23f9150Swiz 			xasprintf(cause, "invalid flag -%c", flag);
223c23f9150Swiz 			return (-1);
224c23f9150Swiz 		}
225c23f9150Swiz 
226c23f9150Swiz 		found = strchr(parse->template, flag);
227c23f9150Swiz 		if (found == NULL) {
228c23f9150Swiz 			xasprintf(cause, "unknown flag -%c", flag);
229c23f9150Swiz 			return (-1);
230c23f9150Swiz 		}
231c23f9150Swiz 		if (found[1] != ':') {
232c23f9150Swiz 			log_debug("%s: -%c", __func__, flag);
233c23f9150Swiz 			args_set(args, flag, NULL, 0);
234c23f9150Swiz 			continue;
235c23f9150Swiz 		}
236c23f9150Swiz 		optional_argument = (found[2] == ':');
237c23f9150Swiz 		return (args_parse_flag_argument(values, count, cause, args, i,
238c23f9150Swiz 		    string, flag, optional_argument));
239c23f9150Swiz 	}
240c23f9150Swiz }
241c23f9150Swiz 
2426db26757Swiz /* Parse arguments into a new argument set. */
2436db26757Swiz struct args *
2446db26757Swiz args_parse(const struct args_parse *parse, struct args_value *values,
2456db26757Swiz     u_int count, char **cause)
2466db26757Swiz {
2476db26757Swiz 	struct args		*args;
2486db26757Swiz 	u_int			 i;
2496db26757Swiz 	enum args_parse_type	 type;
2506db26757Swiz 	struct args_value	*value, *new;
251c23f9150Swiz 	const char		*s;
252c23f9150Swiz 	int			 stop;
253d530c4d0Sjmmv 
2546db26757Swiz 	if (count == 0)
2556db26757Swiz 		return (args_create());
2566db26757Swiz 
2576db26757Swiz 	args = args_create();
2586db26757Swiz 	for (i = 1; i < count; /* nothing */) {
259c23f9150Swiz 		stop = args_parse_flags(parse, values, count, cause, args, &i);
260c23f9150Swiz 		if (stop == -1) {
261928fc495Schristos 			args_free(args);
262d530c4d0Sjmmv 			return (NULL);
263d530c4d0Sjmmv 		}
264c23f9150Swiz 		if (stop == 1)
2656db26757Swiz 			break;
2666db26757Swiz 	}
2676db26757Swiz 	log_debug("%s: flags end at %u of %u", __func__, i, count);
2686db26757Swiz 	if (i != count) {
2696db26757Swiz 		for (/* nothing */; i < count; i++) {
2706db26757Swiz 			value = &values[i];
271d530c4d0Sjmmv 
2726db26757Swiz 			s = args_value_as_string(value);
273c23f9150Swiz 			log_debug("%s: %u = %s (type %s)", __func__, i, s,
274c23f9150Swiz 			    args_type_to_string (value->type));
275d530c4d0Sjmmv 
2766db26757Swiz 			if (parse->cb != NULL) {
2776db26757Swiz 				type = parse->cb(args, args->count, cause);
2786db26757Swiz 				if (type == ARGS_PARSE_INVALID) {
2796db26757Swiz 					args_free(args);
2806db26757Swiz 					return (NULL);
2816db26757Swiz 				}
2826db26757Swiz 			} else
2836db26757Swiz 				type = ARGS_PARSE_STRING;
2846db26757Swiz 
2856db26757Swiz 			args->values = xrecallocarray(args->values,
2866db26757Swiz 			    args->count, args->count + 1, sizeof *args->values);
2876db26757Swiz 			new = &args->values[args->count++];
2886db26757Swiz 
2896db26757Swiz 			switch (type) {
2906db26757Swiz 			case ARGS_PARSE_INVALID:
2916db26757Swiz 				fatalx("unexpected argument type");
2926db26757Swiz 			case ARGS_PARSE_STRING:
2936db26757Swiz 				if (value->type != ARGS_STRING) {
2946db26757Swiz 					xasprintf(cause,
2956db26757Swiz 					    "argument %u must be \"string\"",
2966db26757Swiz 					    args->count);
2976db26757Swiz 					args_free(args);
2986db26757Swiz 					return (NULL);
2996db26757Swiz 				}
3006db26757Swiz 				args_copy_value(new, value);
3016db26757Swiz 				break;
3026db26757Swiz 			case ARGS_PARSE_COMMANDS_OR_STRING:
3036db26757Swiz 				args_copy_value(new, value);
3046db26757Swiz 				break;
3056db26757Swiz 			case ARGS_PARSE_COMMANDS:
3066db26757Swiz 				if (value->type != ARGS_COMMANDS) {
3076db26757Swiz 					xasprintf(cause,
3086db26757Swiz 					    "argument %u must be { commands }",
3096db26757Swiz 					    args->count);
3106db26757Swiz 					args_free(args);
3116db26757Swiz 					return (NULL);
3126db26757Swiz 				}
3136db26757Swiz 				args_copy_value(new, value);
3146db26757Swiz 				break;
3156db26757Swiz 			}
3166db26757Swiz 		}
3176db26757Swiz 	}
3186db26757Swiz 
3196db26757Swiz 	if (parse->lower != -1 && args->count < (u_int)parse->lower) {
3206db26757Swiz 		xasprintf(cause,
3216db26757Swiz 		    "too few arguments (need at least %u)",
3226db26757Swiz 		    parse->lower);
3236db26757Swiz 		args_free(args);
3246db26757Swiz 		return (NULL);
3256db26757Swiz 	}
3266db26757Swiz 	if (parse->upper != -1 && args->count > (u_int)parse->upper) {
3276db26757Swiz 		xasprintf(cause,
3286db26757Swiz 		    "too many arguments (need at most %u)",
3296db26757Swiz 		    parse->upper);
3306db26757Swiz 		args_free(args);
3316db26757Swiz 		return (NULL);
3326db26757Swiz 	}
333d530c4d0Sjmmv 	return (args);
334d530c4d0Sjmmv }
335d530c4d0Sjmmv 
3366db26757Swiz /* Copy and expand a value. */
3376db26757Swiz static void
3386db26757Swiz args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
3396db26757Swiz     char **argv)
3406db26757Swiz {
3416db26757Swiz 	char	*s, *expanded;
3426db26757Swiz 	int	 i;
3436db26757Swiz 
3446db26757Swiz 	to->type = from->type;
3456db26757Swiz 	switch (from->type) {
3466db26757Swiz 	case ARGS_NONE:
3476db26757Swiz 		break;
3486db26757Swiz 	case ARGS_STRING:
3496db26757Swiz 		expanded = xstrdup(from->string);
3506db26757Swiz 		for (i = 0; i < argc; i++) {
3516db26757Swiz 			s = cmd_template_replace(expanded, argv[i], i + 1);
3526db26757Swiz 			free(expanded);
3536db26757Swiz 			expanded = s;
3546db26757Swiz 		}
3556db26757Swiz 		to->string = expanded;
3566db26757Swiz 		break;
3576db26757Swiz 	case ARGS_COMMANDS:
3586db26757Swiz 		to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
3596db26757Swiz 		break;
3606db26757Swiz 	}
3616db26757Swiz }
3626db26757Swiz 
3636db26757Swiz /* Copy an arguments set. */
3646db26757Swiz struct args *
3656db26757Swiz args_copy(struct args *args, int argc, char **argv)
3666db26757Swiz {
3676db26757Swiz 	struct args		*new_args;
3686db26757Swiz 	struct args_entry	*entry;
3696db26757Swiz 	struct args_value	*value, *new_value;
3706db26757Swiz 	u_int			 i;
3716db26757Swiz 
3726db26757Swiz 	cmd_log_argv(argc, argv, "%s", __func__);
3736db26757Swiz 
3746db26757Swiz 	new_args = args_create();
3756db26757Swiz 	RB_FOREACH(entry, args_tree, &args->tree) {
3766db26757Swiz 		if (TAILQ_EMPTY(&entry->values)) {
3776db26757Swiz 			for (i = 0; i < entry->count; i++)
378c23f9150Swiz 				args_set(new_args, entry->flag, NULL, 0);
3796db26757Swiz 			continue;
3806db26757Swiz 		}
3816db26757Swiz 		TAILQ_FOREACH(value, &entry->values, entry) {
3826db26757Swiz 			new_value = xcalloc(1, sizeof *new_value);
3836db26757Swiz 			args_copy_copy_value(new_value, value, argc, argv);
384c23f9150Swiz 			args_set(new_args, entry->flag, new_value, 0);
3856db26757Swiz 		}
3866db26757Swiz 	}
3876db26757Swiz 	if (args->count == 0)
3886db26757Swiz 		return (new_args);
3896db26757Swiz 	new_args->count = args->count;
3906db26757Swiz 	new_args->values = xcalloc(args->count, sizeof *new_args->values);
3916db26757Swiz 	for (i = 0; i < args->count; i++) {
3926db26757Swiz 		new_value = &new_args->values[i];
3936db26757Swiz 		args_copy_copy_value(new_value, &args->values[i], argc, argv);
3946db26757Swiz 	}
3956db26757Swiz 	return (new_args);
3966db26757Swiz }
3976db26757Swiz 
3986db26757Swiz /* Free a value. */
3996db26757Swiz void
4006db26757Swiz args_free_value(struct args_value *value)
4016db26757Swiz {
4026db26757Swiz 	switch (value->type) {
4036db26757Swiz 	case ARGS_NONE:
4046db26757Swiz 		break;
4056db26757Swiz 	case ARGS_STRING:
4066db26757Swiz 		free(value->string);
4076db26757Swiz 		break;
4086db26757Swiz 	case ARGS_COMMANDS:
4096db26757Swiz 		cmd_list_free(value->cmdlist);
4106db26757Swiz 		break;
4116db26757Swiz 	}
4126db26757Swiz 	free(value->cached);
4136db26757Swiz }
4146db26757Swiz 
4156db26757Swiz /* Free values. */
4166db26757Swiz void
4176db26757Swiz args_free_values(struct args_value *values, u_int count)
4186db26757Swiz {
4196db26757Swiz 	u_int	i;
4206db26757Swiz 
4216db26757Swiz 	for (i = 0; i < count; i++)
4226db26757Swiz 		args_free_value(&values[i]);
4236db26757Swiz }
4246db26757Swiz 
425d530c4d0Sjmmv /* Free an arguments set. */
426d530c4d0Sjmmv void
427d530c4d0Sjmmv args_free(struct args *args)
428d530c4d0Sjmmv {
429928fc495Schristos 	struct args_entry	*entry;
430928fc495Schristos 	struct args_entry	*entry1;
4316483eba0Schristos 	struct args_value	*value;
4326483eba0Schristos 	struct args_value	*value1;
433d530c4d0Sjmmv 
4346db26757Swiz 	args_free_values(args->values, args->count);
4356db26757Swiz 	free(args->values);
436d530c4d0Sjmmv 
437928fc495Schristos 	RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) {
438928fc495Schristos 		RB_REMOVE(args_tree, &args->tree, entry);
4396483eba0Schristos 		TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) {
4406483eba0Schristos 			TAILQ_REMOVE(&entry->values, value, entry);
4416db26757Swiz 			args_free_value(value);
4426483eba0Schristos 			free(value);
4436483eba0Schristos 		}
444928fc495Schristos 		free(entry);
445d530c4d0Sjmmv 	}
446d530c4d0Sjmmv 
447928fc495Schristos 	free(args);
448d530c4d0Sjmmv }
449d530c4d0Sjmmv 
4506db26757Swiz /* Convert arguments to vector. */
4516db26757Swiz void
4526db26757Swiz args_to_vector(struct args *args, int *argc, char ***argv)
4536db26757Swiz {
4546db26757Swiz 	char	*s;
4556db26757Swiz 	u_int	 i;
4566db26757Swiz 
4576db26757Swiz 	*argc = 0;
4586db26757Swiz 	*argv = NULL;
4596db26757Swiz 
4606db26757Swiz 	for (i = 0; i < args->count; i++) {
4616db26757Swiz 		switch (args->values[i].type) {
4626db26757Swiz 		case ARGS_NONE:
4636db26757Swiz 			break;
4646db26757Swiz 		case ARGS_STRING:
4656db26757Swiz 			cmd_append_argv(argc, argv, args->values[i].string);
4666db26757Swiz 			break;
4676db26757Swiz 		case ARGS_COMMANDS:
4686db26757Swiz 			s = cmd_list_print(args->values[i].cmdlist, 0);
4696db26757Swiz 			cmd_append_argv(argc, argv, s);
4706db26757Swiz 			free(s);
4716db26757Swiz 			break;
4726db26757Swiz 		}
4736db26757Swiz 	}
4746db26757Swiz }
4756db26757Swiz 
4766db26757Swiz /* Convert arguments from vector. */
4776db26757Swiz struct args_value *
4786db26757Swiz args_from_vector(int argc, char **argv)
4796db26757Swiz {
4806db26757Swiz 	struct args_value	*values;
4816db26757Swiz 	int			 i;
4826db26757Swiz 
4836db26757Swiz 	values = xcalloc(argc, sizeof *values);
4846db26757Swiz 	for (i = 0; i < argc; i++) {
4856db26757Swiz 		values[i].type = ARGS_STRING;
4866db26757Swiz 		values[i].string = xstrdup(argv[i]);
4876db26757Swiz 	}
4886db26757Swiz 	return (values);
4896db26757Swiz }
4906db26757Swiz 
491ed4e6cd4Schristos /* Add to string. */
492ed4e6cd4Schristos static void printflike(3, 4)
493ed4e6cd4Schristos args_print_add(char **buf, size_t *len, const char *fmt, ...)
494d530c4d0Sjmmv {
495ed4e6cd4Schristos 	va_list	 ap;
496ed4e6cd4Schristos 	char	*s;
497ed4e6cd4Schristos 	size_t	 slen;
498ed4e6cd4Schristos 
499ed4e6cd4Schristos 	va_start(ap, fmt);
500ed4e6cd4Schristos 	slen = xvasprintf(&s, fmt, ap);
501ed4e6cd4Schristos 	va_end(ap);
502ed4e6cd4Schristos 
503ed4e6cd4Schristos 	*len += slen;
504ed4e6cd4Schristos 	*buf = xrealloc(*buf, *len);
505ed4e6cd4Schristos 
506ed4e6cd4Schristos 	strlcat(*buf, s, *len);
507ed4e6cd4Schristos 	free(s);
508ed4e6cd4Schristos }
509ed4e6cd4Schristos 
5106483eba0Schristos /* Add value to string. */
5116483eba0Schristos static void
5126db26757Swiz args_print_add_value(char **buf, size_t *len, struct args_value *value)
5136483eba0Schristos {
5146db26757Swiz 	char	*expanded = NULL;
5156483eba0Schristos 
5166483eba0Schristos 	if (**buf != '\0')
5176483eba0Schristos 		args_print_add(buf, len, " ");
5186483eba0Schristos 
5196db26757Swiz 	switch (value->type) {
5206db26757Swiz 	case ARGS_NONE:
5216db26757Swiz 		break;
5226db26757Swiz 	case ARGS_COMMANDS:
5236db26757Swiz 		expanded = cmd_list_print(value->cmdlist, 0);
5246db26757Swiz 		args_print_add(buf, len, "{ %s }", expanded);
5256db26757Swiz 		break;
5266db26757Swiz 	case ARGS_STRING:
5276db26757Swiz 		expanded = args_escape(value->string);
5286db26757Swiz 		args_print_add(buf, len, "%s", expanded);
5296db26757Swiz 		break;
5306db26757Swiz 	}
5316db26757Swiz 	free(expanded);
5326483eba0Schristos }
5336483eba0Schristos 
534ed4e6cd4Schristos /* Print a set of arguments. */
535ed4e6cd4Schristos char *
536ed4e6cd4Schristos args_print(struct args *args)
537ed4e6cd4Schristos {
538ed4e6cd4Schristos 	size_t			 len;
5396483eba0Schristos 	char			*buf;
5406db26757Swiz 	u_int			 i, j;
541928fc495Schristos 	struct args_entry	*entry;
542c23f9150Swiz 	struct args_entry	*last = NULL;
5436483eba0Schristos 	struct args_value	*value;
544d530c4d0Sjmmv 
545ed4e6cd4Schristos 	len = 1;
546ed4e6cd4Schristos 	buf = xcalloc(1, len);
547d530c4d0Sjmmv 
548d530c4d0Sjmmv 	/* Process the flags first. */
549928fc495Schristos 	RB_FOREACH(entry, args_tree, &args->tree) {
550c23f9150Swiz 		if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE)
551c23f9150Swiz 			continue;
5526483eba0Schristos 		if (!TAILQ_EMPTY(&entry->values))
553d530c4d0Sjmmv 			continue;
554d530c4d0Sjmmv 
555ed4e6cd4Schristos 		if (*buf == '\0')
556ed4e6cd4Schristos 			args_print_add(&buf, &len, "-");
5576483eba0Schristos 		for (j = 0; j < entry->count; j++)
558ed4e6cd4Schristos 			args_print_add(&buf, &len, "%c", entry->flag);
559d530c4d0Sjmmv 	}
560d530c4d0Sjmmv 
561d530c4d0Sjmmv 	/* Then the flags with arguments. */
562928fc495Schristos 	RB_FOREACH(entry, args_tree, &args->tree) {
563c23f9150Swiz 		if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) {
564c23f9150Swiz 			if (*buf != '\0')
565c23f9150Swiz 				args_print_add(&buf, &len, " -%c", entry->flag);
566c23f9150Swiz 			else
567c23f9150Swiz 				args_print_add(&buf, &len, "-%c", entry->flag);
568c23f9150Swiz 			last = entry;
569c23f9150Swiz 			continue;
570c23f9150Swiz 		}
571c23f9150Swiz 		if (TAILQ_EMPTY(&entry->values))
572c23f9150Swiz 			continue;
5736db26757Swiz 		TAILQ_FOREACH(value, &entry->values, entry) {
5746db26757Swiz 			if (*buf != '\0')
5756db26757Swiz 				args_print_add(&buf, &len, " -%c", entry->flag);
5766db26757Swiz 			else
5776db26757Swiz 				args_print_add(&buf, &len, "-%c", entry->flag);
5786db26757Swiz 			args_print_add_value(&buf, &len, value);
5796db26757Swiz 		}
580c23f9150Swiz 		last = entry;
581d530c4d0Sjmmv 	}
582c23f9150Swiz 	if (last && (last->flags & ARGS_ENTRY_OPTIONAL_VALUE))
583c23f9150Swiz 		args_print_add(&buf, &len, " --");
584d530c4d0Sjmmv 
585d530c4d0Sjmmv 	/* And finally the argument vector. */
5866db26757Swiz 	for (i = 0; i < args->count; i++)
5876db26757Swiz 		args_print_add_value(&buf, &len, &args->values[i]);
588d530c4d0Sjmmv 
589ed4e6cd4Schristos 	return (buf);
590d530c4d0Sjmmv }
591d530c4d0Sjmmv 
5926483eba0Schristos /* Escape an argument. */
5936483eba0Schristos char *
5946483eba0Schristos args_escape(const char *s)
5956483eba0Schristos {
5966db26757Swiz 	static const char	 dquoted[] = " #';${}%";
5979fb66d81Schristos 	static const char	 squoted[] = " \"";
5986483eba0Schristos 	char			*escaped, *result;
5999fb66d81Schristos 	int			 flags, quotes = 0;
6006483eba0Schristos 
6019fb66d81Schristos 	if (*s == '\0') {
6029fb66d81Schristos 		xasprintf(&result, "''");
6039fb66d81Schristos 		return (result);
6049fb66d81Schristos 	}
6059fb66d81Schristos 	if (s[strcspn(s, dquoted)] != '\0')
6069fb66d81Schristos 		quotes = '"';
6079fb66d81Schristos 	else if (s[strcspn(s, squoted)] != '\0')
6089fb66d81Schristos 		quotes = '\'';
6099fb66d81Schristos 
6106483eba0Schristos 	if (s[0] != ' ' &&
6119fb66d81Schristos 	    s[1] == '\0' &&
6129fb66d81Schristos 	    (quotes != 0 || s[0] == '~')) {
6136483eba0Schristos 		xasprintf(&escaped, "\\%c", s[0]);
6146483eba0Schristos 		return (escaped);
6156483eba0Schristos 	}
6166483eba0Schristos 
6176483eba0Schristos 	flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
6189fb66d81Schristos 	if (quotes == '"')
6196483eba0Schristos 		flags |= VIS_DQ;
6206483eba0Schristos 	utf8_stravis(&escaped, s, flags);
6216483eba0Schristos 
6229fb66d81Schristos 	if (quotes == '\'')
6239fb66d81Schristos 		xasprintf(&result, "'%s'", escaped);
6249fb66d81Schristos 	else if (quotes == '"') {
6256483eba0Schristos 		if (*escaped == '~')
6266483eba0Schristos 			xasprintf(&result, "\"\\%s\"", escaped);
6276483eba0Schristos 		else
6286483eba0Schristos 			xasprintf(&result, "\"%s\"", escaped);
6296483eba0Schristos 	} else {
6306483eba0Schristos 		if (*escaped == '~')
6316483eba0Schristos 			xasprintf(&result, "\\%s", escaped);
6326483eba0Schristos 		else
6336483eba0Schristos 			result = xstrdup(escaped);
6346483eba0Schristos 	}
6356483eba0Schristos 	free(escaped);
6366483eba0Schristos 	return (result);
6376483eba0Schristos }
6386483eba0Schristos 
639d530c4d0Sjmmv /* Return if an argument is present. */
640d530c4d0Sjmmv int
6419fb66d81Schristos args_has(struct args *args, u_char flag)
642d530c4d0Sjmmv {
6436483eba0Schristos 	struct args_entry	*entry;
6446483eba0Schristos 
6459fb66d81Schristos 	entry = args_find(args, flag);
6466483eba0Schristos 	if (entry == NULL)
6476483eba0Schristos 		return (0);
6486483eba0Schristos 	return (entry->count);
649d530c4d0Sjmmv }
650d530c4d0Sjmmv 
651928fc495Schristos /* Set argument value in the arguments tree. */
652c9ad075bSchristos void
653c23f9150Swiz args_set(struct args *args, u_char flag, struct args_value *value, int flags)
654d530c4d0Sjmmv {
655928fc495Schristos 	struct args_entry	*entry;
656928fc495Schristos 
6579fb66d81Schristos 	entry = args_find(args, flag);
6586483eba0Schristos 	if (entry == NULL) {
659928fc495Schristos 		entry = xcalloc(1, sizeof *entry);
6609fb66d81Schristos 		entry->flag = flag;
6616483eba0Schristos 		entry->count = 1;
662c23f9150Swiz 		entry->flags = flags;
6636483eba0Schristos 		TAILQ_INIT(&entry->values);
664928fc495Schristos 		RB_INSERT(args_tree, &args->tree, entry);
6656483eba0Schristos 	} else
6666483eba0Schristos 		entry->count++;
6676db26757Swiz 	if (value != NULL && value->type != ARGS_NONE)
6686483eba0Schristos 		TAILQ_INSERT_TAIL(&entry->values, value, entry);
669*890b6d91Swiz 	else
670*890b6d91Swiz 		free(value);
6716483eba0Schristos }
672d530c4d0Sjmmv 
673d530c4d0Sjmmv /* Get argument value. Will be NULL if it isn't present. */
674d530c4d0Sjmmv const char *
6759fb66d81Schristos args_get(struct args *args, u_char flag)
676d530c4d0Sjmmv {
677928fc495Schristos 	struct args_entry	*entry;
678928fc495Schristos 
6799fb66d81Schristos 	if ((entry = args_find(args, flag)) == NULL)
6809fb66d81Schristos 		return (NULL);
6819fb66d81Schristos 	if (TAILQ_EMPTY(&entry->values))
682928fc495Schristos 		return (NULL);
6836db26757Swiz 	return (TAILQ_LAST(&entry->values, args_values)->string);
6846483eba0Schristos }
6856483eba0Schristos 
6869fb66d81Schristos /* Get first argument. */
6879fb66d81Schristos u_char
6889fb66d81Schristos args_first(struct args *args, struct args_entry **entry)
6899fb66d81Schristos {
6909fb66d81Schristos 	*entry = RB_MIN(args_tree, &args->tree);
6919fb66d81Schristos 	if (*entry == NULL)
6929fb66d81Schristos 		return (0);
6939fb66d81Schristos 	return ((*entry)->flag);
6949fb66d81Schristos }
6959fb66d81Schristos 
6969fb66d81Schristos /* Get next argument. */
6979fb66d81Schristos u_char
6989fb66d81Schristos args_next(struct args_entry **entry)
6999fb66d81Schristos {
7009fb66d81Schristos 	*entry = RB_NEXT(args_tree, &args->tree, *entry);
7019fb66d81Schristos 	if (*entry == NULL)
7029fb66d81Schristos 		return (0);
7039fb66d81Schristos 	return ((*entry)->flag);
7049fb66d81Schristos }
7059fb66d81Schristos 
7066db26757Swiz /* Get argument count. */
7076db26757Swiz u_int
7086db26757Swiz args_count(struct args *args)
7096db26757Swiz {
7106db26757Swiz 	return (args->count);
7116db26757Swiz }
7126db26757Swiz 
7136db26757Swiz /* Get argument values. */
7146db26757Swiz struct args_value *
7156db26757Swiz args_values(struct args *args)
7166db26757Swiz {
7176db26757Swiz 	return (args->values);
7186db26757Swiz }
7196db26757Swiz 
7206db26757Swiz /* Get argument value. */
7216db26757Swiz struct args_value *
7226db26757Swiz args_value(struct args *args, u_int idx)
7236db26757Swiz {
7246db26757Swiz 	if (idx >= args->count)
7256db26757Swiz 		return (NULL);
7266db26757Swiz 	return (&args->values[idx]);
7276db26757Swiz }
7286db26757Swiz 
7296db26757Swiz /* Return argument as string. */
7306483eba0Schristos const char *
7316db26757Swiz args_string(struct args *args, u_int idx)
7326db26757Swiz {
7336db26757Swiz 	if (idx >= args->count)
7346db26757Swiz 		return (NULL);
7356db26757Swiz 	return (args_value_as_string(&args->values[idx]));
7366db26757Swiz }
7376db26757Swiz 
7386db26757Swiz /* Make a command now. */
7396db26757Swiz struct cmd_list *
7406db26757Swiz args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx,
7416db26757Swiz     int expand)
7426db26757Swiz {
7436db26757Swiz 	struct args_command_state	*state;
7446db26757Swiz 	char				*error;
7456db26757Swiz 	struct cmd_list			*cmdlist;
7466db26757Swiz 
7476db26757Swiz 	state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
7486db26757Swiz 	cmdlist = args_make_commands(state, 0, NULL, &error);
7496db26757Swiz 	if (cmdlist == NULL) {
7506db26757Swiz 		cmdq_error(item, "%s", error);
7516db26757Swiz 		free(error);
7526db26757Swiz 	}
7536db26757Swiz 	else
7546db26757Swiz 		cmdlist->references++;
7556db26757Swiz 	args_make_commands_free(state);
7566db26757Swiz 	return (cmdlist);
7576db26757Swiz }
7586db26757Swiz 
7596db26757Swiz /* Save bits to make a command later. */
7606db26757Swiz struct args_command_state *
7616db26757Swiz args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
7626db26757Swiz     const char *default_command, int wait, int expand)
7636db26757Swiz {
7646db26757Swiz 	struct args			*args = cmd_get_args(self);
7656db26757Swiz 	struct cmd_find_state		*target = cmdq_get_target(item);
7666db26757Swiz 	struct client			*tc = cmdq_get_target_client(item);
7676db26757Swiz 	struct args_value		*value;
7686db26757Swiz 	struct args_command_state	*state;
7696db26757Swiz 	const char			*cmd;
770c23f9150Swiz 	const char			*file;
7716db26757Swiz 
7726db26757Swiz 	state = xcalloc(1, sizeof *state);
7736db26757Swiz 
7746db26757Swiz 	if (idx < args->count) {
7756db26757Swiz 		value = &args->values[idx];
7766db26757Swiz 		if (value->type == ARGS_COMMANDS) {
7776db26757Swiz 			state->cmdlist = value->cmdlist;
7786db26757Swiz 			state->cmdlist->references++;
7796db26757Swiz 			return (state);
7806db26757Swiz 		}
7816db26757Swiz 		cmd = value->string;
7826db26757Swiz 	} else {
7836db26757Swiz 		if (default_command == NULL)
7846db26757Swiz 			fatalx("argument out of range");
7856db26757Swiz 		cmd = default_command;
7866db26757Swiz 	}
7876db26757Swiz 
7886db26757Swiz 
7896db26757Swiz 	if (expand)
7906db26757Swiz 		state->cmd = format_single_from_target(item, cmd);
7916db26757Swiz 	else
7926db26757Swiz 		state->cmd = xstrdup(cmd);
7936db26757Swiz 	log_debug("%s: %s", __func__, state->cmd);
7946db26757Swiz 
7956db26757Swiz 	if (wait)
7966db26757Swiz 		state->pi.item = item;
797c23f9150Swiz 	cmd_get_source(self, &file, &state->pi.line);
798c23f9150Swiz 	if (file != NULL)
799c23f9150Swiz 		state->pi.file = xstrdup(file);
8006db26757Swiz 	state->pi.c = tc;
8016db26757Swiz 	if (state->pi.c != NULL)
8026db26757Swiz 		state->pi.c->references++;
8036db26757Swiz 	cmd_find_copy_state(&state->pi.fs, target);
8046db26757Swiz 
8056db26757Swiz 	return (state);
8066db26757Swiz }
8076db26757Swiz 
8086db26757Swiz /* Return argument as command. */
8096db26757Swiz struct cmd_list *
8106db26757Swiz args_make_commands(struct args_command_state *state, int argc, char **argv,
8116db26757Swiz     char **error)
8126db26757Swiz {
8136db26757Swiz 	struct cmd_parse_result	*pr;
8146db26757Swiz 	char			*cmd, *new_cmd;
8156db26757Swiz 	int			 i;
8166db26757Swiz 
8176db26757Swiz 	if (state->cmdlist != NULL) {
8186db26757Swiz 		if (argc == 0)
8196db26757Swiz 			return (state->cmdlist);
8206db26757Swiz 		return (cmd_list_copy(state->cmdlist, argc, argv));
8216db26757Swiz 	}
8226db26757Swiz 
8236db26757Swiz 	cmd = xstrdup(state->cmd);
824c23f9150Swiz 	log_debug("%s: %s", __func__, cmd);
825c23f9150Swiz 	cmd_log_argv(argc, argv, __func__);
8266db26757Swiz 	for (i = 0; i < argc; i++) {
8276db26757Swiz 		new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
8286db26757Swiz 		log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
8296db26757Swiz 		free(cmd);
8306db26757Swiz 		cmd = new_cmd;
8316db26757Swiz 	}
8326db26757Swiz 	log_debug("%s: %s", __func__, cmd);
8336db26757Swiz 
8346db26757Swiz 	pr = cmd_parse_from_string(cmd, &state->pi);
8356db26757Swiz 	free(cmd);
8366db26757Swiz 	switch (pr->status) {
8376db26757Swiz 	case CMD_PARSE_ERROR:
8386db26757Swiz 		*error = pr->error;
8396db26757Swiz 		return (NULL);
8406db26757Swiz 	case CMD_PARSE_SUCCESS:
8416db26757Swiz 		return (pr->cmdlist);
8426db26757Swiz 	}
8436db26757Swiz 	fatalx("invalid parse return state");
8446db26757Swiz }
8456db26757Swiz 
8466db26757Swiz /* Free commands state. */
8476db26757Swiz void
8486db26757Swiz args_make_commands_free(struct args_command_state *state)
8496db26757Swiz {
8506db26757Swiz 	if (state->cmdlist != NULL)
8516db26757Swiz 		cmd_list_free(state->cmdlist);
8526db26757Swiz 	if (state->pi.c != NULL)
8536db26757Swiz 		server_client_unref(state->pi.c);
854f844e94eSwiz 	free(__UNCONST(state->pi.file));
8556db26757Swiz 	free(state->cmd);
8566db26757Swiz 	free(state);
8576db26757Swiz }
8586db26757Swiz 
8596db26757Swiz /* Get prepared command. */
8606db26757Swiz char *
8616db26757Swiz args_make_commands_get_command(struct args_command_state *state)
8626db26757Swiz {
8636db26757Swiz 	struct cmd	*first;
8646db26757Swiz 	int		 n;
8656db26757Swiz 	char		*s;
8666db26757Swiz 
8676db26757Swiz 	if (state->cmdlist != NULL) {
8686db26757Swiz 		first = cmd_list_first(state->cmdlist);
8696db26757Swiz 		if (first == NULL)
8706db26757Swiz 			return (xstrdup(""));
8716db26757Swiz 		return (xstrdup(cmd_get_entry(first)->name));
8726db26757Swiz 	}
8736db26757Swiz 	n = strcspn(state->cmd, " ,");
8746db26757Swiz 	xasprintf(&s, "%.*s", n, state->cmd);
8756db26757Swiz 	return (s);
8766db26757Swiz }
8776db26757Swiz 
8786db26757Swiz /* Get first value in argument. */
8796db26757Swiz struct args_value *
8806db26757Swiz args_first_value(struct args *args, u_char flag)
8816483eba0Schristos {
8826483eba0Schristos 	struct args_entry	*entry;
8836483eba0Schristos 
8849fb66d81Schristos 	if ((entry = args_find(args, flag)) == NULL)
8856483eba0Schristos 		return (NULL);
8866db26757Swiz 	return (TAILQ_FIRST(&entry->values));
8876483eba0Schristos }
8886483eba0Schristos 
8896483eba0Schristos /* Get next value in argument. */
8906db26757Swiz struct args_value *
8916db26757Swiz args_next_value(struct args_value *value)
8926483eba0Schristos {
8936db26757Swiz 	return (TAILQ_NEXT(value, entry));
894d530c4d0Sjmmv }
895d530c4d0Sjmmv 
896d530c4d0Sjmmv /* Convert an argument value to a number. */
897d530c4d0Sjmmv long long
8989fb66d81Schristos args_strtonum(struct args *args, u_char flag, long long minval,
8999fb66d81Schristos     long long maxval, char **cause)
900d530c4d0Sjmmv {
901d530c4d0Sjmmv 	const char		*errstr;
902d530c4d0Sjmmv 	long long		 ll;
903928fc495Schristos 	struct args_entry	*entry;
9046483eba0Schristos 	struct args_value	*value;
905d530c4d0Sjmmv 
9069fb66d81Schristos 	if ((entry = args_find(args, flag)) == NULL) {
907d530c4d0Sjmmv 		*cause = xstrdup("missing");
908d530c4d0Sjmmv 		return (0);
909d530c4d0Sjmmv 	}
9106483eba0Schristos 	value = TAILQ_LAST(&entry->values, args_values);
9116db26757Swiz 	if (value == NULL ||
9126db26757Swiz 	    value->type != ARGS_STRING ||
9136db26757Swiz 	    value->string == NULL) {
9146db26757Swiz 		*cause = xstrdup("missing");
9156db26757Swiz 		return (0);
9166db26757Swiz 	}
917d530c4d0Sjmmv 
9186db26757Swiz 	ll = strtonum(value->string, minval, maxval, &errstr);
919d530c4d0Sjmmv 	if (errstr != NULL) {
920d530c4d0Sjmmv 		*cause = xstrdup(errstr);
921d530c4d0Sjmmv 		return (0);
922d530c4d0Sjmmv 	}
923d530c4d0Sjmmv 
924d530c4d0Sjmmv 	*cause = NULL;
925d530c4d0Sjmmv 	return (ll);
926d530c4d0Sjmmv }
9279fb66d81Schristos 
928c23f9150Swiz /* Convert an argument value to a number, and expand formats. */
929c23f9150Swiz long long
930c23f9150Swiz args_strtonum_and_expand(struct args *args, u_char flag, long long minval,
931c23f9150Swiz     long long maxval, struct cmdq_item *item, char **cause)
932c23f9150Swiz {
933c23f9150Swiz 	const char		*errstr;
934c23f9150Swiz 	char			*formatted;
935c23f9150Swiz 	long long		 ll;
936c23f9150Swiz 	struct args_entry	*entry;
937c23f9150Swiz 	struct args_value	*value;
938c23f9150Swiz 
939c23f9150Swiz 	if ((entry = args_find(args, flag)) == NULL) {
940c23f9150Swiz 		*cause = xstrdup("missing");
941c23f9150Swiz 		return (0);
942c23f9150Swiz 	}
943c23f9150Swiz 	value = TAILQ_LAST(&entry->values, args_values);
944c23f9150Swiz 	if (value == NULL ||
945c23f9150Swiz 	    value->type != ARGS_STRING ||
946c23f9150Swiz 	    value->string == NULL) {
947c23f9150Swiz 		*cause = xstrdup("missing");
948c23f9150Swiz 		return (0);
949c23f9150Swiz 	}
950c23f9150Swiz 
951c23f9150Swiz 	formatted = format_single_from_target(item, value->string);
952c23f9150Swiz 	ll = strtonum(formatted, minval, maxval, &errstr);
953c23f9150Swiz 	free(formatted);
954c23f9150Swiz 	if (errstr != NULL) {
955c23f9150Swiz 		*cause = xstrdup(errstr);
956c23f9150Swiz 		return (0);
957c23f9150Swiz 	}
958c23f9150Swiz 
959c23f9150Swiz 	*cause = NULL;
960c23f9150Swiz 	return (ll);
961c23f9150Swiz }
962c23f9150Swiz 
9639fb66d81Schristos /* Convert an argument to a number which may be a percentage. */
9649fb66d81Schristos long long
9659fb66d81Schristos args_percentage(struct args *args, u_char flag, long long minval,
9669fb66d81Schristos     long long maxval, long long curval, char **cause)
9679fb66d81Schristos {
9689fb66d81Schristos 	const char		*value;
9699fb66d81Schristos 	struct args_entry	*entry;
9709fb66d81Schristos 
9719fb66d81Schristos 	if ((entry = args_find(args, flag)) == NULL) {
9729fb66d81Schristos 		*cause = xstrdup("missing");
9739fb66d81Schristos 		return (0);
9749fb66d81Schristos 	}
975c23f9150Swiz 	if (TAILQ_EMPTY(&entry->values)) {
976c23f9150Swiz 		*cause = xstrdup("empty");
977c23f9150Swiz 		return (0);
978c23f9150Swiz 	}
9796db26757Swiz 	value = TAILQ_LAST(&entry->values, args_values)->string;
9809fb66d81Schristos 	return (args_string_percentage(value, minval, maxval, curval, cause));
9819fb66d81Schristos }
9829fb66d81Schristos 
9839fb66d81Schristos /* Convert a string to a number which may be a percentage. */
9849fb66d81Schristos long long
9859fb66d81Schristos args_string_percentage(const char *value, long long minval, long long maxval,
9869fb66d81Schristos     long long curval, char **cause)
9879fb66d81Schristos {
9889fb66d81Schristos 	const char	*errstr;
9899fb66d81Schristos 	long long	 ll;
9909fb66d81Schristos 	size_t		 valuelen = strlen(value);
9919fb66d81Schristos 	char		*copy;
9929fb66d81Schristos 
993c23f9150Swiz 	if (valuelen == 0) {
994c23f9150Swiz 		*cause = xstrdup("empty");
995c23f9150Swiz 		return (0);
996c23f9150Swiz 	}
9979fb66d81Schristos 	if (value[valuelen - 1] == '%') {
9989fb66d81Schristos 		copy = xstrdup(value);
9999fb66d81Schristos 		copy[valuelen - 1] = '\0';
10009fb66d81Schristos 
10019fb66d81Schristos 		ll = strtonum(copy, 0, 100, &errstr);
10029fb66d81Schristos 		free(copy);
10039fb66d81Schristos 		if (errstr != NULL) {
10049fb66d81Schristos 			*cause = xstrdup(errstr);
10059fb66d81Schristos 			return (0);
10069fb66d81Schristos 		}
10079fb66d81Schristos 		ll = (curval * ll) / 100;
10089fb66d81Schristos 		if (ll < minval) {
10099fb66d81Schristos 			*cause = xstrdup("too small");
10109fb66d81Schristos 			return (0);
10119fb66d81Schristos 		}
10129fb66d81Schristos 		if (ll > maxval) {
10139fb66d81Schristos 			*cause = xstrdup("too large");
10149fb66d81Schristos 			return (0);
10159fb66d81Schristos 		}
10169fb66d81Schristos 	} else {
10179fb66d81Schristos 		ll = strtonum(value, minval, maxval, &errstr);
10189fb66d81Schristos 		if (errstr != NULL) {
10199fb66d81Schristos 			*cause = xstrdup(errstr);
10209fb66d81Schristos 			return (0);
10219fb66d81Schristos 		}
10229fb66d81Schristos 	}
10239fb66d81Schristos 
10249fb66d81Schristos 	*cause = NULL;
10259fb66d81Schristos 	return (ll);
10269fb66d81Schristos }
1027c23f9150Swiz 
1028c23f9150Swiz /*
1029c23f9150Swiz  * Convert an argument to a number which may be a percentage, and expand
1030c23f9150Swiz  * formats.
1031c23f9150Swiz  */
1032c23f9150Swiz long long
1033c23f9150Swiz args_percentage_and_expand(struct args *args, u_char flag, long long minval,
1034c23f9150Swiz     long long maxval, long long curval, struct cmdq_item *item, char **cause)
1035c23f9150Swiz {
1036c23f9150Swiz 	const char		*value;
1037c23f9150Swiz 	struct args_entry	*entry;
1038c23f9150Swiz 
1039c23f9150Swiz 	if ((entry = args_find(args, flag)) == NULL) {
1040c23f9150Swiz 		*cause = xstrdup("missing");
1041c23f9150Swiz 		return (0);
1042c23f9150Swiz 	}
1043c23f9150Swiz 	if (TAILQ_EMPTY(&entry->values)) {
1044c23f9150Swiz 		*cause = xstrdup("empty");
1045c23f9150Swiz 		return (0);
1046c23f9150Swiz 	}
1047c23f9150Swiz 	value = TAILQ_LAST(&entry->values, args_values)->string;
1048c23f9150Swiz 	return (args_string_percentage_and_expand(value, minval, maxval, curval,
1049c23f9150Swiz 		    item, cause));
1050c23f9150Swiz }
1051c23f9150Swiz 
1052c23f9150Swiz /*
1053c23f9150Swiz  * Convert a string to a number which may be a percentage, and expand formats.
1054c23f9150Swiz  */
1055c23f9150Swiz long long
1056c23f9150Swiz args_string_percentage_and_expand(const char *value, long long minval,
1057c23f9150Swiz     long long maxval, long long curval, struct cmdq_item *item, char **cause)
1058c23f9150Swiz {
1059c23f9150Swiz 	const char	*errstr;
1060c23f9150Swiz 	long long	 ll;
1061c23f9150Swiz 	size_t		 valuelen = strlen(value);
1062c23f9150Swiz 	char		*copy, *f;
1063c23f9150Swiz 
1064c23f9150Swiz 	if (value[valuelen - 1] == '%') {
1065c23f9150Swiz 		copy = xstrdup(value);
1066c23f9150Swiz 		copy[valuelen - 1] = '\0';
1067c23f9150Swiz 
1068c23f9150Swiz 		f = format_single_from_target(item, copy);
1069c23f9150Swiz 		ll = strtonum(f, 0, 100, &errstr);
1070c23f9150Swiz 		free(f);
1071c23f9150Swiz 		free(copy);
1072c23f9150Swiz 		if (errstr != NULL) {
1073c23f9150Swiz 			*cause = xstrdup(errstr);
1074c23f9150Swiz 			return (0);
1075c23f9150Swiz 		}
1076c23f9150Swiz 		ll = (curval * ll) / 100;
1077c23f9150Swiz 		if (ll < minval) {
1078c23f9150Swiz 			*cause = xstrdup("too small");
1079c23f9150Swiz 			return (0);
1080c23f9150Swiz 		}
1081c23f9150Swiz 		if (ll > maxval) {
1082c23f9150Swiz 			*cause = xstrdup("too large");
1083c23f9150Swiz 			return (0);
1084c23f9150Swiz 		}
1085c23f9150Swiz 	} else {
1086c23f9150Swiz 		f = format_single_from_target(item, value);
1087c23f9150Swiz 		ll = strtonum(f, minval, maxval, &errstr);
1088c23f9150Swiz 		free(f);
1089c23f9150Swiz 		if (errstr != NULL) {
1090c23f9150Swiz 			*cause = xstrdup(errstr);
1091c23f9150Swiz 			return (0);
1092c23f9150Swiz 		}
1093c23f9150Swiz 	}
1094c23f9150Swiz 
1095c23f9150Swiz 	*cause = NULL;
1096c23f9150Swiz 	return (ll);
1097c23f9150Swiz }
1098