=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/tmux/control.c,v retrieving revision 1.34 retrieving revision 1.35 diff -u -r1.34 -r1.35 --- src/usr.bin/tmux/control.c 2020/05/26 08:41:47 1.34 +++ src/usr.bin/tmux/control.c 2020/06/01 09:43:01 1.35 @@ -1,4 +1,4 @@ -/* $OpenBSD: control.c,v 1.34 2020/05/26 08:41:47 nicm Exp $ */ +/* $OpenBSD: control.c,v 1.35 2020/06/01 09:43:01 nicm Exp $ */ /* * Copyright (c) 2012 Nicholas Marriott @@ -27,112 +27,183 @@ #include "tmux.h" -/* Control client offset. */ -struct control_offset { - u_int pane; +/* + * 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; - struct window_pane_offset offset; - int flags; -#define CONTROL_OFFSET_OFF 0x1 + TAILQ_ENTRY(control_block) entry; + TAILQ_ENTRY(control_block) all_entry; + }; - RB_ENTRY(control_offset) 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_offsets, control_offset); +RB_HEAD(control_panes, control_pane); /* Control client state. */ struct control_state { - struct control_offsets offsets; + struct control_panes panes; - struct bufferevent *read_event; - struct bufferevent *write_event; + TAILQ_HEAD(, control_pane) pending_list; + u_int pending_count; + + TAILQ_HEAD(, control_block) all_blocks; + + struct bufferevent *read_event; + struct bufferevent *write_event; }; -/* Compare client offsets. */ +/* 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_offset_cmp(struct control_offset *co1, struct control_offset *co2) +control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2) { - if (co1->pane < co2->pane) + if (cp1->pane < cp2->pane) return (-1); - if (co1->pane > co2->pane) + if (cp1->pane > cp2->pane) return (1); return (0); } -RB_GENERATE_STATIC(control_offsets, control_offset, entry, control_offset_cmp); +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_offset * -control_get_offset(struct client *c, struct window_pane *wp) +static struct control_pane * +control_get_pane(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; - struct control_offset co = { .pane = wp->id }; + struct control_pane cp = { .pane = wp->id }; - return (RB_FIND(control_offsets, &cs->offsets, &co)); + return (RB_FIND(control_panes, &cs->panes, &cp)); } /* Add pane offsets for this client. */ -static struct control_offset * -control_add_offset(struct client *c, struct window_pane *wp) +static struct control_pane * +control_add_pane(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; - struct control_offset *co; + struct control_pane *cp; - co = control_get_offset(c, wp); - if (co != NULL) - return (co); + cp = control_get_pane(c, wp); + if (cp != NULL) + return (cp); - co = xcalloc(1, sizeof *co); - co->pane = wp->id; - RB_INSERT(control_offsets, &cs->offsets, co); - memcpy(&co->offset, &wp->offset, sizeof co->offset); - return (co); + 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); } -/* Free control offsets. */ +/* Reset control offsets. */ void -control_free_offsets(struct client *c) +control_reset_offsets(struct client *c) { struct control_state *cs = c->control_state; - struct control_offset *co, *co1; + struct control_pane *cp, *cp1; - RB_FOREACH_SAFE(co, control_offsets, &cs->offsets, co1) { - RB_REMOVE(control_offsets, &cs->offsets, co); - free(co); + 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_offset *co; + struct control_state *cs = c->control_state; + struct control_pane *cp; if (c->flags & CLIENT_CONTROL_NOOUTPUT) { *off = 0; return (NULL); } - co = control_get_offset(c, wp); - if (co == NULL) { + cp = control_get_pane(c, wp); + if (cp == NULL) { *off = 0; return (NULL); } - if (co->flags & CONTROL_OFFSET_OFF) { + if (cp->flags & CONTROL_PANE_OFF) { *off = 1; return (NULL); } - return (&co->offset); + *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_offset *co; + struct control_pane *cp; - co = control_get_offset(c, wp); - if (co != NULL) { - co->flags &= ~CONTROL_OFFSET_OFF; - memcpy(&co->offset, &wp->offset, sizeof co->offset); + 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); } } @@ -140,70 +211,104 @@ void control_set_pane_off(struct client *c, struct window_pane *wp) { - struct control_offset *co; + struct control_pane *cp; - co = control_add_offset(c, wp); - co->flags |= CONTROL_OFFSET_OFF; + cp = control_add_pane(c, wp); + cp->flags |= CONTROL_PANE_OFF; } /* Write a line. */ -void -control_write(struct client *c, const char *fmt, ...) +static void +control_vwrite(struct client *c, const char *fmt, va_list ap) { struct control_state *cs = c->control_state; - va_list ap; char *s; - va_start(ap, fmt); xvasprintf(&s, fmt, ap); - va_end(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_offset *co; - struct evbuffer *message; - u_char *new_data; - size_t new_size, i; + struct control_pane *cp; + struct control_block *cb; + size_t new_size; - if (c->flags & CLIENT_CONTROL_NOOUTPUT) - return; if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) return; - co = control_add_offset(c, wp); - if (co->flags & CONTROL_OFFSET_OFF) { - window_pane_update_used_data(wp, &co->offset, SIZE_MAX, 1); + if (c->flags & CONTROL_IGNORE_FLAGS) { + cp = control_get_pane(c, wp); + if (cp != NULL) + goto ignore; return; } - new_data = window_pane_get_new_data(wp, &co->offset, &new_size); + 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); - message = evbuffer_new(); - if (message == NULL) - fatalx("out of memory"); - evbuffer_add_printf(message, "%%output %%%u ", wp->id); + cb = xcalloc(1, sizeof *cb); + cb->size = new_size; + TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); - for (i = 0; i < new_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]); + 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++; } - evbuffer_add(message, "\n", 1); + bufferevent_enable(cs->write_event, EV_WRITE); + return; - bufferevent_write_buffer(cs->write_event, message); - evbuffer_free(message); - - window_pane_update_used_data(wp, &co->offset, new_size, 1); +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. */ @@ -246,8 +351,8 @@ line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); if (line == NULL) break; - log_debug("%s: %s", __func__, line); - if (*line == '\0') { /* empty line exit */ + log_debug("%s: %s: %s", __func__, c->name, line); + if (*line == '\0') { /* empty line detach */ free(line); c->flags |= CLIENT_EXIT; break; @@ -263,6 +368,168 @@ } } +/* 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) + 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) @@ -277,34 +544,59 @@ setblocking(c->fd, 0); cs = c->control_state = xcalloc(1, sizeof *cs); - RB_INIT(&cs->offsets); + RB_INIT(&cs->panes); + TAILQ_INIT(&cs->pending_list); + TAILQ_INIT(&cs->all_blocks); - cs->read_event = bufferevent_new(c->fd, control_read_callback, NULL, - control_error_callback, c); + 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, NULL, - control_error_callback, c); + cs->write_event = bufferevent_new(c->out_fd, NULL, + control_write_callback, control_error_callback, c); } - bufferevent_enable(cs->write_event, EV_WRITE); + bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, + 0); - if (c->flags & CLIENT_CONTROLCONTROL) - control_write(c, "\033P1000p"); + 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); - control_free_offsets(c); + TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) + control_free_block(cs, cb); + control_reset_offsets(c); + free(cs); }