| /* $OpenBSD$ */ |
| |
| /* |
| * Copyright (c) 2013 Nicholas Marriott <nicholas.marriott@gmail.com> |
| * |
| * 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 <sys/types.h> |
| |
| #include <ctype.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| |
| #include "tmux.h" |
| |
| /* Command queue flags. */ |
| #define CMDQ_FIRED 0x1 |
| #define CMDQ_WAITING 0x2 |
| |
| /* Command queue item type. */ |
| enum cmdq_type { |
| CMDQ_COMMAND, |
| CMDQ_CALLBACK, |
| }; |
| |
| /* Command queue item. */ |
| struct cmdq_item { |
| char *name; |
| struct cmdq_list *queue; |
| struct cmdq_item *next; |
| |
| struct client *client; |
| struct client *target_client; |
| |
| enum cmdq_type type; |
| u_int group; |
| |
| u_int number; |
| time_t time; |
| |
| int flags; |
| |
| struct cmdq_state *state; |
| struct cmd_find_state source; |
| struct cmd_find_state target; |
| |
| struct cmd_list *cmdlist; |
| struct cmd *cmd; |
| |
| cmdq_cb cb; |
| void *data; |
| |
| TAILQ_ENTRY(cmdq_item) entry; |
| }; |
| TAILQ_HEAD(cmdq_item_list, cmdq_item); |
| |
| /* |
| * Command queue state. This is the context for commands on the command queue. |
| * It holds information about how the commands were fired (the key and flags), |
| * any additional formats for the commands, and the current default target. |
| * Multiple commands can share the same state and a command may update the |
| * default target. |
| */ |
| struct cmdq_state { |
| int references; |
| int flags; |
| |
| struct format_tree *formats; |
| |
| struct key_event event; |
| struct cmd_find_state current; |
| }; |
| |
| /* Command queue. */ |
| struct cmdq_list { |
| struct cmdq_item *item; |
| struct cmdq_item_list list; |
| }; |
| |
| /* Get command queue name. */ |
| static const char * |
| cmdq_name(struct client *c) |
| { |
| static char s[256]; |
| |
| if (c == NULL) |
| return ("<global>"); |
| if (c->name != NULL) |
| xsnprintf(s, sizeof s, "<%s>", c->name); |
| else |
| xsnprintf(s, sizeof s, "<%p>", c); |
| return (s); |
| } |
| |
| /* Get command queue from client. */ |
| static struct cmdq_list * |
| cmdq_get(struct client *c) |
| { |
| static struct cmdq_list *global_queue; |
| |
| if (c == NULL) { |
| if (global_queue == NULL) |
| global_queue = cmdq_new(); |
| return (global_queue); |
| } |
| return (c->queue); |
| } |
| |
| /* Create a queue. */ |
| struct cmdq_list * |
| cmdq_new(void) |
| { |
| struct cmdq_list *queue; |
| |
| queue = xcalloc (1, sizeof *queue); |
| TAILQ_INIT (&queue->list); |
| return (queue); |
| } |
| |
| /* Free a queue. */ |
| void |
| cmdq_free(struct cmdq_list *queue) |
| { |
| if (!TAILQ_EMPTY(&queue->list)) |
| fatalx("queue not empty"); |
| free(queue); |
| } |
| |
| /* Get item name. */ |
| const char * |
| cmdq_get_name(struct cmdq_item *item) |
| { |
| return (item->name); |
| } |
| |
| /* Get item client. */ |
| struct client * |
| cmdq_get_client(struct cmdq_item *item) |
| { |
| return (item->client); |
| } |
| |
| /* Get item target client. */ |
| struct client * |
| cmdq_get_target_client(struct cmdq_item *item) |
| { |
| return (item->target_client); |
| } |
| |
| /* Get item state. */ |
| struct cmdq_state * |
| cmdq_get_state(struct cmdq_item *item) |
| { |
| return (item->state); |
| } |
| |
| /* Get item target. */ |
| struct cmd_find_state * |
| cmdq_get_target(struct cmdq_item *item) |
| { |
| return (&item->target); |
| } |
| |
| /* Get item source. */ |
| struct cmd_find_state * |
| cmdq_get_source(struct cmdq_item *item) |
| { |
| return (&item->source); |
| } |
| |
| /* Get state event. */ |
| struct key_event * |
| cmdq_get_event(struct cmdq_item *item) |
| { |
| return (&item->state->event); |
| } |
| |
| /* Get state current target. */ |
| struct cmd_find_state * |
| cmdq_get_current(struct cmdq_item *item) |
| { |
| return (&item->state->current); |
| } |
| |
| /* Get state flags. */ |
| int |
| cmdq_get_flags(struct cmdq_item *item) |
| { |
| return (item->state->flags); |
| } |
| |
| /* Create a new state. */ |
| struct cmdq_state * |
| cmdq_new_state(struct cmd_find_state *current, struct key_event *event, |
| int flags) |
| { |
| struct cmdq_state *state; |
| |
| state = xcalloc(1, sizeof *state); |
| state->references = 1; |
| state->flags = flags; |
| |
| if (event != NULL) |
| memcpy(&state->event, event, sizeof state->event); |
| else |
| state->event.key = KEYC_NONE; |
| if (current != NULL && cmd_find_valid_state(current)) |
| cmd_find_copy_state(&state->current, current); |
| else |
| cmd_find_clear_state(&state->current, 0); |
| |
| return (state); |
| } |
| |
| /* Add a reference to a state. */ |
| struct cmdq_state * |
| cmdq_link_state(struct cmdq_state *state) |
| { |
| state->references++; |
| return (state); |
| } |
| |
| /* Make a copy of a state. */ |
| struct cmdq_state * |
| cmdq_copy_state(struct cmdq_state *state) |
| { |
| return (cmdq_new_state(&state->current, &state->event, state->flags)); |
| } |
| |
| /* Free a state. */ |
| void |
| cmdq_free_state(struct cmdq_state *state) |
| { |
| if (--state->references != 0) |
| return; |
| |
| if (state->formats != NULL) |
| format_free(state->formats); |
| free(state); |
| } |
| |
| /* Add a format to command queue. */ |
| void |
| cmdq_add_format(struct cmdq_state *state, const char *key, const char *fmt, ...) |
| { |
| va_list ap; |
| char *value; |
| |
| va_start(ap, fmt); |
| xvasprintf(&value, fmt, ap); |
| va_end(ap); |
| |
| if (state->formats == NULL) |
| state->formats = format_create(NULL, NULL, FORMAT_NONE, 0); |
| format_add(state->formats, key, "%s", value); |
| |
| free(value); |
| } |
| |
| /* Merge formats from item. */ |
| void |
| cmdq_merge_formats(struct cmdq_item *item, struct format_tree *ft) |
| { |
| const struct cmd_entry *entry; |
| |
| if (item->cmd != NULL) { |
| entry = cmd_get_entry (item->cmd); |
| format_add(ft, "command", "%s", entry->name); |
| } |
| if (item->state->formats != NULL) |
| format_merge(ft, item->state->formats); |
| } |
| |
| /* Append an item. */ |
| struct cmdq_item * |
| cmdq_append(struct client *c, struct cmdq_item *item) |
| { |
| struct cmdq_list *queue = cmdq_get(c); |
| struct cmdq_item *next; |
| |
| do { |
| next = item->next; |
| item->next = NULL; |
| |
| if (c != NULL) |
| c->references++; |
| item->client = c; |
| |
| item->queue = queue; |
| TAILQ_INSERT_TAIL(&queue->list, item, entry); |
| log_debug("%s %s: %s", __func__, cmdq_name(c), item->name); |
| |
| item = next; |
| } while (item != NULL); |
| return (TAILQ_LAST(&queue->list, cmdq_item_list)); |
| } |
| |
| /* Insert an item. */ |
| struct cmdq_item * |
| cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) |
| { |
| struct client *c = after->client; |
| struct cmdq_list *queue = after->queue; |
| struct cmdq_item *next; |
| |
| do { |
| next = item->next; |
| item->next = after->next; |
| after->next = item; |
| |
| if (c != NULL) |
| c->references++; |
| item->client = c; |
| |
| item->queue = queue; |
| TAILQ_INSERT_AFTER(&queue->list, after, item, entry); |
| log_debug("%s %s: %s after %s", __func__, cmdq_name(c), |
| item->name, after->name); |
| |
| after = item; |
| item = next; |
| } while (item != NULL); |
| return (after); |
| } |
| |
| /* Insert a hook. */ |
| void |
| cmdq_insert_hook(struct session *s, struct cmdq_item *item, |
| struct cmd_find_state *current, const char *fmt, ...) |
| { |
| struct cmdq_state *state = item->state; |
| struct cmd *cmd = item->cmd; |
| struct args *args = cmd_get_args(cmd); |
| struct args_entry *entryp; |
| struct args_value *valuep; |
| struct options *oo; |
| va_list ap; |
| char *name, tmp[32], flag, *arguments; |
| int i; |
| const char *value; |
| struct cmdq_item *new_item; |
| struct cmdq_state *new_state; |
| struct options_entry *o; |
| struct options_array_item *a; |
| struct cmd_list *cmdlist; |
| |
| if (item->state->flags & CMDQ_STATE_NOHOOKS) |
| return; |
| if (s == NULL) |
| oo = global_s_options; |
| else |
| oo = s->options; |
| |
| va_start(ap, fmt); |
| xvasprintf(&name, fmt, ap); |
| va_end(ap); |
| |
| o = options_get(oo, name); |
| if (o == NULL) { |
| free(name); |
| return; |
| } |
| log_debug("running hook %s (parent %p)", name, item); |
| |
| /* |
| * The hooks get a new state because they should not update the current |
| * target or formats for any subsequent commands. |
| */ |
| new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS); |
| cmdq_add_format(new_state, "hook", "%s", name); |
| |
| arguments = args_print(args); |
| cmdq_add_format(new_state, "hook_arguments", "%s", arguments); |
| free(arguments); |
| |
| for (i = 0; i < args->argc; i++) { |
| xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); |
| cmdq_add_format(new_state, tmp, "%s", args->argv[i]); |
| } |
| flag = args_first(args, &entryp); |
| while (flag != 0) { |
| value = args_get(args, flag); |
| if (value == NULL) { |
| xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); |
| cmdq_add_format(new_state, tmp, "1"); |
| } else { |
| xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); |
| cmdq_add_format(new_state, tmp, "%s", value); |
| } |
| |
| i = 0; |
| value = args_first_value(args, flag, &valuep); |
| while (value != NULL) { |
| xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); |
| cmdq_add_format(new_state, tmp, "%s", value); |
| i++; |
| value = args_next_value(&valuep); |
| } |
| |
| flag = args_next(&entryp); |
| } |
| |
| a = options_array_first(o); |
| while (a != NULL) { |
| cmdlist = options_array_item_value(a)->cmdlist; |
| if (cmdlist != NULL) { |
| new_item = cmdq_get_command(cmdlist, new_state); |
| if (item != NULL) |
| item = cmdq_insert_after(item, new_item); |
| else |
| item = cmdq_append(NULL, new_item); |
| } |
| a = options_array_next(a); |
| } |
| |
| cmdq_free_state(new_state); |
| free(name); |
| } |
| |
| /* Continue processing command queue. */ |
| void |
| cmdq_continue(struct cmdq_item *item) |
| { |
| item->flags &= ~CMDQ_WAITING; |
| } |
| |
| /* Remove an item. */ |
| static void |
| cmdq_remove(struct cmdq_item *item) |
| { |
| if (item->client != NULL) |
| server_client_unref(item->client); |
| if (item->cmdlist != NULL) |
| cmd_list_free(item->cmdlist); |
| cmdq_free_state(item->state); |
| |
| TAILQ_REMOVE(&item->queue->list, item, entry); |
| |
| free(item->name); |
| free(item); |
| } |
| |
| /* Remove all subsequent items that match this item's group. */ |
| static void |
| cmdq_remove_group(struct cmdq_item *item) |
| { |
| struct cmdq_item *this, *next; |
| |
| if (item->group == 0) |
| return; |
| this = TAILQ_NEXT(item, entry); |
| while (this != NULL) { |
| next = TAILQ_NEXT(this, entry); |
| if (this->group == item->group) |
| cmdq_remove(this); |
| this = next; |
| } |
| } |
| |
| /* Get a command for the command queue. */ |
| struct cmdq_item * |
| cmdq_get_command(struct cmd_list *cmdlist, struct cmdq_state *state) |
| { |
| struct cmdq_item *item, *first = NULL, *last = NULL; |
| struct cmd *cmd; |
| const struct cmd_entry *entry; |
| int created = 0; |
| |
| if (state == NULL) { |
| state = cmdq_new_state(NULL, NULL, 0); |
| created = 1; |
| } |
| |
| cmd = cmd_list_first(cmdlist); |
| while (cmd != NULL) { |
| entry = cmd_get_entry(cmd); |
| |
| item = xcalloc(1, sizeof *item); |
| xasprintf(&item->name, "[%s/%p]", entry->name, item); |
| item->type = CMDQ_COMMAND; |
| |
| item->group = cmd_get_group(cmd); |
| item->state = cmdq_link_state(state); |
| |
| item->cmdlist = cmdlist; |
| item->cmd = cmd; |
| |
| cmdlist->references++; |
| log_debug("%s: %s group %u", __func__, item->name, item->group); |
| |
| if (first == NULL) |
| first = item; |
| if (last != NULL) |
| last->next = item; |
| last = item; |
| |
| cmd = cmd_list_next(cmd); |
| } |
| |
| if (created) |
| cmdq_free_state(state); |
| return (first); |
| } |
| |
| /* Fill in flag for a command. */ |
| static enum cmd_retval |
| cmdq_find_flag(struct cmdq_item *item, struct cmd_find_state *fs, |
| const struct cmd_entry_flag *flag) |
| { |
| const char *value; |
| |
| if (flag->flag == 0) { |
| cmd_find_clear_state(fs, 0); |
| return (CMD_RETURN_NORMAL); |
| } |
| |
| value = args_get(cmd_get_args(item->cmd), flag->flag); |
| if (cmd_find_target(fs, item, value, flag->type, flag->flags) != 0) { |
| cmd_find_clear_state(fs, 0); |
| return (CMD_RETURN_ERROR); |
| } |
| return (CMD_RETURN_NORMAL); |
| } |
| |
| /* Fire command on command queue. */ |
| static enum cmd_retval |
| cmdq_fire_command(struct cmdq_item *item) |
| { |
| const char *name = cmdq_name(item->client); |
| struct cmdq_state *state = item->state; |
| struct cmd *cmd = item->cmd; |
| struct args *args = cmd_get_args(cmd); |
| const struct cmd_entry *entry = cmd_get_entry(cmd); |
| struct client *tc, *saved = item->client; |
| enum cmd_retval retval; |
| struct cmd_find_state *fsp, fs; |
| int flags, quiet = 0; |
| char *tmp; |
| |
| if (log_get_level() > 1) { |
| tmp = cmd_print(cmd); |
| log_debug("%s %s: (%u) %s", __func__, name, item->group, tmp); |
| free(tmp); |
| } |
| |
| flags = !!(state->flags & CMDQ_STATE_CONTROL); |
| cmdq_guard(item, "begin", flags); |
| |
| if (item->client == NULL) |
| item->client = cmd_find_client(item, NULL, 1); |
| |
| if (entry->flags & CMD_CLIENT_CANFAIL) |
| quiet = 1; |
| if (entry->flags & CMD_CLIENT_CFLAG) { |
| tc = cmd_find_client(item, args_get(args, 'c'), quiet); |
| if (tc == NULL && !quiet) { |
| retval = CMD_RETURN_ERROR; |
| goto out; |
| } |
| } else if (entry->flags & CMD_CLIENT_TFLAG) { |
| tc = cmd_find_client(item, args_get(args, 't'), quiet); |
| if (tc == NULL && !quiet) { |
| retval = CMD_RETURN_ERROR; |
| goto out; |
| } |
| } else |
| tc = cmd_find_client(item, NULL, 1); |
| item->target_client = tc; |
| |
| retval = cmdq_find_flag(item, &item->source, &entry->source); |
| if (retval == CMD_RETURN_ERROR) |
| goto out; |
| retval = cmdq_find_flag(item, &item->target, &entry->target); |
| if (retval == CMD_RETURN_ERROR) |
| goto out; |
| |
| |
| retval = entry->exec(cmd, item); |
| if (retval == CMD_RETURN_ERROR) |
| goto out; |
| |
| if (entry->flags & CMD_AFTERHOOK) { |
| if (cmd_find_valid_state(&item->target)) |
| fsp = &item->target; |
| else if (cmd_find_valid_state(&item->state->current)) |
| fsp = &item->state->current; |
| else if (cmd_find_from_client(&fs, item->client, 0) == 0) |
| fsp = &fs; |
| else |
| goto out; |
| cmdq_insert_hook(fsp->s, item, fsp, "after-%s", entry->name); |
| } |
| |
| out: |
| item->client = saved; |
| if (retval == CMD_RETURN_ERROR) |
| cmdq_guard(item, "error", flags); |
| else |
| cmdq_guard(item, "end", flags); |
| return (retval); |
| } |
| |
| /* Get a callback for the command queue. */ |
| struct cmdq_item * |
| cmdq_get_callback1(const char *name, cmdq_cb cb, void *data) |
| { |
| struct cmdq_item *item; |
| |
| item = xcalloc(1, sizeof *item); |
| xasprintf(&item->name, "[%s/%p]", name, item); |
| item->type = CMDQ_CALLBACK; |
| |
| item->group = 0; |
| item->state = cmdq_new_state(NULL, NULL, 0); |
| |
| item->cb = cb; |
| item->data = data; |
| |
| return (item); |
| } |
| |
| /* Generic error callback. */ |
| static enum cmd_retval |
| cmdq_error_callback(struct cmdq_item *item, void *data) |
| { |
| char *error = data; |
| |
| cmdq_error(item, "%s", error); |
| free(error); |
| |
| return (CMD_RETURN_NORMAL); |
| } |
| |
| /* Get an error callback for the command queue. */ |
| struct cmdq_item * |
| cmdq_get_error(const char *error) |
| { |
| return (cmdq_get_callback(cmdq_error_callback, xstrdup(error))); |
| } |
| |
| /* Fire callback on callback queue. */ |
| static enum cmd_retval |
| cmdq_fire_callback(struct cmdq_item *item) |
| { |
| return (item->cb(item, item->data)); |
| } |
| |
| /* Process next item on command queue. */ |
| u_int |
| cmdq_next(struct client *c) |
| { |
| struct cmdq_list *queue = cmdq_get(c); |
| const char *name = cmdq_name(c); |
| struct cmdq_item *item; |
| enum cmd_retval retval; |
| u_int items = 0; |
| static u_int number; |
| |
| if (TAILQ_EMPTY(&queue->list)) { |
| log_debug("%s %s: empty", __func__, name); |
| return (0); |
| } |
| if (TAILQ_FIRST(&queue->list)->flags & CMDQ_WAITING) { |
| log_debug("%s %s: waiting", __func__, name); |
| return (0); |
| } |
| |
| log_debug("%s %s: enter", __func__, name); |
| for (;;) { |
| item = queue->item = TAILQ_FIRST(&queue->list); |
| if (item == NULL) |
| break; |
| log_debug("%s %s: %s (%d), flags %x", __func__, name, |
| item->name, item->type, item->flags); |
| |
| /* |
| * Any item with the waiting flag set waits until an external |
| * event clears the flag (for example, a job - look at |
| * run-shell). |
| */ |
| if (item->flags & CMDQ_WAITING) |
| goto waiting; |
| |
| /* |
| * Items are only fired once, once the fired flag is set, a |
| * waiting flag can only be cleared by an external event. |
| */ |
| if (~item->flags & CMDQ_FIRED) { |
| item->time = time(NULL); |
| item->number = ++number; |
| |
| switch (item->type) { |
| case CMDQ_COMMAND: |
| retval = cmdq_fire_command(item); |
| |
| /* |
| * If a command returns an error, remove any |
| * subsequent commands in the same group. |
| */ |
| if (retval == CMD_RETURN_ERROR) |
| cmdq_remove_group(item); |
| break; |
| case CMDQ_CALLBACK: |
| retval = cmdq_fire_callback(item); |
| break; |
| default: |
| retval = CMD_RETURN_ERROR; |
| break; |
| } |
| item->flags |= CMDQ_FIRED; |
| |
| if (retval == CMD_RETURN_WAIT) { |
| item->flags |= CMDQ_WAITING; |
| goto waiting; |
| } |
| items++; |
| } |
| cmdq_remove(item); |
| } |
| queue->item = NULL; |
| |
| log_debug("%s %s: exit (empty)", __func__, name); |
| return (items); |
| |
| waiting: |
| log_debug("%s %s: exit (wait)", __func__, name); |
| return (items); |
| } |
| |
| /* Get running item if any. */ |
| struct cmdq_item * |
| cmdq_running(struct client *c) |
| { |
| struct cmdq_list *queue = cmdq_get(c); |
| |
| return (queue->item); |
| } |
| |
| /* Print a guard line. */ |
| void |
| cmdq_guard(struct cmdq_item *item, const char *guard, int flags) |
| { |
| struct client *c = item->client; |
| long t = item->time; |
| u_int number = item->number; |
| |
| if (c != NULL && (c->flags & CLIENT_CONTROL)) |
| file_print(c, "%%%s %ld %u %d\n", guard, t, number, flags); |
| } |
| |
| /* Show message from command. */ |
| void |
| cmdq_print(struct cmdq_item *item, const char *fmt, ...) |
| { |
| struct client *c = item->client; |
| struct window_pane *wp; |
| struct window_mode_entry *wme; |
| va_list ap; |
| char *tmp, *msg; |
| |
| va_start(ap, fmt); |
| xvasprintf(&msg, fmt, ap); |
| va_end(ap); |
| |
| log_debug("%s: %s", __func__, msg); |
| |
| if (c == NULL) |
| /* nothing */; |
| else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { |
| if (~c->flags & CLIENT_UTF8) { |
| tmp = msg; |
| msg = utf8_sanitize(tmp); |
| free(tmp); |
| } |
| file_print(c, "%s\n", msg); |
| } else { |
| wp = c->session->curw->window->active; |
| wme = TAILQ_FIRST(&wp->modes); |
| if (wme == NULL || wme->mode != &window_view_mode) { |
| window_pane_set_mode(wp, NULL, &window_view_mode, NULL, |
| NULL); |
| } |
| window_copy_add(wp, "%s", msg); |
| } |
| |
| free(msg); |
| } |
| |
| /* Show error from command. */ |
| void |
| cmdq_error(struct cmdq_item *item, const char *fmt, ...) |
| { |
| struct client *c = item->client; |
| struct cmd *cmd = item->cmd; |
| va_list ap; |
| char *msg, *tmp; |
| const char *file; |
| u_int line; |
| |
| va_start(ap, fmt); |
| xvasprintf(&msg, fmt, ap); |
| va_end(ap); |
| |
| log_debug("%s: %s", __func__, msg); |
| |
| if (c == NULL) { |
| cmd_get_source(cmd, &file, &line); |
| cfg_add_cause("%s:%u: %s", file, line, msg); |
| } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { |
| if (~c->flags & CLIENT_UTF8) { |
| tmp = msg; |
| msg = utf8_sanitize(tmp); |
| free(tmp); |
| } |
| if (c->flags & CLIENT_CONTROL) |
| file_print(c, "%s\n", msg); |
| else |
| file_error(c, "%s\n", msg); |
| c->retval = 1; |
| } else { |
| *msg = toupper((u_char) *msg); |
| status_message_set(c, "%s", msg); |
| } |
| |
| free(msg); |
| } |