| /* $OpenBSD$ */ |
| |
| /* |
| * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com> |
| * Copyright (c) 2012 George Nachman <tmux@georgester.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 <event.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "tmux.h" |
| |
| /* |
| * Block of data to output. Each client has one "all" queue of blocks and |
| * another queue for each pane (in struct client_offset). %output blocks are |
| * added to both queues and other output lines (notifications) added only to |
| * the client queue. |
| * |
| * When a client becomes writeable, data from blocks on the pane queue are sent |
| * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written, |
| * it is removed from both pane and client queues and if this means non-%output |
| * blocks are now at the head of the client queue, they are written. |
| * |
| * This means a %output block holds up any subsequent non-%output blocks until |
| * it is written which enforces ordering even if the client cannot accept the |
| * entire block in one go. |
| */ |
| struct control_block { |
| size_t size; |
| char *line; |
| |
| TAILQ_ENTRY(control_block) entry; |
| TAILQ_ENTRY(control_block) all_entry; |
| }; |
| |
| /* Control client pane. */ |
| struct control_pane { |
| u_int pane; |
| |
| /* |
| * Offsets into the pane data. The first (offset) is the data we have |
| * written; the second (queued) the data we have queued (pointed to by |
| * a block). |
| */ |
| struct window_pane_offset offset; |
| struct window_pane_offset queued; |
| |
| int flags; |
| #define CONTROL_PANE_OFF 0x1 |
| |
| int pending_flag; |
| TAILQ_ENTRY(control_pane) pending_entry; |
| |
| TAILQ_HEAD(, control_block) blocks; |
| |
| RB_ENTRY(control_pane) entry; |
| }; |
| RB_HEAD(control_panes, control_pane); |
| |
| /* Control client state. */ |
| struct control_state { |
| struct control_panes panes; |
| |
| TAILQ_HEAD(, control_pane) pending_list; |
| u_int pending_count; |
| |
| TAILQ_HEAD(, control_block) all_blocks; |
| |
| struct bufferevent *read_event; |
| struct bufferevent *write_event; |
| }; |
| |
| /* Low watermark. */ |
| #define CONTROL_BUFFER_LOW 512 |
| #define CONTROL_BUFFER_HIGH 8192 |
| |
| /* Minimum to write to each client. */ |
| #define CONTROL_WRITE_MINIMUM 32 |
| |
| /* Flags to ignore client. */ |
| #define CONTROL_IGNORE_FLAGS \ |
| (CLIENT_CONTROL_NOOUTPUT| \ |
| CLIENT_UNATTACHEDFLAGS) |
| |
| /* Compare client panes. */ |
| static int |
| control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2) |
| { |
| if (cp1->pane < cp2->pane) |
| return (-1); |
| if (cp1->pane > cp2->pane) |
| return (1); |
| return (0); |
| } |
| RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp); |
| |
| /* Free a block. */ |
| static void |
| control_free_block(struct control_state *cs, struct control_block *cb) |
| { |
| free(cb->line); |
| TAILQ_REMOVE(&cs->all_blocks, cb, all_entry); |
| free(cb); |
| } |
| |
| /* Get pane offsets for this client. */ |
| static struct control_pane * |
| control_get_pane(struct client *c, struct window_pane *wp) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_pane cp = { .pane = wp->id }; |
| |
| return (RB_FIND(control_panes, &cs->panes, &cp)); |
| } |
| |
| /* Add pane offsets for this client. */ |
| static struct control_pane * |
| control_add_pane(struct client *c, struct window_pane *wp) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_pane *cp; |
| |
| cp = control_get_pane(c, wp); |
| if (cp != NULL) |
| return (cp); |
| |
| cp = xcalloc(1, sizeof *cp); |
| cp->pane = wp->id; |
| RB_INSERT(control_panes, &cs->panes, cp); |
| |
| memcpy(&cp->offset, &wp->offset, sizeof cp->offset); |
| memcpy(&cp->queued, &wp->offset, sizeof cp->queued); |
| TAILQ_INIT(&cp->blocks); |
| |
| return (cp); |
| } |
| |
| /* Reset control offsets. */ |
| void |
| control_reset_offsets(struct client *c) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_pane *cp, *cp1; |
| |
| RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) { |
| RB_REMOVE(control_panes, &cs->panes, cp); |
| free(cp); |
| } |
| |
| TAILQ_INIT(&cs->pending_list); |
| cs->pending_count = 0; |
| } |
| |
| /* Get offsets for client. */ |
| struct window_pane_offset * |
| control_pane_offset(struct client *c, struct window_pane *wp, int *off) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_pane *cp; |
| |
| if (c->flags & CLIENT_CONTROL_NOOUTPUT) { |
| *off = 0; |
| return (NULL); |
| } |
| |
| cp = control_get_pane(c, wp); |
| if (cp == NULL) { |
| *off = 0; |
| return (NULL); |
| } |
| if (cp->flags & CONTROL_PANE_OFF) { |
| *off = 1; |
| return (NULL); |
| } |
| *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW); |
| return (&cp->offset); |
| } |
| |
| /* Set pane as on. */ |
| void |
| control_set_pane_on(struct client *c, struct window_pane *wp) |
| { |
| struct control_pane *cp; |
| |
| cp = control_get_pane(c, wp); |
| if (cp != NULL) { |
| cp->flags &= ~CONTROL_PANE_OFF; |
| memcpy(&cp->offset, &wp->offset, sizeof cp->offset); |
| memcpy(&cp->queued, &wp->offset, sizeof cp->queued); |
| } |
| } |
| |
| /* Set pane as off. */ |
| void |
| control_set_pane_off(struct client *c, struct window_pane *wp) |
| { |
| struct control_pane *cp; |
| |
| cp = control_add_pane(c, wp); |
| cp->flags |= CONTROL_PANE_OFF; |
| } |
| |
| /* Write a line. */ |
| static void |
| control_vwrite(struct client *c, const char *fmt, va_list ap) |
| { |
| struct control_state *cs = c->control_state; |
| char *s; |
| |
| xvasprintf(&s, fmt, ap); |
| log_debug("%s: %s: writing line: %s", __func__, c->name, s); |
| |
| bufferevent_write(cs->write_event, s, strlen(s)); |
| bufferevent_write(cs->write_event, "\n", 1); |
| |
| bufferevent_enable(cs->write_event, EV_WRITE); |
| free(s); |
| } |
| |
| /* Write a line. */ |
| void |
| control_write(struct client *c, const char *fmt, ...) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_block *cb; |
| va_list ap; |
| |
| va_start(ap, fmt); |
| |
| if (TAILQ_EMPTY(&cs->all_blocks)) { |
| control_vwrite(c, fmt, ap); |
| va_end(ap); |
| return; |
| } |
| |
| cb = xcalloc(1, sizeof *cb); |
| xvasprintf(&cb->line, fmt, ap); |
| TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); |
| |
| log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); |
| bufferevent_enable(cs->write_event, EV_WRITE); |
| |
| va_end(ap); |
| } |
| |
| /* Write output from a pane. */ |
| void |
| control_write_output(struct client *c, struct window_pane *wp) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_pane *cp; |
| struct control_block *cb; |
| size_t new_size; |
| |
| if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) |
| return; |
| |
| if (c->flags & CONTROL_IGNORE_FLAGS) { |
| cp = control_get_pane(c, wp); |
| if (cp != NULL) |
| goto ignore; |
| return; |
| } |
| cp = control_add_pane(c, wp); |
| if (cp->flags & CONTROL_PANE_OFF) |
| goto ignore; |
| |
| window_pane_get_new_data(wp, &cp->queued, &new_size); |
| if (new_size == 0) |
| return; |
| window_pane_update_used_data(wp, &cp->queued, new_size); |
| |
| cb = xcalloc(1, sizeof *cb); |
| cb->size = new_size; |
| TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); |
| |
| TAILQ_INSERT_TAIL(&cp->blocks, cb, entry); |
| log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name, |
| cb->size, wp->id); |
| |
| if (!cp->pending_flag) { |
| log_debug("%s: %s: %%%u now pending", __func__, c->name, |
| wp->id); |
| TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry); |
| cp->pending_flag = 1; |
| cs->pending_count++; |
| } |
| bufferevent_enable(cs->write_event, EV_WRITE); |
| return; |
| |
| ignore: |
| log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id); |
| window_pane_update_used_data(wp, &cp->offset, SIZE_MAX); |
| window_pane_update_used_data(wp, &cp->queued, SIZE_MAX); |
| } |
| |
| /* Control client error callback. */ |
| static enum cmd_retval |
| control_error(struct cmdq_item *item, void *data) |
| { |
| struct client *c = cmdq_get_client(item); |
| char *error = data; |
| |
| cmdq_guard(item, "begin", 1); |
| control_write(c, "parse error: %s", error); |
| cmdq_guard(item, "error", 1); |
| |
| free(error); |
| return (CMD_RETURN_NORMAL); |
| } |
| |
| /* Control client error callback. */ |
| static void |
| control_error_callback(__unused struct bufferevent *bufev, |
| __unused short what, void *data) |
| { |
| struct client *c = data; |
| |
| c->flags |= CLIENT_EXIT; |
| } |
| |
| /* Control client input callback. Read lines and fire commands. */ |
| static void |
| control_read_callback(__unused struct bufferevent *bufev, void *data) |
| { |
| struct client *c = data; |
| struct control_state *cs = c->control_state; |
| struct evbuffer *buffer = cs->read_event->input; |
| char *line, *error; |
| struct cmdq_state *state; |
| enum cmd_parse_status status; |
| |
| for (;;) { |
| line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); |
| if (line == NULL) |
| break; |
| log_debug("%s: %s: %s", __func__, c->name, line); |
| if (*line == '\0') { /* empty line detach */ |
| free(line); |
| c->flags |= CLIENT_EXIT; |
| break; |
| } |
| |
| state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL); |
| status = cmd_parse_and_append(line, NULL, c, state, &error); |
| if (status == CMD_PARSE_ERROR) |
| cmdq_append(c, cmdq_get_callback(control_error, error)); |
| cmdq_free_state(state); |
| |
| free(line); |
| } |
| } |
| |
| /* Does this control client have outstanding data to write? */ |
| int |
| control_all_done(struct client *c) |
| { |
| struct control_state *cs = c->control_state; |
| |
| if (!TAILQ_EMPTY(&cs->all_blocks)) |
| return (0); |
| return (EVBUFFER_LENGTH(cs->write_event->output) == 0); |
| } |
| |
| /* Flush all blocks until output. */ |
| static void |
| control_flush_all_blocks(struct client *c) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_block *cb, *cb1; |
| |
| TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) { |
| if (cb->size != 0) |
| break; |
| log_debug("%s: %s: flushing line: %s", __func__, c->name, |
| cb->line); |
| |
| bufferevent_write(cs->write_event, cb->line, strlen(cb->line)); |
| bufferevent_write(cs->write_event, "\n", 1); |
| control_free_block(cs, cb); |
| } |
| } |
| |
| /* Append data to buffer. */ |
| static struct evbuffer * |
| control_append_data(struct control_pane *cp, struct evbuffer *message, |
| struct window_pane *wp, size_t size) |
| { |
| u_char *new_data; |
| size_t new_size; |
| u_int i; |
| |
| if (message == NULL) { |
| message = evbuffer_new(); |
| if (message == NULL) |
| fatalx("out of memory"); |
| evbuffer_add_printf(message, "%%output %%%u ", wp->id); |
| } |
| |
| new_data = window_pane_get_new_data(wp, &cp->offset, &new_size); |
| if (new_size < size) |
| fatalx("not enough data: %zu < %zu", new_size, size); |
| for (i = 0; i < size; i++) { |
| if (new_data[i] < ' ' || new_data[i] == '\\') |
| evbuffer_add_printf(message, "\\%03o", new_data[i]); |
| else |
| evbuffer_add_printf(message, "%c", new_data[i]); |
| } |
| window_pane_update_used_data(wp, &cp->offset, size); |
| return (message); |
| } |
| |
| /* Write buffer. */ |
| static void |
| control_write_data(struct client *c, struct evbuffer *message) |
| { |
| struct control_state *cs = c->control_state; |
| |
| log_debug("%s: %s: %.*s", __func__, c->name, |
| (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message)); |
| |
| evbuffer_add(message, "\n", 1); |
| bufferevent_write_buffer(cs->write_event, message); |
| evbuffer_free(message); |
| } |
| |
| /* Write output to client. */ |
| static int |
| control_write_pending(struct client *c, struct control_pane *cp, size_t limit) |
| { |
| struct control_state *cs = c->control_state; |
| struct session *s = c->session; |
| struct window_pane *wp = NULL; |
| struct evbuffer *message = NULL; |
| size_t used = 0, size; |
| struct control_block *cb, *cb1; |
| |
| if (s == NULL || |
| (wp = window_pane_find_by_id(cp->pane)) == NULL || |
| winlink_find_by_window(&s->windows, wp->window) == NULL) { |
| TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { |
| TAILQ_REMOVE(&cp->blocks, cb, entry); |
| control_free_block(cs, cb); |
| } |
| control_flush_all_blocks(c); |
| return (0); |
| } |
| |
| while (used != limit && !TAILQ_EMPTY(&cp->blocks)) { |
| cb = TAILQ_FIRST(&cp->blocks); |
| log_debug("%s: %s: output block %zu for %%%u (used %zu/%zu)", |
| __func__, c->name, cb->size, cp->pane, used, limit); |
| |
| size = cb->size; |
| if (size > limit - used) |
| size = limit - used; |
| used += size; |
| |
| message = control_append_data(cp, message, wp, size); |
| |
| cb->size -= size; |
| if (cb->size == 0) { |
| TAILQ_REMOVE(&cp->blocks, cb, entry); |
| control_free_block(cs, cb); |
| |
| cb = TAILQ_FIRST(&cs->all_blocks); |
| if (cb != NULL && cb->size == 0) { |
| if (wp != NULL && message != NULL) { |
| control_write_data(c, message); |
| message = NULL; |
| } |
| control_flush_all_blocks(c); |
| } |
| } |
| } |
| if (message != NULL) |
| control_write_data(c, message); |
| return (!TAILQ_EMPTY(&cp->blocks)); |
| } |
| |
| /* Control client write callback. */ |
| static void |
| control_write_callback(__unused struct bufferevent *bufev, void *data) |
| { |
| struct client *c = data; |
| struct control_state *cs = c->control_state; |
| struct control_pane *cp, *cp1; |
| struct evbuffer *evb = cs->write_event->output; |
| size_t space, limit; |
| |
| control_flush_all_blocks(c); |
| |
| while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) { |
| if (cs->pending_count == 0) |
| break; |
| space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb); |
| log_debug("%s: %s: %zu bytes available, %u panes", __func__, |
| c->name, space, cs->pending_count); |
| |
| limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */ |
| if (limit < CONTROL_WRITE_MINIMUM) |
| limit = CONTROL_WRITE_MINIMUM; |
| |
| TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) { |
| if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH) |
| break; |
| if (control_write_pending(c, cp, limit)) |
| continue; |
| TAILQ_REMOVE(&cs->pending_list, cp, pending_entry); |
| cp->pending_flag = 0; |
| cs->pending_count--; |
| } |
| } |
| if (EVBUFFER_LENGTH(evb) == 0) |
| bufferevent_disable(cs->write_event, EV_WRITE); |
| } |
| |
| /* Initialize for control mode. */ |
| void |
| control_start(struct client *c) |
| { |
| struct control_state *cs; |
| |
| if (c->flags & CLIENT_CONTROLCONTROL) { |
| close(c->out_fd); |
| c->out_fd = -1; |
| } else |
| setblocking(c->out_fd, 0); |
| setblocking(c->fd, 0); |
| |
| cs = c->control_state = xcalloc(1, sizeof *cs); |
| RB_INIT(&cs->panes); |
| TAILQ_INIT(&cs->pending_list); |
| TAILQ_INIT(&cs->all_blocks); |
| |
| cs->read_event = bufferevent_new(c->fd, control_read_callback, |
| control_write_callback, control_error_callback, c); |
| bufferevent_enable(cs->read_event, EV_READ); |
| |
| if (c->flags & CLIENT_CONTROLCONTROL) |
| cs->write_event = cs->read_event; |
| else { |
| cs->write_event = bufferevent_new(c->out_fd, NULL, |
| control_write_callback, control_error_callback, c); |
| } |
| bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, |
| 0); |
| |
| if (c->flags & CLIENT_CONTROLCONTROL) { |
| bufferevent_write(cs->write_event, "\033P1000p", 7); |
| bufferevent_enable(cs->write_event, EV_WRITE); |
| } |
| } |
| |
| /* Flush all output for a client that is detaching. */ |
| void |
| control_flush(struct client *c) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_pane *cp; |
| struct control_block *cb, *cb1; |
| |
| RB_FOREACH(cp, control_panes, &cs->panes) { |
| TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { |
| TAILQ_REMOVE(&cp->blocks, cb, entry); |
| control_free_block(cs, cb); |
| } |
| } |
| } |
| |
| /* Stop control mode. */ |
| void |
| control_stop(struct client *c) |
| { |
| struct control_state *cs = c->control_state; |
| struct control_block *cb, *cb1; |
| |
| if (~c->flags & CLIENT_CONTROLCONTROL) |
| bufferevent_free(cs->write_event); |
| bufferevent_free(cs->read_event); |
| |
| TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) |
| control_free_block(cs, cb); |
| control_reset_offsets(c); |
| |
| free(cs); |
| } |