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