xref: /openbsd-src/usr.bin/tmux/arguments.c (revision a2d98599e3c69439489c0bfafa67f0ebef23661b)
1 /* $OpenBSD: arguments.c,v 1.50 2021/09/09 13:38:32 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     int expand)
656 {
657 	struct args_command_state	*state;
658 	char				*error;
659 	struct cmd_list			*cmdlist;
660 
661 	state = args_make_commands_prepare(self, item, idx, NULL, 0, expand);
662 	cmdlist = args_make_commands(state, 0, NULL, &error);
663 	if (cmdlist == NULL) {
664 		cmdq_error(item, "%s", error);
665 		free(error);
666 	}
667 	else
668 		cmdlist->references++;
669 	args_make_commands_free(state);
670 	return (cmdlist);
671 }
672 
673 /* Save bits to make a command later. */
674 struct args_command_state *
675 args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx,
676     const char *default_command, int wait, int expand)
677 {
678 	struct args			*args = cmd_get_args(self);
679 	struct cmd_find_state		*target = cmdq_get_target(item);
680 	struct client			*tc = cmdq_get_target_client(item);
681 	struct args_value		*value;
682 	struct args_command_state	*state;
683 	const char			*cmd;
684 
685 	state = xcalloc(1, sizeof *state);
686 
687 	if (idx < args->count) {
688 		value = &args->values[idx];
689 		if (value->type == ARGS_COMMANDS) {
690 			state->cmdlist = value->cmdlist;
691 			state->cmdlist->references++;
692 			return (state);
693 		}
694 		cmd = value->string;
695 	} else {
696 		if (default_command == NULL)
697 			fatalx("argument out of range");
698 		cmd = default_command;
699 	}
700 
701 
702 	if (expand)
703 		state->cmd = format_single_from_target(item, cmd);
704 	else
705 		state->cmd = xstrdup(cmd);
706 	log_debug("%s: %s", __func__, state->cmd);
707 
708 	if (wait)
709 		state->pi.item = item;
710 	cmd_get_source(self, &state->pi.file, &state->pi.line);
711 	state->pi.c = tc;
712 	if (state->pi.c != NULL)
713 		state->pi.c->references++;
714 	cmd_find_copy_state(&state->pi.fs, target);
715 
716 	return (state);
717 }
718 
719 /* Return argument as command. */
720 struct cmd_list *
721 args_make_commands(struct args_command_state *state, int argc, char **argv,
722     char **error)
723 {
724 	struct cmd_parse_result	*pr;
725 	char			*cmd, *new_cmd;
726 	int			 i;
727 
728 	if (state->cmdlist != NULL) {
729 		if (argc == 0)
730 			return (state->cmdlist);
731 		return (cmd_list_copy(state->cmdlist, argc, argv));
732 	}
733 
734 	cmd = xstrdup(state->cmd);
735 	for (i = 0; i < argc; i++) {
736 		new_cmd = cmd_template_replace(cmd, argv[i], i + 1);
737 		log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd);
738 		free(cmd);
739 		cmd = new_cmd;
740 	}
741 	log_debug("%s: %s", __func__, cmd);
742 
743 	pr = cmd_parse_from_string(cmd, &state->pi);
744 	free(cmd);
745 	switch (pr->status) {
746 	case CMD_PARSE_ERROR:
747 		*error = pr->error;
748 		return (NULL);
749 	case CMD_PARSE_SUCCESS:
750 		return (pr->cmdlist);
751 	}
752 }
753 
754 /* Free commands state. */
755 void
756 args_make_commands_free(struct args_command_state *state)
757 {
758 	if (state->cmdlist != NULL)
759 		cmd_list_free(state->cmdlist);
760 	if (state->pi.c != NULL)
761 		server_client_unref(state->pi.c);
762 	free(state->cmd);
763 	free(state);
764 }
765 
766 /* Get prepared command. */
767 char *
768 args_make_commands_get_command(struct args_command_state *state)
769 {
770 	struct cmd	*first;
771 	int		 n;
772 	char		*s;
773 
774 	if (state->cmdlist != NULL) {
775 		first = cmd_list_first(state->cmdlist);
776 		if (first == NULL)
777 			return (xstrdup(""));
778 		return (xstrdup(cmd_get_entry(first)->name));
779 	}
780 	n = strcspn(state->cmd, " ,");
781 	xasprintf(&s, "%.*s", n, state->cmd);
782 	return (s);
783 }
784 
785 /* Get first value in argument. */
786 struct args_value *
787 args_first_value(struct args *args, u_char flag)
788 {
789 	struct args_entry	*entry;
790 
791 	if ((entry = args_find(args, flag)) == NULL)
792 		return (NULL);
793 	return (TAILQ_FIRST(&entry->values));
794 }
795 
796 /* Get next value in argument. */
797 struct args_value *
798 args_next_value(struct args_value *value)
799 {
800 	return (TAILQ_NEXT(value, entry));
801 }
802 
803 /* Convert an argument value to a number. */
804 long long
805 args_strtonum(struct args *args, u_char flag, long long minval,
806     long long maxval, char **cause)
807 {
808 	const char		*errstr;
809 	long long		 ll;
810 	struct args_entry	*entry;
811 	struct args_value	*value;
812 
813 	if ((entry = args_find(args, flag)) == NULL) {
814 		*cause = xstrdup("missing");
815 		return (0);
816 	}
817 	value = TAILQ_LAST(&entry->values, args_values);
818 
819 	ll = strtonum(value->string, minval, maxval, &errstr);
820 	if (errstr != NULL) {
821 		*cause = xstrdup(errstr);
822 		return (0);
823 	}
824 
825 	*cause = NULL;
826 	return (ll);
827 }
828 
829 /* Convert an argument to a number which may be a percentage. */
830 long long
831 args_percentage(struct args *args, u_char flag, long long minval,
832     long long maxval, long long curval, char **cause)
833 {
834 	const char		*value;
835 	struct args_entry	*entry;
836 
837 	if ((entry = args_find(args, flag)) == NULL) {
838 		*cause = xstrdup("missing");
839 		return (0);
840 	}
841 	value = TAILQ_LAST(&entry->values, args_values)->string;
842 	return (args_string_percentage(value, minval, maxval, curval, cause));
843 }
844 
845 /* Convert a string to a number which may be a percentage. */
846 long long
847 args_string_percentage(const char *value, long long minval, long long maxval,
848     long long curval, char **cause)
849 {
850 	const char	*errstr;
851 	long long	 ll;
852 	size_t		 valuelen = strlen(value);
853 	char		*copy;
854 
855 	if (value[valuelen - 1] == '%') {
856 		copy = xstrdup(value);
857 		copy[valuelen - 1] = '\0';
858 
859 		ll = strtonum(copy, 0, 100, &errstr);
860 		free(copy);
861 		if (errstr != NULL) {
862 			*cause = xstrdup(errstr);
863 			return (0);
864 		}
865 		ll = (curval * ll) / 100;
866 		if (ll < minval) {
867 			*cause = xstrdup("too small");
868 			return (0);
869 		}
870 		if (ll > maxval) {
871 			*cause = xstrdup("too large");
872 			return (0);
873 		}
874 	} else {
875 		ll = strtonum(value, minval, maxval, &errstr);
876 		if (errstr != NULL) {
877 			*cause = xstrdup(errstr);
878 			return (0);
879 		}
880 	}
881 
882 	*cause = NULL;
883 	return (ll);
884 }
885