1 /* $OpenBSD: arguments.c,v 1.51 2021/09/09 21:55: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 (flag == '?') { 157 args_free(args); 158 return (NULL); 159 } 160 if (!isalnum(flag)) { 161 xasprintf(cause, "invalid flag -%c", flag); 162 args_free(args); 163 return (NULL); 164 } 165 found = strchr(parse->template, flag); 166 if (found == NULL) { 167 xasprintf(cause, "unknown flag -%c", flag); 168 args_free(args); 169 return (NULL); 170 } 171 argument = *++found; 172 if (argument != ':') { 173 log_debug("%s: -%c", __func__, flag); 174 args_set(args, flag, NULL); 175 continue; 176 } 177 new = xcalloc(1, sizeof *new); 178 if (*string != '\0') { 179 new->type = ARGS_STRING; 180 new->string = xstrdup(string); 181 } else { 182 if (i == count) { 183 xasprintf(cause, 184 "-%c expects an argument", 185 flag); 186 args_free(args); 187 return (NULL); 188 } 189 if (values[i].type != ARGS_STRING) { 190 xasprintf(cause, 191 "-%c argument must be a string", 192 flag); 193 args_free(args); 194 return (NULL); 195 } 196 args_copy_value(new, &values[i++]); 197 } 198 s = args_value_as_string(new); 199 log_debug("%s: -%c = %s", __func__, flag, s); 200 args_set(args, flag, new); 201 break; 202 } 203 } 204 log_debug("%s: flags end at %u of %u", __func__, i, count); 205 if (i != count) { 206 for (/* nothing */; i < count; i++) { 207 value = &values[i]; 208 209 s = args_value_as_string(value); 210 log_debug("%s: %u = %s (type %d)", __func__, i, s, 211 value->type); 212 213 if (parse->cb != NULL) { 214 type = parse->cb(args, args->count, cause); 215 if (type == ARGS_PARSE_INVALID) { 216 args_free(args); 217 return (NULL); 218 } 219 } else 220 type = ARGS_PARSE_STRING; 221 222 args->values = xrecallocarray(args->values, 223 args->count, args->count + 1, sizeof *args->values); 224 new = &args->values[args->count++]; 225 226 switch (type) { 227 case ARGS_PARSE_INVALID: 228 fatalx("unexpected argument type"); 229 case ARGS_PARSE_STRING: 230 if (value->type != ARGS_STRING) { 231 xasprintf(cause, 232 "argument %u must be \"string\"", 233 args->count); 234 args_free(args); 235 return (NULL); 236 } 237 args_copy_value(new, value); 238 break; 239 case ARGS_PARSE_COMMANDS_OR_STRING: 240 args_copy_value(new, value); 241 break; 242 case ARGS_PARSE_COMMANDS: 243 if (value->type != ARGS_COMMANDS) { 244 xasprintf(cause, 245 "argument %u must be { commands }", 246 args->count); 247 args_free(args); 248 return (NULL); 249 } 250 args_copy_value(new, value); 251 break; 252 } 253 } 254 } 255 256 if (parse->lower != -1 && args->count < (u_int)parse->lower) { 257 xasprintf(cause, 258 "too few arguments (need at least %u)", 259 parse->lower); 260 args_free(args); 261 return (NULL); 262 } 263 if (parse->upper != -1 && args->count > (u_int)parse->upper) { 264 xasprintf(cause, 265 "too many arguments (need at most %u)", 266 parse->upper); 267 args_free(args); 268 return (NULL); 269 } 270 return (args); 271 } 272 273 /* Copy and expand a value. */ 274 static void 275 args_copy_copy_value(struct args_value *to, struct args_value *from, int argc, 276 char **argv) 277 { 278 char *s, *expanded; 279 int i; 280 281 to->type = from->type; 282 switch (from->type) { 283 case ARGS_NONE: 284 break; 285 case ARGS_STRING: 286 expanded = xstrdup(from->string); 287 for (i = 0; i < argc; i++) { 288 s = cmd_template_replace(expanded, argv[i], i + 1); 289 free(expanded); 290 expanded = s; 291 } 292 to->string = expanded; 293 break; 294 case ARGS_COMMANDS: 295 to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv); 296 break; 297 } 298 } 299 300 /* Copy an arguments set. */ 301 struct args * 302 args_copy(struct args *args, int argc, char **argv) 303 { 304 struct args *new_args; 305 struct args_entry *entry; 306 struct args_value *value, *new_value; 307 u_int i; 308 309 cmd_log_argv(argc, argv, "%s", __func__); 310 311 new_args = args_create(); 312 RB_FOREACH(entry, args_tree, &args->tree) { 313 if (TAILQ_EMPTY(&entry->values)) { 314 for (i = 0; i < entry->count; i++) 315 args_set(new_args, entry->flag, NULL); 316 continue; 317 } 318 TAILQ_FOREACH(value, &entry->values, entry) { 319 new_value = xcalloc(1, sizeof *new_value); 320 args_copy_copy_value(new_value, value, argc, argv); 321 args_set(new_args, entry->flag, new_value); 322 } 323 } 324 if (args->count == 0) 325 return (new_args); 326 new_args->count = args->count; 327 new_args->values = xcalloc(args->count, sizeof *new_args->values); 328 for (i = 0; i < args->count; i++) { 329 new_value = &new_args->values[i]; 330 args_copy_copy_value(new_value, &args->values[i], argc, argv); 331 } 332 return (new_args); 333 } 334 335 /* Free a value. */ 336 void 337 args_free_value(struct args_value *value) 338 { 339 switch (value->type) { 340 case ARGS_NONE: 341 break; 342 case ARGS_STRING: 343 free(value->string); 344 break; 345 case ARGS_COMMANDS: 346 cmd_list_free(value->cmdlist); 347 break; 348 } 349 free(value->cached); 350 } 351 352 /* Free values. */ 353 void 354 args_free_values(struct args_value *values, u_int count) 355 { 356 u_int i; 357 358 for (i = 0; i < count; i++) 359 args_free_value(&values[i]); 360 } 361 362 /* Free an arguments set. */ 363 void 364 args_free(struct args *args) 365 { 366 struct args_entry *entry; 367 struct args_entry *entry1; 368 struct args_value *value; 369 struct args_value *value1; 370 371 args_free_values(args->values, args->count); 372 free(args->values); 373 374 RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) { 375 RB_REMOVE(args_tree, &args->tree, entry); 376 TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) { 377 TAILQ_REMOVE(&entry->values, value, entry); 378 args_free_value(value); 379 free(value); 380 } 381 free(entry); 382 } 383 384 free(args); 385 } 386 387 /* Convert arguments to vector. */ 388 void 389 args_to_vector(struct args *args, int *argc, char ***argv) 390 { 391 char *s; 392 u_int i; 393 394 *argc = 0; 395 *argv = NULL; 396 397 for (i = 0; i < args->count; i++) { 398 switch (args->values[i].type) { 399 case ARGS_NONE: 400 break; 401 case ARGS_STRING: 402 cmd_append_argv(argc, argv, args->values[i].string); 403 break; 404 case ARGS_COMMANDS: 405 s = cmd_list_print(args->values[i].cmdlist, 0); 406 cmd_append_argv(argc, argv, s); 407 free(s); 408 break; 409 } 410 } 411 } 412 413 /* Convert arguments from vector. */ 414 struct args_value * 415 args_from_vector(int argc, char **argv) 416 { 417 struct args_value *values; 418 int i; 419 420 values = xcalloc(argc, sizeof *values); 421 for (i = 0; i < argc; i++) { 422 values[i].type = ARGS_STRING; 423 values[i].string = xstrdup(argv[i]); 424 } 425 return (values); 426 } 427 428 /* Add to string. */ 429 static void printflike(3, 4) 430 args_print_add(char **buf, size_t *len, const char *fmt, ...) 431 { 432 va_list ap; 433 char *s; 434 size_t slen; 435 436 va_start(ap, fmt); 437 slen = xvasprintf(&s, fmt, ap); 438 va_end(ap); 439 440 *len += slen; 441 *buf = xrealloc(*buf, *len); 442 443 strlcat(*buf, s, *len); 444 free(s); 445 } 446 447 /* Add value to string. */ 448 static void 449 args_print_add_value(char **buf, size_t *len, struct args_value *value) 450 { 451 char *expanded = NULL; 452 453 if (**buf != '\0') 454 args_print_add(buf, len, " "); 455 456 switch (value->type) { 457 case ARGS_NONE: 458 break; 459 case ARGS_COMMANDS: 460 expanded = cmd_list_print(value->cmdlist, 0); 461 args_print_add(buf, len, "{ %s }", expanded); 462 break; 463 case ARGS_STRING: 464 expanded = args_escape(value->string); 465 args_print_add(buf, len, "%s", expanded); 466 break; 467 } 468 free(expanded); 469 } 470 471 /* Print a set of arguments. */ 472 char * 473 args_print(struct args *args) 474 { 475 size_t len; 476 char *buf; 477 u_int i, j; 478 struct args_entry *entry; 479 struct args_value *value; 480 481 len = 1; 482 buf = xcalloc(1, len); 483 484 /* Process the flags first. */ 485 RB_FOREACH(entry, args_tree, &args->tree) { 486 if (!TAILQ_EMPTY(&entry->values)) 487 continue; 488 489 if (*buf == '\0') 490 args_print_add(&buf, &len, "-"); 491 for (j = 0; j < entry->count; j++) 492 args_print_add(&buf, &len, "%c", entry->flag); 493 } 494 495 /* Then the flags with arguments. */ 496 RB_FOREACH(entry, args_tree, &args->tree) { 497 TAILQ_FOREACH(value, &entry->values, entry) { 498 if (*buf != '\0') 499 args_print_add(&buf, &len, " -%c", entry->flag); 500 else 501 args_print_add(&buf, &len, "-%c", entry->flag); 502 args_print_add_value(&buf, &len, value); 503 } 504 } 505 506 /* And finally the argument vector. */ 507 for (i = 0; i < args->count; i++) 508 args_print_add_value(&buf, &len, &args->values[i]); 509 510 return (buf); 511 } 512 513 /* Escape an argument. */ 514 char * 515 args_escape(const char *s) 516 { 517 static const char dquoted[] = " #';${}%"; 518 static const char squoted[] = " \""; 519 char *escaped, *result; 520 int flags, quotes = 0; 521 522 if (*s == '\0') { 523 xasprintf(&result, "''"); 524 return (result); 525 } 526 if (s[strcspn(s, dquoted)] != '\0') 527 quotes = '"'; 528 else if (s[strcspn(s, squoted)] != '\0') 529 quotes = '\''; 530 531 if (s[0] != ' ' && 532 s[1] == '\0' && 533 (quotes != 0 || s[0] == '~')) { 534 xasprintf(&escaped, "\\%c", s[0]); 535 return (escaped); 536 } 537 538 flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; 539 if (quotes == '"') 540 flags |= VIS_DQ; 541 utf8_stravis(&escaped, s, flags); 542 543 if (quotes == '\'') 544 xasprintf(&result, "'%s'", escaped); 545 else if (quotes == '"') { 546 if (*escaped == '~') 547 xasprintf(&result, "\"\\%s\"", escaped); 548 else 549 xasprintf(&result, "\"%s\"", escaped); 550 } else { 551 if (*escaped == '~') 552 xasprintf(&result, "\\%s", escaped); 553 else 554 result = xstrdup(escaped); 555 } 556 free(escaped); 557 return (result); 558 } 559 560 /* Return if an argument is present. */ 561 int 562 args_has(struct args *args, u_char flag) 563 { 564 struct args_entry *entry; 565 566 entry = args_find(args, flag); 567 if (entry == NULL) 568 return (0); 569 return (entry->count); 570 } 571 572 /* Set argument value in the arguments tree. */ 573 void 574 args_set(struct args *args, u_char flag, struct args_value *value) 575 { 576 struct args_entry *entry; 577 578 entry = args_find(args, flag); 579 if (entry == NULL) { 580 entry = xcalloc(1, sizeof *entry); 581 entry->flag = flag; 582 entry->count = 1; 583 TAILQ_INIT(&entry->values); 584 RB_INSERT(args_tree, &args->tree, entry); 585 } else 586 entry->count++; 587 if (value != NULL && value->type != ARGS_NONE) 588 TAILQ_INSERT_TAIL(&entry->values, value, entry); 589 } 590 591 /* Get argument value. Will be NULL if it isn't present. */ 592 const char * 593 args_get(struct args *args, u_char flag) 594 { 595 struct args_entry *entry; 596 597 if ((entry = args_find(args, flag)) == NULL) 598 return (NULL); 599 if (TAILQ_EMPTY(&entry->values)) 600 return (NULL); 601 return (TAILQ_LAST(&entry->values, args_values)->string); 602 } 603 604 /* Get first argument. */ 605 u_char 606 args_first(struct args *args, struct args_entry **entry) 607 { 608 *entry = RB_MIN(args_tree, &args->tree); 609 if (*entry == NULL) 610 return (0); 611 return ((*entry)->flag); 612 } 613 614 /* Get next argument. */ 615 u_char 616 args_next(struct args_entry **entry) 617 { 618 *entry = RB_NEXT(args_tree, &args->tree, *entry); 619 if (*entry == NULL) 620 return (0); 621 return ((*entry)->flag); 622 } 623 624 /* Get argument count. */ 625 u_int 626 args_count(struct args *args) 627 { 628 return (args->count); 629 } 630 631 /* Get argument values. */ 632 struct args_value * 633 args_values(struct args *args) 634 { 635 return (args->values); 636 } 637 638 /* Get argument value. */ 639 struct args_value * 640 args_value(struct args *args, u_int idx) 641 { 642 if (idx >= args->count) 643 return (NULL); 644 return (&args->values[idx]); 645 } 646 647 /* Return argument as string. */ 648 const char * 649 args_string(struct args *args, u_int idx) 650 { 651 if (idx >= args->count) 652 return (NULL); 653 return (args_value_as_string(&args->values[idx])); 654 } 655 656 /* Make a command now. */ 657 struct cmd_list * 658 args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx, 659 int expand) 660 { 661 struct args_command_state *state; 662 char *error; 663 struct cmd_list *cmdlist; 664 665 state = args_make_commands_prepare(self, item, idx, NULL, 0, expand); 666 cmdlist = args_make_commands(state, 0, NULL, &error); 667 if (cmdlist == NULL) { 668 cmdq_error(item, "%s", error); 669 free(error); 670 } 671 else 672 cmdlist->references++; 673 args_make_commands_free(state); 674 return (cmdlist); 675 } 676 677 /* Save bits to make a command later. */ 678 struct args_command_state * 679 args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx, 680 const char *default_command, int wait, int expand) 681 { 682 struct args *args = cmd_get_args(self); 683 struct cmd_find_state *target = cmdq_get_target(item); 684 struct client *tc = cmdq_get_target_client(item); 685 struct args_value *value; 686 struct args_command_state *state; 687 const char *cmd; 688 689 state = xcalloc(1, sizeof *state); 690 691 if (idx < args->count) { 692 value = &args->values[idx]; 693 if (value->type == ARGS_COMMANDS) { 694 state->cmdlist = value->cmdlist; 695 state->cmdlist->references++; 696 return (state); 697 } 698 cmd = value->string; 699 } else { 700 if (default_command == NULL) 701 fatalx("argument out of range"); 702 cmd = default_command; 703 } 704 705 706 if (expand) 707 state->cmd = format_single_from_target(item, cmd); 708 else 709 state->cmd = xstrdup(cmd); 710 log_debug("%s: %s", __func__, state->cmd); 711 712 if (wait) 713 state->pi.item = item; 714 cmd_get_source(self, &state->pi.file, &state->pi.line); 715 state->pi.c = tc; 716 if (state->pi.c != NULL) 717 state->pi.c->references++; 718 cmd_find_copy_state(&state->pi.fs, target); 719 720 return (state); 721 } 722 723 /* Return argument as command. */ 724 struct cmd_list * 725 args_make_commands(struct args_command_state *state, int argc, char **argv, 726 char **error) 727 { 728 struct cmd_parse_result *pr; 729 char *cmd, *new_cmd; 730 int i; 731 732 if (state->cmdlist != NULL) { 733 if (argc == 0) 734 return (state->cmdlist); 735 return (cmd_list_copy(state->cmdlist, argc, argv)); 736 } 737 738 cmd = xstrdup(state->cmd); 739 for (i = 0; i < argc; i++) { 740 new_cmd = cmd_template_replace(cmd, argv[i], i + 1); 741 log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd); 742 free(cmd); 743 cmd = new_cmd; 744 } 745 log_debug("%s: %s", __func__, cmd); 746 747 pr = cmd_parse_from_string(cmd, &state->pi); 748 free(cmd); 749 switch (pr->status) { 750 case CMD_PARSE_ERROR: 751 *error = pr->error; 752 return (NULL); 753 case CMD_PARSE_SUCCESS: 754 return (pr->cmdlist); 755 } 756 } 757 758 /* Free commands state. */ 759 void 760 args_make_commands_free(struct args_command_state *state) 761 { 762 if (state->cmdlist != NULL) 763 cmd_list_free(state->cmdlist); 764 if (state->pi.c != NULL) 765 server_client_unref(state->pi.c); 766 free(state->cmd); 767 free(state); 768 } 769 770 /* Get prepared command. */ 771 char * 772 args_make_commands_get_command(struct args_command_state *state) 773 { 774 struct cmd *first; 775 int n; 776 char *s; 777 778 if (state->cmdlist != NULL) { 779 first = cmd_list_first(state->cmdlist); 780 if (first == NULL) 781 return (xstrdup("")); 782 return (xstrdup(cmd_get_entry(first)->name)); 783 } 784 n = strcspn(state->cmd, " ,"); 785 xasprintf(&s, "%.*s", n, state->cmd); 786 return (s); 787 } 788 789 /* Get first value in argument. */ 790 struct args_value * 791 args_first_value(struct args *args, u_char flag) 792 { 793 struct args_entry *entry; 794 795 if ((entry = args_find(args, flag)) == NULL) 796 return (NULL); 797 return (TAILQ_FIRST(&entry->values)); 798 } 799 800 /* Get next value in argument. */ 801 struct args_value * 802 args_next_value(struct args_value *value) 803 { 804 return (TAILQ_NEXT(value, entry)); 805 } 806 807 /* Convert an argument value to a number. */ 808 long long 809 args_strtonum(struct args *args, u_char flag, long long minval, 810 long long maxval, char **cause) 811 { 812 const char *errstr; 813 long long ll; 814 struct args_entry *entry; 815 struct args_value *value; 816 817 if ((entry = args_find(args, flag)) == NULL) { 818 *cause = xstrdup("missing"); 819 return (0); 820 } 821 value = TAILQ_LAST(&entry->values, args_values); 822 823 ll = strtonum(value->string, minval, maxval, &errstr); 824 if (errstr != NULL) { 825 *cause = xstrdup(errstr); 826 return (0); 827 } 828 829 *cause = NULL; 830 return (ll); 831 } 832 833 /* Convert an argument to a number which may be a percentage. */ 834 long long 835 args_percentage(struct args *args, u_char flag, long long minval, 836 long long maxval, long long curval, char **cause) 837 { 838 const char *value; 839 struct args_entry *entry; 840 841 if ((entry = args_find(args, flag)) == NULL) { 842 *cause = xstrdup("missing"); 843 return (0); 844 } 845 value = TAILQ_LAST(&entry->values, args_values)->string; 846 return (args_string_percentage(value, minval, maxval, curval, cause)); 847 } 848 849 /* Convert a string to a number which may be a percentage. */ 850 long long 851 args_string_percentage(const char *value, long long minval, long long maxval, 852 long long curval, char **cause) 853 { 854 const char *errstr; 855 long long ll; 856 size_t valuelen = strlen(value); 857 char *copy; 858 859 if (value[valuelen - 1] == '%') { 860 copy = xstrdup(value); 861 copy[valuelen - 1] = '\0'; 862 863 ll = strtonum(copy, 0, 100, &errstr); 864 free(copy); 865 if (errstr != NULL) { 866 *cause = xstrdup(errstr); 867 return (0); 868 } 869 ll = (curval * ll) / 100; 870 if (ll < minval) { 871 *cause = xstrdup("too small"); 872 return (0); 873 } 874 if (ll > maxval) { 875 *cause = xstrdup("too large"); 876 return (0); 877 } 878 } else { 879 ll = strtonum(value, minval, maxval, &errstr); 880 if (errstr != NULL) { 881 *cause = xstrdup(errstr); 882 return (0); 883 } 884 } 885 886 *cause = NULL; 887 return (ll); 888 } 889