xref: /openbsd-src/usr.bin/tmux/arguments.c (revision ffcef06798eb7b98532e76a80212f0772bebc4f6)
1 /* $OpenBSD: arguments.c,v 1.49 2021/09/02 07:11:03 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <ctype.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <vis.h>
25 
26 #include "tmux.h"
27 
28 /*
29  * Manipulate command arguments.
30  */
31 
32 /* List of argument values. */
33 TAILQ_HEAD(args_values, args_value);
34 
35 /* Single arguments flag. */
36 struct args_entry {
37 	u_char			 flag;
38 	struct args_values	 values;
39 	u_int			 count;
40 	RB_ENTRY(args_entry)	 entry;
41 };
42 
43 /* Parsed argument flags and values. */
44 struct args {
45 	struct args_tree	 tree;
46 	u_int			 count;
47 	struct args_value	*values;
48 };
49 
50 /* Prepared command state. */
51 struct args_command_state {
52 	struct cmd_list		*cmdlist;
53 	char			*cmd;
54 	struct cmd_parse_input	 pi;
55 };
56 
57 static struct args_entry	*args_find(struct args *, u_char);
58 
59 static int	args_cmp(struct args_entry *, struct args_entry *);
60 RB_GENERATE_STATIC(args_tree, args_entry, entry, args_cmp);
61 
62 /* Arguments tree comparison function. */
63 static int
64 args_cmp(struct args_entry *a1, struct args_entry *a2)
65 {
66 	return (a1->flag - a2->flag);
67 }
68 
69 /* Find a flag in the arguments tree. */
70 static struct args_entry *
71 args_find(struct args *args, u_char flag)
72 {
73 	struct args_entry	entry;
74 
75 	entry.flag = flag;
76 	return (RB_FIND(args_tree, &args->tree, &entry));
77 }
78 
79 /* Copy value. */
80 static void
81 args_copy_value(struct args_value *to, struct args_value *from)
82 {
83 	to->type = from->type;
84 	switch (from->type) {
85 	case ARGS_NONE:
86 		break;
87 	case ARGS_COMMANDS:
88 		to->cmdlist = from->cmdlist;
89 		to->cmdlist->references++;
90 		break;
91 	case ARGS_STRING:
92 		to->string = xstrdup(from->string);
93 		break;
94 	}
95 }
96 
97 /* Get value as string. */
98 static const char *
99 args_value_as_string(struct args_value *value)
100 {
101 	switch (value->type) {
102 	case ARGS_NONE:
103 		return ("");
104 	case ARGS_COMMANDS:
105 		if (value->cached == NULL)
106 			value->cached = cmd_list_print(value->cmdlist, 0);
107 		return (value->cached);
108 	case ARGS_STRING:
109 		return (value->string);
110 	}
111 }
112 
113 /* Create an empty arguments set. */
114 struct args *
115 args_create(void)
116 {
117 	struct args	 *args;
118 
119 	args = xcalloc(1, sizeof *args);
120 	RB_INIT(&args->tree);
121 	return (args);
122 }
123 
124 /* Parse arguments into a new argument set. */
125 struct args *
126 args_parse(const struct args_parse *parse, struct args_value *values,
127     u_int count, char **cause)
128 {
129 	struct args		*args;
130 	u_int			 i;
131 	enum args_parse_type	 type;
132 	struct args_value	*value, *new;
133 	u_char			 flag, argument;
134 	const char		*found, *string, *s;
135 
136 	if (count == 0)
137 		return (args_create());
138 
139 	args = args_create();
140 	for (i = 1; i < count; /* nothing */) {
141 		value = &values[i];
142 		if (value->type != ARGS_STRING)
143 			break;
144 
145 		string = value->string;
146 		if (*string++ != '-' || *string == '\0')
147 			break;
148 		i++;
149 		if (string[0] == '-' && string[1] == '\0')
150 			break;
151 
152 		for (;;) {
153 			flag = *string++;
154 			if (flag == '\0')
155 				break;
156 			if (!isalnum(flag)) {
157 				xasprintf(cause, "invalid flag -%c", flag);
158 				args_free(args);
159 				return (NULL);
160 			}
161 			found = strchr(parse->template, flag);
162 			if (found == NULL) {
163 				xasprintf(cause, "unknown flag -%c", flag);
164 				args_free(args);
165 				return (NULL);
166 			}
167 			argument = *++found;
168 			if (argument != ':') {
169 				log_debug("%s: -%c", __func__, flag);
170 				args_set(args, flag, NULL);
171 				continue;
172 			}
173 			new = xcalloc(1, sizeof *new);
174 			if (*string != '\0') {
175 				new->type = ARGS_STRING;
176 				new->string = xstrdup(string);
177 			} else {
178 				if (i == count) {
179 					xasprintf(cause,
180 					    "-%c expects an argument",
181 					    flag);
182 					args_free(args);
183 					return (NULL);
184 				}
185 				if (values[i].type != ARGS_STRING) {
186 					xasprintf(cause,
187 					    "-%c argument must be a string",
188 					    flag);
189 					args_free(args);
190 					return (NULL);
191 				}
192 				args_copy_value(new, &values[i++]);
193 			}
194 			s = args_value_as_string(new);
195 			log_debug("%s: -%c = %s", __func__, flag, s);
196 			args_set(args, flag, new);
197 			break;
198 		}
199 	}
200 	log_debug("%s: flags end at %u of %u", __func__, i, count);
201 	if (i != count) {
202 		for (/* nothing */; i < count; i++) {
203 			value = &values[i];
204 
205 			s = args_value_as_string(value);
206 			log_debug("%s: %u = %s (type %d)", __func__, i, s,
207 			    value->type);
208 
209 			if (parse->cb != NULL) {
210 				type = parse->cb(args, args->count, cause);
211 				if (type == ARGS_PARSE_INVALID) {
212 					args_free(args);
213 					return (NULL);
214 				}
215 			} else
216 				type = ARGS_PARSE_STRING;
217 
218 			args->values = xrecallocarray(args->values,
219 			    args->count, args->count + 1, sizeof *args->values);
220 			new = &args->values[args->count++];
221 
222 			switch (type) {
223 			case ARGS_PARSE_INVALID:
224 				fatalx("unexpected argument type");
225 			case ARGS_PARSE_STRING:
226 				if (value->type != ARGS_STRING) {
227 					xasprintf(cause,
228 					    "argument %u must be \"string\"",
229 					    args->count);
230 					args_free(args);
231 					return (NULL);
232 				}
233 				args_copy_value(new, value);
234 				break;
235 			case ARGS_PARSE_COMMANDS_OR_STRING:
236 				args_copy_value(new, value);
237 				break;
238 			case ARGS_PARSE_COMMANDS:
239 				if (value->type != ARGS_COMMANDS) {
240 					xasprintf(cause,
241 					    "argument %u must be { commands }",
242 					    args->count);
243 					args_free(args);
244 					return (NULL);
245 				}
246 				args_copy_value(new, value);
247 				break;
248 			}
249 		}
250 	}
251 
252 	if (parse->lower != -1 && args->count < (u_int)parse->lower) {
253 		xasprintf(cause,
254 		    "too few arguments (need at least %u)",
255 		    parse->lower);
256 		args_free(args);
257 		return (NULL);
258 	}
259 	if (parse->upper != -1 && args->count > (u_int)parse->upper) {
260 		xasprintf(cause,
261 		    "too many arguments (need at most %u)",
262 		    parse->upper);
263 		args_free(args);
264 		return (NULL);
265 	}
266 	return (args);
267 }
268 
269 /* Copy and expand a value. */
270 static void
271 args_copy_copy_value(struct args_value *to, struct args_value *from, int argc,
272     char **argv)
273 {
274 	char	*s, *expanded;
275 	int	 i;
276 
277 	to->type = from->type;
278 	switch (from->type) {
279 	case ARGS_NONE:
280 		break;
281 	case ARGS_STRING:
282 		expanded = xstrdup(from->string);
283 		for (i = 0; i < argc; i++) {
284 			s = cmd_template_replace(expanded, argv[i], i + 1);
285 			free(expanded);
286 			expanded = s;
287 		}
288 		to->string = expanded;
289 		break;
290 	case ARGS_COMMANDS:
291 		to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv);
292 		break;
293 	}
294 }
295 
296 /* Copy an arguments set. */
297 struct args *
298 args_copy(struct args *args, int argc, char **argv)
299 {
300 	struct args		*new_args;
301 	struct args_entry	*entry;
302 	struct args_value	*value, *new_value;
303 	u_int			 i;
304 
305 	cmd_log_argv(argc, argv, "%s", __func__);
306 
307 	new_args = args_create();
308 	RB_FOREACH(entry, args_tree, &args->tree) {
309 		if (TAILQ_EMPTY(&entry->values)) {
310 			for (i = 0; i < entry->count; i++)
311 				args_set(new_args, entry->flag, NULL);
312 			continue;
313 		}
314 		TAILQ_FOREACH(value, &entry->values, entry) {
315 			new_value = xcalloc(1, sizeof *new_value);
316 			args_copy_copy_value(new_value, value, argc, argv);
317 			args_set(new_args, entry->flag, new_value);
318 		}
319 	}
320 	if (args->count == 0)
321 		return (new_args);
322 	new_args->count = args->count;
323 	new_args->values = xcalloc(args->count, sizeof *new_args->values);
324 	for (i = 0; i < args->count; i++) {
325 		new_value = &new_args->values[i];
326 		args_copy_copy_value(new_value, &args->values[i], argc, argv);
327 	}
328 	return (new_args);
329 }
330 
331 /* Free a value. */
332 void
333 args_free_value(struct args_value *value)
334 {
335 	switch (value->type) {
336 	case ARGS_NONE:
337 		break;
338 	case ARGS_STRING:
339 		free(value->string);
340 		break;
341 	case ARGS_COMMANDS:
342 		cmd_list_free(value->cmdlist);
343 		break;
344 	}
345 	free(value->cached);
346 }
347 
348 /* Free values. */
349 void
350 args_free_values(struct args_value *values, u_int count)
351 {
352 	u_int	i;
353 
354 	for (i = 0; i < count; i++)
355 		args_free_value(&values[i]);
356 }
357 
358 /* Free an arguments set. */
359 void
360 args_free(struct args *args)
361 {
362 	struct args_entry	*entry;
363 	struct args_entry	*entry1;
364 	struct args_value	*value;
365 	struct args_value	*value1;
366 
367 	args_free_values(args->values, args->count);
368 	free(args->values);
369 
370 	RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) {
371 		RB_REMOVE(args_tree, &args->tree, entry);
372 		TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) {
373 			TAILQ_REMOVE(&entry->values, value, entry);
374 			args_free_value(value);
375 			free(value);
376 		}
377 		free(entry);
378 	}
379 
380 	free(args);
381 }
382 
383 /* Convert arguments to vector. */
384 void
385 args_to_vector(struct args *args, int *argc, char ***argv)
386 {
387 	char	*s;
388 	u_int	 i;
389 
390 	*argc = 0;
391 	*argv = NULL;
392 
393 	for (i = 0; i < args->count; i++) {
394 		switch (args->values[i].type) {
395 		case ARGS_NONE:
396 			break;
397 		case ARGS_STRING:
398 			cmd_append_argv(argc, argv, args->values[i].string);
399 			break;
400 		case ARGS_COMMANDS:
401 			s = cmd_list_print(args->values[i].cmdlist, 0);
402 			cmd_append_argv(argc, argv, s);
403 			free(s);
404 			break;
405 		}
406 	}
407 }
408 
409 /* Convert arguments from vector. */
410 struct args_value *
411 args_from_vector(int argc, char **argv)
412 {
413 	struct args_value	*values;
414 	int			 i;
415 
416 	values = xcalloc(argc, sizeof *values);
417 	for (i = 0; i < argc; i++) {
418 		values[i].type = ARGS_STRING;
419 		values[i].string = xstrdup(argv[i]);
420 	}
421 	return (values);
422 }
423 
424 /* Add to string. */
425 static void printflike(3, 4)
426 args_print_add(char **buf, size_t *len, const char *fmt, ...)
427 {
428 	va_list	 ap;
429 	char	*s;
430 	size_t	 slen;
431 
432 	va_start(ap, fmt);
433 	slen = xvasprintf(&s, fmt, ap);
434 	va_end(ap);
435 
436 	*len += slen;
437 	*buf = xrealloc(*buf, *len);
438 
439 	strlcat(*buf, s, *len);
440 	free(s);
441 }
442 
443 /* Add value to string. */
444 static void
445 args_print_add_value(char **buf, size_t *len, struct args_value *value)
446 {
447 	char	*expanded = NULL;
448 
449 	if (**buf != '\0')
450 		args_print_add(buf, len, " ");
451 
452 	switch (value->type) {
453 	case ARGS_NONE:
454 		break;
455 	case ARGS_COMMANDS:
456 		expanded = cmd_list_print(value->cmdlist, 0);
457 		args_print_add(buf, len, "{ %s }", expanded);
458 		break;
459 	case ARGS_STRING:
460 		expanded = args_escape(value->string);
461 		args_print_add(buf, len, "%s", expanded);
462 		break;
463 	}
464 	free(expanded);
465 }
466 
467 /* Print a set of arguments. */
468 char *
469 args_print(struct args *args)
470 {
471 	size_t			 len;
472 	char			*buf;
473 	u_int			 i, j;
474 	struct args_entry	*entry;
475 	struct args_value	*value;
476 
477 	len = 1;
478 	buf = xcalloc(1, len);
479 
480 	/* Process the flags first. */
481 	RB_FOREACH(entry, args_tree, &args->tree) {
482 		if (!TAILQ_EMPTY(&entry->values))
483 			continue;
484 
485 		if (*buf == '\0')
486 			args_print_add(&buf, &len, "-");
487 		for (j = 0; j < entry->count; j++)
488 			args_print_add(&buf, &len, "%c", entry->flag);
489 	}
490 
491 	/* Then the flags with arguments. */
492 	RB_FOREACH(entry, args_tree, &args->tree) {
493 		TAILQ_FOREACH(value, &entry->values, entry) {
494 			if (*buf != '\0')
495 				args_print_add(&buf, &len, " -%c", entry->flag);
496 			else
497 				args_print_add(&buf, &len, "-%c", entry->flag);
498 			args_print_add_value(&buf, &len, value);
499 		}
500 	}
501 
502 	/* And finally the argument vector. */
503 	for (i = 0; i < args->count; i++)
504 		args_print_add_value(&buf, &len, &args->values[i]);
505 
506 	return (buf);
507 }
508 
509 /* Escape an argument. */
510 char *
511 args_escape(const char *s)
512 {
513 	static const char	 dquoted[] = " #';${}%";
514 	static const char	 squoted[] = " \"";
515 	char			*escaped, *result;
516 	int			 flags, quotes = 0;
517 
518 	if (*s == '\0') {
519 		xasprintf(&result, "''");
520 		return (result);
521 	}
522 	if (s[strcspn(s, dquoted)] != '\0')
523 		quotes = '"';
524 	else if (s[strcspn(s, squoted)] != '\0')
525 		quotes = '\'';
526 
527 	if (s[0] != ' ' &&
528 	    s[1] == '\0' &&
529 	    (quotes != 0 || s[0] == '~')) {
530 		xasprintf(&escaped, "\\%c", s[0]);
531 		return (escaped);
532 	}
533 
534 	flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
535 	if (quotes == '"')
536 		flags |= VIS_DQ;
537 	utf8_stravis(&escaped, s, flags);
538 
539 	if (quotes == '\'')
540 		xasprintf(&result, "'%s'", escaped);
541 	else if (quotes == '"') {
542 		if (*escaped == '~')
543 			xasprintf(&result, "\"\\%s\"", escaped);
544 		else
545 			xasprintf(&result, "\"%s\"", escaped);
546 	} else {
547 		if (*escaped == '~')
548 			xasprintf(&result, "\\%s", escaped);
549 		else
550 			result = xstrdup(escaped);
551 	}
552 	free(escaped);
553 	return (result);
554 }
555 
556 /* Return if an argument is present. */
557 int
558 args_has(struct args *args, u_char flag)
559 {
560 	struct args_entry	*entry;
561 
562 	entry = args_find(args, flag);
563 	if (entry == NULL)
564 		return (0);
565 	return (entry->count);
566 }
567 
568 /* Set argument value in the arguments tree. */
569 void
570 args_set(struct args *args, u_char flag, struct args_value *value)
571 {
572 	struct args_entry	*entry;
573 
574 	entry = args_find(args, flag);
575 	if (entry == NULL) {
576 		entry = xcalloc(1, sizeof *entry);
577 		entry->flag = flag;
578 		entry->count = 1;
579 		TAILQ_INIT(&entry->values);
580 		RB_INSERT(args_tree, &args->tree, entry);
581 	} else
582 		entry->count++;
583 	if (value != NULL && value->type != ARGS_NONE)
584 		TAILQ_INSERT_TAIL(&entry->values, value, entry);
585 }
586 
587 /* Get argument value. Will be NULL if it isn't present. */
588 const char *
589 args_get(struct args *args, u_char flag)
590 {
591 	struct args_entry	*entry;
592 
593 	if ((entry = args_find(args, flag)) == NULL)
594 		return (NULL);
595 	if (TAILQ_EMPTY(&entry->values))
596 		return (NULL);
597 	return (TAILQ_LAST(&entry->values, args_values)->string);
598 }
599 
600 /* Get first argument. */
601 u_char
602 args_first(struct args *args, struct args_entry **entry)
603 {
604 	*entry = RB_MIN(args_tree, &args->tree);
605 	if (*entry == NULL)
606 		return (0);
607 	return ((*entry)->flag);
608 }
609 
610 /* Get next argument. */
611 u_char
612 args_next(struct args_entry **entry)
613 {
614 	*entry = RB_NEXT(args_tree, &args->tree, *entry);
615 	if (*entry == NULL)
616 		return (0);
617 	return ((*entry)->flag);
618 }
619 
620 /* Get argument count. */
621 u_int
622 args_count(struct args *args)
623 {
624 	return (args->count);
625 }
626 
627 /* Get argument values. */
628 struct args_value *
629 args_values(struct args *args)
630 {
631 	return (args->values);
632 }
633 
634 /* Get argument value. */
635 struct args_value *
636 args_value(struct args *args, u_int idx)
637 {
638 	if (idx >= args->count)
639 		return (NULL);
640 	return (&args->values[idx]);
641 }
642 
643 /* Return argument as string. */
644 const char *
645 args_string(struct args *args, u_int idx)
646 {
647 	if (idx >= args->count)
648 		return (NULL);
649 	return (args_value_as_string(&args->values[idx]));
650 }
651 
652 /* Make a command now. */
653 struct cmd_list *
654 args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx)
655 {
656 	struct args_command_state	*state;
657 	char				*error;
658 	struct cmd_list			*cmdlist;
659 
660 	state = args_make_commands_prepare(self, item, idx, NULL, 0, 0);
661 	cmdlist = args_make_commands(state, 0, NULL, &error);
662 	if (cmdlist == NULL) {
663 		cmdq_error(item, "%s", error);
664 		free(error);
665 	}
666 	else
667 		cmdlist->references++;
668 	args_make_commands_free(state);
669 	return (cmdlist);
670 }
671 
672 /* Save bits to make a command later. */
673 struct args_command_state *
674 args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
675     const char *default_command, int wait, int expand)
676 {
677 	struct args			*args = cmd_get_args(self);
678 	struct cmd_find_state		*target = cmdq_get_target(item);
679 	struct client			*tc = cmdq_get_target_client(item);
680 	struct args_value		*value;
681 	struct args_command_state	*state;
682 	const char			*cmd;
683 
684 	state = xcalloc(1, sizeof *state);
685 
686 	if (idx < args->count) {
687 		value = &args->values[idx];
688 		if (value->type == ARGS_COMMANDS) {
689 			state->cmdlist = value->cmdlist;
690 			state->cmdlist->references++;
691 			return (state);
692 		}
693 		cmd = value->string;
694 	} else {
695 		if (default_command == NULL)
696 			fatalx("argument out of range");
697 		cmd = default_command;
698 	}
699 
700 
701 	if (expand)
702 		state->cmd = format_single_from_target(item, cmd);
703 	else
704 		state->cmd = xstrdup(cmd);
705 	log_debug("%s: %s", __func__, state->cmd);
706 
707 	if (wait)
708 		state->pi.item = item;
709 	cmd_get_source(self, &state->pi.file, &state->pi.line);
710 	state->pi.c = tc;
711 	if (state->pi.c != NULL)
712 		state->pi.c->references++;
713 	cmd_find_copy_state(&state->pi.fs, target);
714 
715 	return (state);
716 }
717 
718 /* Return argument as command. */
719 struct cmd_list *
720 args_make_commands(struct args_command_state *state, int argc, char **argv,
721     char **error)
722 {
723 	struct cmd_parse_result	*pr;
724 	char			*cmd, *new_cmd;
725 	int			 i;
726 
727 	if (state->cmdlist != NULL) {
728 		if (argc == 0)
729 			return (state->cmdlist);
730 		return (cmd_list_copy(state->cmdlist, argc, argv));
731 	}
732 
733 	cmd = xstrdup(state->cmd);
734 	for (i = 0; i < argc; i++) {
735 		new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
736 		log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
737 		free(cmd);
738 		cmd = new_cmd;
739 	}
740 	log_debug("%s: %s", __func__, cmd);
741 
742 	pr = cmd_parse_from_string(cmd, &state->pi);
743 	free(cmd);
744 	switch (pr->status) {
745 	case CMD_PARSE_ERROR:
746 		*error = pr->error;
747 		return (NULL);
748 	case CMD_PARSE_SUCCESS:
749 		return (pr->cmdlist);
750 	}
751 }
752 
753 /* Free commands state. */
754 void
755 args_make_commands_free(struct args_command_state *state)
756 {
757 	if (state->cmdlist != NULL)
758 		cmd_list_free(state->cmdlist);
759 	if (state->pi.c != NULL)
760 		server_client_unref(state->pi.c);
761 	free(state->cmd);
762 	free(state);
763 }
764 
765 /* Get prepared command. */
766 char *
767 args_make_commands_get_command(struct args_command_state *state)
768 {
769 	struct cmd	*first;
770 	int		 n;
771 	char		*s;
772 
773 	if (state->cmdlist != NULL) {
774 		first = cmd_list_first(state->cmdlist);
775 		if (first == NULL)
776 			return (xstrdup(""));
777 		return (xstrdup(cmd_get_entry(first)->name));
778 	}
779 	n = strcspn(state->cmd, " ,");
780 	xasprintf(&s, "%.*s", n, state->cmd);
781 	return (s);
782 }
783 
784 /* Get first value in argument. */
785 struct args_value *
786 args_first_value(struct args *args, u_char flag)
787 {
788 	struct args_entry	*entry;
789 
790 	if ((entry = args_find(args, flag)) == NULL)
791 		return (NULL);
792 	return (TAILQ_FIRST(&entry->values));
793 }
794 
795 /* Get next value in argument. */
796 struct args_value *
797 args_next_value(struct args_value *value)
798 {
799 	return (TAILQ_NEXT(value, entry));
800 }
801 
802 /* Convert an argument value to a number. */
803 long long
804 args_strtonum(struct args *args, u_char flag, long long minval,
805     long long maxval, char **cause)
806 {
807 	const char		*errstr;
808 	long long		 ll;
809 	struct args_entry	*entry;
810 	struct args_value	*value;
811 
812 	if ((entry = args_find(args, flag)) == NULL) {
813 		*cause = xstrdup("missing");
814 		return (0);
815 	}
816 	value = TAILQ_LAST(&entry->values, args_values);
817 
818 	ll = strtonum(value->string, minval, maxval, &errstr);
819 	if (errstr != NULL) {
820 		*cause = xstrdup(errstr);
821 		return (0);
822 	}
823 
824 	*cause = NULL;
825 	return (ll);
826 }
827 
828 /* Convert an argument to a number which may be a percentage. */
829 long long
830 args_percentage(struct args *args, u_char flag, long long minval,
831     long long maxval, long long curval, char **cause)
832 {
833 	const char		*value;
834 	struct args_entry	*entry;
835 
836 	if ((entry = args_find(args, flag)) == NULL) {
837 		*cause = xstrdup("missing");
838 		return (0);
839 	}
840 	value = TAILQ_LAST(&entry->values, args_values)->string;
841 	return (args_string_percentage(value, minval, maxval, curval, cause));
842 }
843 
844 /* Convert a string to a number which may be a percentage. */
845 long long
846 args_string_percentage(const char *value, long long minval, long long maxval,
847     long long curval, char **cause)
848 {
849 	const char	*errstr;
850 	long long	 ll;
851 	size_t		 valuelen = strlen(value);
852 	char		*copy;
853 
854 	if (value[valuelen - 1] == '%') {
855 		copy = xstrdup(value);
856 		copy[valuelen - 1] = '\0';
857 
858 		ll = strtonum(copy, 0, 100, &errstr);
859 		free(copy);
860 		if (errstr != NULL) {
861 			*cause = xstrdup(errstr);
862 			return (0);
863 		}
864 		ll = (curval * ll) / 100;
865 		if (ll < minval) {
866 			*cause = xstrdup("too small");
867 			return (0);
868 		}
869 		if (ll > maxval) {
870 			*cause = xstrdup("too large");
871 			return (0);
872 		}
873 	} else {
874 		ll = strtonum(value, minval, maxval, &errstr);
875 		if (errstr != NULL) {
876 			*cause = xstrdup(errstr);
877 			return (0);
878 		}
879 	}
880 
881 	*cause = NULL;
882 	return (ll);
883 }
884