/* $OpenBSD: arguments.c,v 1.63 2024/04/15 08:19:55 nicm Exp $ */ /* * Copyright (c) 2010 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "tmux.h" /* * Manipulate command arguments. */ /* List of argument values. */ TAILQ_HEAD(args_values, args_value); /* Single arguments flag. */ struct args_entry { u_char flag; struct args_values values; u_int count; int flags; #define ARGS_ENTRY_OPTIONAL_VALUE 0x1 RB_ENTRY(args_entry) entry; }; /* Parsed argument flags and values. */ struct args { struct args_tree tree; u_int count; struct args_value *values; }; /* Prepared command state. */ struct args_command_state { struct cmd_list *cmdlist; char *cmd; struct cmd_parse_input pi; }; static struct args_entry *args_find(struct args *, u_char); static int args_cmp(struct args_entry *, struct args_entry *); RB_GENERATE_STATIC(args_tree, args_entry, entry, args_cmp); /* Arguments tree comparison function. */ static int args_cmp(struct args_entry *a1, struct args_entry *a2) { return (a1->flag - a2->flag); } /* Find a flag in the arguments tree. */ static struct args_entry * args_find(struct args *args, u_char flag) { struct args_entry entry; entry.flag = flag; return (RB_FIND(args_tree, &args->tree, &entry)); } /* Copy value. */ static void args_copy_value(struct args_value *to, struct args_value *from) { to->type = from->type; switch (from->type) { case ARGS_NONE: break; case ARGS_COMMANDS: to->cmdlist = from->cmdlist; to->cmdlist->references++; break; case ARGS_STRING: to->string = xstrdup(from->string); break; } } /* Type to string. */ static const char * args_type_to_string (enum args_type type) { switch (type) { case ARGS_NONE: return "NONE"; case ARGS_STRING: return "STRING"; case ARGS_COMMANDS: return "COMMANDS"; } return "INVALID"; } /* Get value as string. */ static const char * args_value_as_string(struct args_value *value) { switch (value->type) { case ARGS_NONE: return (""); case ARGS_COMMANDS: if (value->cached == NULL) value->cached = cmd_list_print(value->cmdlist, 0); return (value->cached); case ARGS_STRING: return (value->string); } fatalx("unexpected argument type"); } /* Create an empty arguments set. */ struct args * args_create(void) { struct args *args; args = xcalloc(1, sizeof *args); RB_INIT(&args->tree); return (args); } /* Parse a single flag. */ static int args_parse_flag_argument(struct args_value *values, u_int count, char **cause, struct args *args, u_int *i, const char *string, int flag, int optional_argument) { struct args_value *argument, *new; const char *s; new = xcalloc(1, sizeof *new); if (*string != '\0') { new->type = ARGS_STRING; new->string = xstrdup(string); goto out; } if (*i == count) argument = NULL; else { argument = &values[*i]; if (argument->type != ARGS_STRING) { xasprintf(cause, "-%c argument must be a string", flag); return (-1); } } if (argument == NULL) { if (optional_argument) { log_debug("%s: -%c (optional)", __func__, flag); args_set(args, flag, NULL, ARGS_ENTRY_OPTIONAL_VALUE); args_free_value(new); free(new); return (0); /* either - or end */ } xasprintf(cause, "-%c expects an argument", flag); return (-1); } args_copy_value(new, argument); (*i)++; out: s = args_value_as_string(new); log_debug("%s: -%c = %s", __func__, flag, s); args_set(args, flag, new, 0); return (0); } /* Parse flags argument. */ static int args_parse_flags(const struct args_parse *parse, struct args_value *values, u_int count, char **cause, struct args *args, u_int *i) { struct args_value *value; u_char flag; const char *found, *string; int optional_argument; value = &values[*i]; if (value->type != ARGS_STRING) return (1); string = value->string; log_debug("%s: next %s", __func__, string); if (*string++ != '-' || *string == '\0') return (1); (*i)++; if (string[0] == '-' && string[1] == '\0') return (1); for (;;) { flag = *string++; if (flag == '\0') return (0); if (flag == '?') return (-1); if (!isalnum(flag)) { xasprintf(cause, "invalid flag -%c", flag); return (-1); } found = strchr(parse->template, flag); if (found == NULL) { xasprintf(cause, "unknown flag -%c", flag); return (-1); } if (found[1] != ':') { log_debug("%s: -%c", __func__, flag); args_set(args, flag, NULL, 0); continue; } optional_argument = (found[2] == ':'); return (args_parse_flag_argument(values, count, cause, args, i, string, flag, optional_argument)); } } /* Parse arguments into a new argument set. */ struct args * args_parse(const struct args_parse *parse, struct args_value *values, u_int count, char **cause) { struct args *args; u_int i; enum args_parse_type type; struct args_value *value, *new; const char *s; int stop; if (count == 0) return (args_create()); args = args_create(); for (i = 1; i < count; /* nothing */) { stop = args_parse_flags(parse, values, count, cause, args, &i); if (stop == -1) { args_free(args); return (NULL); } if (stop == 1) break; } log_debug("%s: flags end at %u of %u", __func__, i, count); if (i != count) { for (/* nothing */; i < count; i++) { value = &values[i]; s = args_value_as_string(value); log_debug("%s: %u = %s (type %s)", __func__, i, s, args_type_to_string (value->type)); if (parse->cb != NULL) { type = parse->cb(args, args->count, cause); if (type == ARGS_PARSE_INVALID) { args_free(args); return (NULL); } } else type = ARGS_PARSE_STRING; args->values = xrecallocarray(args->values, args->count, args->count + 1, sizeof *args->values); new = &args->values[args->count++]; switch (type) { case ARGS_PARSE_INVALID: fatalx("unexpected argument type"); case ARGS_PARSE_STRING: if (value->type != ARGS_STRING) { xasprintf(cause, "argument %u must be \"string\"", args->count); args_free(args); return (NULL); } args_copy_value(new, value); break; case ARGS_PARSE_COMMANDS_OR_STRING: args_copy_value(new, value); break; case ARGS_PARSE_COMMANDS: if (value->type != ARGS_COMMANDS) { xasprintf(cause, "argument %u must be { commands }", args->count); args_free(args); return (NULL); } args_copy_value(new, value); break; } } } if (parse->lower != -1 && args->count < (u_int)parse->lower) { xasprintf(cause, "too few arguments (need at least %u)", parse->lower); args_free(args); return (NULL); } if (parse->upper != -1 && args->count > (u_int)parse->upper) { xasprintf(cause, "too many arguments (need at most %u)", parse->upper); args_free(args); return (NULL); } return (args); } /* Copy and expand a value. */ static void args_copy_copy_value(struct args_value *to, struct args_value *from, int argc, char **argv) { char *s, *expanded; int i; to->type = from->type; switch (from->type) { case ARGS_NONE: break; case ARGS_STRING: expanded = xstrdup(from->string); for (i = 0; i < argc; i++) { s = cmd_template_replace(expanded, argv[i], i + 1); free(expanded); expanded = s; } to->string = expanded; break; case ARGS_COMMANDS: to->cmdlist = cmd_list_copy(from->cmdlist, argc, argv); break; } } /* Copy an arguments set. */ struct args * args_copy(struct args *args, int argc, char **argv) { struct args *new_args; struct args_entry *entry; struct args_value *value, *new_value; u_int i; cmd_log_argv(argc, argv, "%s", __func__); new_args = args_create(); RB_FOREACH(entry, args_tree, &args->tree) { if (TAILQ_EMPTY(&entry->values)) { for (i = 0; i < entry->count; i++) args_set(new_args, entry->flag, NULL, 0); continue; } TAILQ_FOREACH(value, &entry->values, entry) { new_value = xcalloc(1, sizeof *new_value); args_copy_copy_value(new_value, value, argc, argv); args_set(new_args, entry->flag, new_value, 0); } } if (args->count == 0) return (new_args); new_args->count = args->count; new_args->values = xcalloc(args->count, sizeof *new_args->values); for (i = 0; i < args->count; i++) { new_value = &new_args->values[i]; args_copy_copy_value(new_value, &args->values[i], argc, argv); } return (new_args); } /* Free a value. */ void args_free_value(struct args_value *value) { switch (value->type) { case ARGS_NONE: break; case ARGS_STRING: free(value->string); break; case ARGS_COMMANDS: cmd_list_free(value->cmdlist); break; } free(value->cached); } /* Free values. */ void args_free_values(struct args_value *values, u_int count) { u_int i; for (i = 0; i < count; i++) args_free_value(&values[i]); } /* Free an arguments set. */ void args_free(struct args *args) { struct args_entry *entry; struct args_entry *entry1; struct args_value *value; struct args_value *value1; args_free_values(args->values, args->count); free(args->values); RB_FOREACH_SAFE(entry, args_tree, &args->tree, entry1) { RB_REMOVE(args_tree, &args->tree, entry); TAILQ_FOREACH_SAFE(value, &entry->values, entry, value1) { TAILQ_REMOVE(&entry->values, value, entry); args_free_value(value); free(value); } free(entry); } free(args); } /* Convert arguments to vector. */ void args_to_vector(struct args *args, int *argc, char ***argv) { char *s; u_int i; *argc = 0; *argv = NULL; for (i = 0; i < args->count; i++) { switch (args->values[i].type) { case ARGS_NONE: break; case ARGS_STRING: cmd_append_argv(argc, argv, args->values[i].string); break; case ARGS_COMMANDS: s = cmd_list_print(args->values[i].cmdlist, 0); cmd_append_argv(argc, argv, s); free(s); break; } } } /* Convert arguments from vector. */ struct args_value * args_from_vector(int argc, char **argv) { struct args_value *values; int i; values = xcalloc(argc, sizeof *values); for (i = 0; i < argc; i++) { values[i].type = ARGS_STRING; values[i].string = xstrdup(argv[i]); } return (values); } /* Add to string. */ static void printflike(3, 4) args_print_add(char **buf, size_t *len, const char *fmt, ...) { va_list ap; char *s; size_t slen; va_start(ap, fmt); slen = xvasprintf(&s, fmt, ap); va_end(ap); *len += slen; *buf = xrealloc(*buf, *len); strlcat(*buf, s, *len); free(s); } /* Add value to string. */ static void args_print_add_value(char **buf, size_t *len, struct args_value *value) { char *expanded = NULL; if (**buf != '\0') args_print_add(buf, len, " "); switch (value->type) { case ARGS_NONE: break; case ARGS_COMMANDS: expanded = cmd_list_print(value->cmdlist, 0); args_print_add(buf, len, "{ %s }", expanded); break; case ARGS_STRING: expanded = args_escape(value->string); args_print_add(buf, len, "%s", expanded); break; } free(expanded); } /* Print a set of arguments. */ char * args_print(struct args *args) { size_t len; char *buf; u_int i, j; struct args_entry *entry; struct args_entry *last = NULL; struct args_value *value; len = 1; buf = xcalloc(1, len); /* Process the flags first. */ RB_FOREACH(entry, args_tree, &args->tree) { if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) continue; if (!TAILQ_EMPTY(&entry->values)) continue; if (*buf == '\0') args_print_add(&buf, &len, "-"); for (j = 0; j < entry->count; j++) args_print_add(&buf, &len, "%c", entry->flag); } /* Then the flags with arguments. */ RB_FOREACH(entry, args_tree, &args->tree) { if (entry->flags & ARGS_ENTRY_OPTIONAL_VALUE) { if (*buf != '\0') args_print_add(&buf, &len, " -%c", entry->flag); else args_print_add(&buf, &len, "-%c", entry->flag); last = entry; continue; } if (TAILQ_EMPTY(&entry->values)) continue; TAILQ_FOREACH(value, &entry->values, entry) { if (*buf != '\0') args_print_add(&buf, &len, " -%c", entry->flag); else args_print_add(&buf, &len, "-%c", entry->flag); args_print_add_value(&buf, &len, value); } last = entry; } if (last && (last->flags & ARGS_ENTRY_OPTIONAL_VALUE)) args_print_add(&buf, &len, " --"); /* And finally the argument vector. */ for (i = 0; i < args->count; i++) args_print_add_value(&buf, &len, &args->values[i]); return (buf); } /* Escape an argument. */ char * args_escape(const char *s) { static const char dquoted[] = " #';${}%"; static const char squoted[] = " \""; char *escaped, *result; int flags, quotes = 0; if (*s == '\0') { xasprintf(&result, "''"); return (result); } if (s[strcspn(s, dquoted)] != '\0') quotes = '"'; else if (s[strcspn(s, squoted)] != '\0') quotes = '\''; if (s[0] != ' ' && s[1] == '\0' && (quotes != 0 || s[0] == '~')) { xasprintf(&escaped, "\\%c", s[0]); return (escaped); } flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL; if (quotes == '"') flags |= VIS_DQ; utf8_stravis(&escaped, s, flags); if (quotes == '\'') xasprintf(&result, "'%s'", escaped); else if (quotes == '"') { if (*escaped == '~') xasprintf(&result, "\"\\%s\"", escaped); else xasprintf(&result, "\"%s\"", escaped); } else { if (*escaped == '~') xasprintf(&result, "\\%s", escaped); else result = xstrdup(escaped); } free(escaped); return (result); } /* Return if an argument is present. */ int args_has(struct args *args, u_char flag) { struct args_entry *entry; entry = args_find(args, flag); if (entry == NULL) return (0); return (entry->count); } /* Set argument value in the arguments tree. */ void args_set(struct args *args, u_char flag, struct args_value *value, int flags) { struct args_entry *entry; entry = args_find(args, flag); if (entry == NULL) { entry = xcalloc(1, sizeof *entry); entry->flag = flag; entry->count = 1; entry->flags = flags; TAILQ_INIT(&entry->values); RB_INSERT(args_tree, &args->tree, entry); } else entry->count++; if (value != NULL && value->type != ARGS_NONE) TAILQ_INSERT_TAIL(&entry->values, value, entry); else free(value); } /* Get argument value. Will be NULL if it isn't present. */ const char * args_get(struct args *args, u_char flag) { struct args_entry *entry; if ((entry = args_find(args, flag)) == NULL) return (NULL); if (TAILQ_EMPTY(&entry->values)) return (NULL); return (TAILQ_LAST(&entry->values, args_values)->string); } /* Get first argument. */ u_char args_first(struct args *args, struct args_entry **entry) { *entry = RB_MIN(args_tree, &args->tree); if (*entry == NULL) return (0); return ((*entry)->flag); } /* Get next argument. */ u_char args_next(struct args_entry **entry) { *entry = RB_NEXT(args_tree, &args->tree, *entry); if (*entry == NULL) return (0); return ((*entry)->flag); } /* Get argument count. */ u_int args_count(struct args *args) { return (args->count); } /* Get argument values. */ struct args_value * args_values(struct args *args) { return (args->values); } /* Get argument value. */ struct args_value * args_value(struct args *args, u_int idx) { if (idx >= args->count) return (NULL); return (&args->values[idx]); } /* Return argument as string. */ const char * args_string(struct args *args, u_int idx) { if (idx >= args->count) return (NULL); return (args_value_as_string(&args->values[idx])); } /* Make a command now. */ struct cmd_list * args_make_commands_now(struct cmd *self, struct cmdq_item *item, u_int idx, int expand) { struct args_command_state *state; char *error; struct cmd_list *cmdlist; state = args_make_commands_prepare(self, item, idx, NULL, 0, expand); cmdlist = args_make_commands(state, 0, NULL, &error); if (cmdlist == NULL) { cmdq_error(item, "%s", error); free(error); } else cmdlist->references++; args_make_commands_free(state); return (cmdlist); } /* Save bits to make a command later. */ struct args_command_state * args_make_commands_prepare(struct cmd *self, struct cmdq_item *item, u_int idx, const char *default_command, int wait, int expand) { struct args *args = cmd_get_args(self); struct cmd_find_state *target = cmdq_get_target(item); struct client *tc = cmdq_get_target_client(item); struct args_value *value; struct args_command_state *state; const char *cmd; const char *file; state = xcalloc(1, sizeof *state); if (idx < args->count) { value = &args->values[idx]; if (value->type == ARGS_COMMANDS) { state->cmdlist = value->cmdlist; state->cmdlist->references++; return (state); } cmd = value->string; } else { if (default_command == NULL) fatalx("argument out of range"); cmd = default_command; } if (expand) state->cmd = format_single_from_target(item, cmd); else state->cmd = xstrdup(cmd); log_debug("%s: %s", __func__, state->cmd); if (wait) state->pi.item = item; cmd_get_source(self, &file, &state->pi.line); if (file != NULL) state->pi.file = xstrdup(file); state->pi.c = tc; if (state->pi.c != NULL) state->pi.c->references++; cmd_find_copy_state(&state->pi.fs, target); return (state); } /* Return argument as command. */ struct cmd_list * args_make_commands(struct args_command_state *state, int argc, char **argv, char **error) { struct cmd_parse_result *pr; char *cmd, *new_cmd; int i; if (state->cmdlist != NULL) { if (argc == 0) return (state->cmdlist); return (cmd_list_copy(state->cmdlist, argc, argv)); } cmd = xstrdup(state->cmd); log_debug("%s: %s", __func__, cmd); cmd_log_argv(argc, argv, __func__); for (i = 0; i < argc; i++) { new_cmd = cmd_template_replace(cmd, argv[i], i + 1); log_debug("%s: %%%u %s: %s", __func__, i + 1, argv[i], new_cmd); free(cmd); cmd = new_cmd; } log_debug("%s: %s", __func__, cmd); pr = cmd_parse_from_string(cmd, &state->pi); free(cmd); switch (pr->status) { case CMD_PARSE_ERROR: *error = pr->error; return (NULL); case CMD_PARSE_SUCCESS: return (pr->cmdlist); } fatalx("invalid parse return state"); } /* Free commands state. */ void args_make_commands_free(struct args_command_state *state) { if (state->cmdlist != NULL) cmd_list_free(state->cmdlist); if (state->pi.c != NULL) server_client_unref(state->pi.c); free((void *)state->pi.file); free(state->cmd); free(state); } /* Get prepared command. */ char * args_make_commands_get_command(struct args_command_state *state) { struct cmd *first; int n; char *s; if (state->cmdlist != NULL) { first = cmd_list_first(state->cmdlist); if (first == NULL) return (xstrdup("")); return (xstrdup(cmd_get_entry(first)->name)); } n = strcspn(state->cmd, " ,"); xasprintf(&s, "%.*s", n, state->cmd); return (s); } /* Get first value in argument. */ struct args_value * args_first_value(struct args *args, u_char flag) { struct args_entry *entry; if ((entry = args_find(args, flag)) == NULL) return (NULL); return (TAILQ_FIRST(&entry->values)); } /* Get next value in argument. */ struct args_value * args_next_value(struct args_value *value) { return (TAILQ_NEXT(value, entry)); } /* Convert an argument value to a number. */ long long args_strtonum(struct args *args, u_char flag, long long minval, long long maxval, char **cause) { const char *errstr; long long ll; struct args_entry *entry; struct args_value *value; if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } value = TAILQ_LAST(&entry->values, args_values); if (value == NULL || value->type != ARGS_STRING || value->string == NULL) { *cause = xstrdup("missing"); return (0); } ll = strtonum(value->string, minval, maxval, &errstr); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } *cause = NULL; return (ll); } /* Convert an argument value to a number, and expand formats. */ long long args_strtonum_and_expand(struct args *args, u_char flag, long long minval, long long maxval, struct cmdq_item *item, char **cause) { const char *errstr; char *formatted; long long ll; struct args_entry *entry; struct args_value *value; if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } value = TAILQ_LAST(&entry->values, args_values); if (value == NULL || value->type != ARGS_STRING || value->string == NULL) { *cause = xstrdup("missing"); return (0); } formatted = format_single_from_target(item, value->string); ll = strtonum(formatted, minval, maxval, &errstr); free(formatted); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } *cause = NULL; return (ll); } /* Convert an argument to a number which may be a percentage. */ long long args_percentage(struct args *args, u_char flag, long long minval, long long maxval, long long curval, char **cause) { const char *value; struct args_entry *entry; if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } if (TAILQ_EMPTY(&entry->values)) { *cause = xstrdup("empty"); return (0); } value = TAILQ_LAST(&entry->values, args_values)->string; return (args_string_percentage(value, minval, maxval, curval, cause)); } /* Convert a string to a number which may be a percentage. */ long long args_string_percentage(const char *value, long long minval, long long maxval, long long curval, char **cause) { const char *errstr; long long ll; size_t valuelen = strlen(value); char *copy; if (valuelen == 0) { *cause = xstrdup("empty"); return (0); } if (value[valuelen - 1] == '%') { copy = xstrdup(value); copy[valuelen - 1] = '\0'; ll = strtonum(copy, 0, 100, &errstr); free(copy); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } ll = (curval * ll) / 100; if (ll < minval) { *cause = xstrdup("too small"); return (0); } if (ll > maxval) { *cause = xstrdup("too large"); return (0); } } else { ll = strtonum(value, minval, maxval, &errstr); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } } *cause = NULL; return (ll); } /* * Convert an argument to a number which may be a percentage, and expand * formats. */ long long args_percentage_and_expand(struct args *args, u_char flag, long long minval, long long maxval, long long curval, struct cmdq_item *item, char **cause) { const char *value; struct args_entry *entry; if ((entry = args_find(args, flag)) == NULL) { *cause = xstrdup("missing"); return (0); } if (TAILQ_EMPTY(&entry->values)) { *cause = xstrdup("empty"); return (0); } value = TAILQ_LAST(&entry->values, args_values)->string; return (args_string_percentage_and_expand(value, minval, maxval, curval, item, cause)); } /* * Convert a string to a number which may be a percentage, and expand formats. */ long long args_string_percentage_and_expand(const char *value, long long minval, long long maxval, long long curval, struct cmdq_item *item, char **cause) { const char *errstr; long long ll; size_t valuelen = strlen(value); char *copy, *f; if (value[valuelen - 1] == '%') { copy = xstrdup(value); copy[valuelen - 1] = '\0'; f = format_single_from_target(item, copy); ll = strtonum(f, 0, 100, &errstr); free(f); free(copy); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } ll = (curval * ll) / 100; if (ll < minval) { *cause = xstrdup("too small"); return (0); } if (ll > maxval) { *cause = xstrdup("too large"); return (0); } } else { f = format_single_from_target(item, value); ll = strtonum(f, minval, maxval, &errstr); free(f); if (errstr != NULL) { *cause = xstrdup(errstr); return (0); } } *cause = NULL; return (ll); }