From 20636d956dd36c1f14152569a4d44a50eea9083d Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Sun, 24 Mar 2013 09:54:10 +0000 Subject: Add a command queue to standardize and simplify commands that call other commands and allow a command to block execution of subsequent commands. This allows run-shell and if-shell to be synchronous which has been much requested. Each client has a default command queue and commands are consumed one at a time from it. A command may suspend execution from the queue by returning CMD_RETURN_WAIT and then resume it by calling cmd_continue() - for example run-shell does this from the callback that is fired after the job is freed. When the command queue becomes empty, command clients are automatically exited (unless attaching). A callback is also fired - this is used for nested commands in, for example, if-shell which can block execution of the client's cmdq until a new cmdq becomes empty. Also merge all the old error/info/print functions together and lose the old curclient/cmdclient distinction - a cmdq is bound to one client (or none if in the configuration file), this is a command client if c->session is NULL otherwise an attached client. --- cmd-queue.c | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 cmd-queue.c (limited to 'cmd-queue.c') diff --git a/cmd-queue.c b/cmd-queue.c new file mode 100644 index 00000000..07884df2 --- /dev/null +++ b/cmd-queue.c @@ -0,0 +1,258 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2013 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 "tmux.h" + +/* Create new command queue. */ +struct cmd_q * +cmdq_new(struct client *c) +{ + struct cmd_q *cmdq; + + cmdq = xcalloc(1, sizeof *cmdq); + cmdq->references = 1; + cmdq->dead = 0; + + cmdq->client = c; + cmdq->client_exit = 0; + + TAILQ_INIT(&cmdq->queue); + cmdq->item = NULL; + cmdq->cmd = NULL; + + return (cmdq); +} + +/* Free command queue */ +int +cmdq_free(struct cmd_q *cmdq) +{ + if (--cmdq->references != 0) + return (cmdq->dead); + + cmdq_flush(cmdq); + free(cmdq); + return (1); +} + +/* Show message from command. */ +void printflike2 +cmdq_print(struct cmd_q *cmdq, const char *fmt, ...) +{ + struct client *c = cmdq->client; + struct window *w; + va_list ap; + + va_start(ap, fmt); + + if (c == NULL) + /* nothing */; + else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + va_start(ap, fmt); + evbuffer_add_vprintf(c->stdout_data, fmt, ap); + va_end(ap); + + evbuffer_add(c->stdout_data, "\n", 1); + server_push_stdout(c); + } else { + w = c->session->curw->window; + if (w->active->mode != &window_copy_mode) { + window_pane_reset_mode(w->active); + window_pane_set_mode(w->active, &window_copy_mode); + window_copy_init_for_output(w->active); + } + window_copy_vadd(w->active, fmt, ap); + } + + va_end(ap); +} + +/* Show info from command. */ +void printflike2 +cmdq_info(struct cmd_q *cmdq, const char *fmt, ...) +{ + struct client *c = cmdq->client; + va_list ap; + char *msg; + + if (options_get_number(&global_options, "quiet")) + return; + + va_start(ap, fmt); + + if (c == NULL) + /* nothing */; + else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + va_start(ap, fmt); + evbuffer_add_vprintf(c->stdout_data, fmt, ap); + va_end(ap); + + evbuffer_add(c->stdout_data, "\n", 1); + server_push_stdout(c); + } else { + xvasprintf(&msg, fmt, ap); + *msg = toupper((u_char) *msg); + status_message_set(c, "%s", msg); + free(msg); + } + + va_end(ap); + +} + +/* Show error from command. */ +void printflike2 +cmdq_error(struct cmd_q *cmdq, const char *fmt, ...) +{ + struct client *c = cmdq->client; + struct cmd *cmd = cmdq->cmd; + va_list ap; + char *msg, *cause; + size_t msglen; + + va_start(ap, fmt); + msglen = xvasprintf(&msg, fmt, ap); + va_end(ap); + + if (c == NULL) { + xasprintf(&cause, "%s:%u: %s", cmd->file, cmd->line, msg); + ARRAY_ADD(&cfg_causes, cause); + } else if (c->session == NULL || (c->flags & CLIENT_CONTROL)) { + evbuffer_add(c->stderr_data, msg, msglen); + evbuffer_add(c->stderr_data, "\n", 1); + + server_push_stderr(c); + c->retcode = 1; + } else { + *msg = toupper((u_char) *msg); + status_message_set(c, "%s", msg); + } + + free(msg); +} + +/* Add command list to queue and begin processing if needed. */ +void +cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist) +{ + cmdq_append(cmdq, cmdlist); + + if (cmdq->item == NULL) { + cmdq->cmd = NULL; + cmdq_continue(cmdq); + } +} + +/* Add command list to queue. */ +void +cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist) +{ + struct cmd_q_item *item; + + item = xcalloc(1, sizeof *item); + item->cmdlist = cmdlist; + TAILQ_INSERT_TAIL(&cmdq->queue, item, qentry); + cmdlist->references++; +} + +/* Continue processing command queue. Returns 1 if finishes empty. */ +int +cmdq_continue(struct cmd_q *cmdq) +{ + struct client *c = cmdq->client; + struct cmd_q_item *next; + enum cmd_retval retval; + int guards, empty; + + guards = 0; + if (c != NULL && c->session != NULL) + guards = c->flags & CLIENT_CONTROL; + + notify_disable(); + + empty = TAILQ_EMPTY(&cmdq->queue); + if (empty) + goto empty; + + if (cmdq->item == NULL) { + cmdq->item = TAILQ_FIRST(&cmdq->queue); + cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list); + } else + cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry); + + do { + next = TAILQ_NEXT(cmdq->item, qentry); + + while (cmdq->cmd != NULL) { + if (guards) + cmdq_print(cmdq, "%%begin"); + retval = cmdq->cmd->entry->exec(cmdq->cmd, cmdq); + if (guards) + cmdq_print(cmdq, "%%end"); + + if (retval == CMD_RETURN_ERROR) + break; + if (retval == CMD_RETURN_WAIT) + goto out; + if (retval == CMD_RETURN_STOP) { + cmdq_flush(cmdq); + goto empty; + } + + cmdq->cmd = TAILQ_NEXT(cmdq->cmd, qentry); + } + + TAILQ_REMOVE(&cmdq->queue, cmdq->item, qentry); + cmd_list_free(cmdq->item->cmdlist); + free(cmdq->item); + + cmdq->item = next; + if (cmdq->item != NULL) + cmdq->cmd = TAILQ_FIRST(&cmdq->item->cmdlist->list); + } while (cmdq->item != NULL); + +empty: + if (cmdq->client_exit) + cmdq->client->flags |= CLIENT_EXIT; + if (cmdq->emptyfn != NULL) + cmdq->emptyfn(cmdq); /* may free cmdq */ + empty = 1; + +out: + notify_enable(); + return (empty); +} + +/* Flush command queue. */ +void +cmdq_flush(struct cmd_q *cmdq) +{ + struct cmd_q_item *item, *item1; + + TAILQ_FOREACH_SAFE(item, &cmdq->queue, qentry, item1) { + TAILQ_REMOVE(&cmdq->queue, item, qentry); + cmd_list_free(item->cmdlist); + free(item); + } + cmdq->item = NULL; +} -- cgit From ebd9c615c8137e68f8831d4dbfd968a2f6a10a72 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 25 Mar 2013 10:06:13 +0000 Subject: Add some additional debug logging. --- cmd-queue.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'cmd-queue.c') diff --git a/cmd-queue.c b/cmd-queue.c index 07884df2..fba371dd 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -183,6 +183,7 @@ cmdq_continue(struct cmd_q *cmdq) struct cmd_q_item *next; enum cmd_retval retval; int guards, empty; + char s[1024]; guards = 0; if (c != NULL && c->session != NULL) @@ -204,11 +205,19 @@ cmdq_continue(struct cmd_q *cmdq) next = TAILQ_NEXT(cmdq->item, qentry); while (cmdq->cmd != NULL) { + cmd_print(cmdq->cmd, s, sizeof s); + log_debug("cmdq %p: %s (client %d)", cmdq, s, + cmdq->client != NULL ? cmdq->client->ibuf.fd : -1); + if (guards) cmdq_print(cmdq, "%%begin"); retval = cmdq->cmd->entry->exec(cmdq->cmd, cmdq); - if (guards) - cmdq_print(cmdq, "%%end"); + if (guards) { + if (retval == CMD_RETURN_ERROR) + cmdq_print(cmdq, "%%error"); + else + cmdq_print(cmdq, "%%end"); + } if (retval == CMD_RETURN_ERROR) break; -- cgit From 0ef24f99124b450bef17e3cc7895761e9eced049 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 25 Mar 2013 11:41:16 +0000 Subject: Only send end guard if begin was sent, from George Nachman. --- cmd-queue.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'cmd-queue.c') diff --git a/cmd-queue.c b/cmd-queue.c index fba371dd..8bcc9192 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -151,6 +151,23 @@ cmdq_error(struct cmd_q *cmdq, const char *fmt, ...) free(msg); } +/* Print a guard line. */ +int +cmdq_guard(struct cmd_q *cmdq, const char *guard) +{ + struct client *c = cmdq->client; + + if (c == NULL || c->session == NULL) + return 0; + if (!(c->flags & CLIENT_CONTROL)) + return 0; + + evbuffer_add_printf(c->stdout_data, "%%%s %ld %u\n", guard, + (long) cmdq->time, cmdq->number); + server_push_stdout(c); + return 1; +} + /* Add command list to queue and begin processing if needed. */ void cmdq_run(struct cmd_q *cmdq, struct cmd_list *cmdlist) @@ -179,16 +196,11 @@ cmdq_append(struct cmd_q *cmdq, struct cmd_list *cmdlist) int cmdq_continue(struct cmd_q *cmdq) { - struct client *c = cmdq->client; struct cmd_q_item *next; enum cmd_retval retval; - int guards, empty; + int empty, guard; char s[1024]; - guards = 0; - if (c != NULL && c->session != NULL) - guards = c->flags & CLIENT_CONTROL; - notify_disable(); empty = TAILQ_EMPTY(&cmdq->queue); @@ -209,14 +221,16 @@ cmdq_continue(struct cmd_q *cmdq) log_debug("cmdq %p: %s (client %d)", cmdq, s, cmdq->client != NULL ? cmdq->client->ibuf.fd : -1); - if (guards) - cmdq_print(cmdq, "%%begin"); + cmdq->time = time(NULL); + cmdq->number++; + + guard = cmdq_guard(cmdq, "begin"); retval = cmdq->cmd->entry->exec(cmdq->cmd, cmdq); - if (guards) { + if (guard) { if (retval == CMD_RETURN_ERROR) - cmdq_print(cmdq, "%%error"); + cmdq_guard(cmdq, "error"); else - cmdq_print(cmdq, "%%end"); + cmdq_guard(cmdq, "end"); } if (retval == CMD_RETURN_ERROR) -- cgit