|  | /* $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 <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; | 
|  | uint64_t			 t; | 
|  |  | 
|  | 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 | 
|  | #define CONTROL_PANE_PAUSED 0x2 | 
|  |  | 
|  | 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); | 
|  |  | 
|  | /* Subscription pane. */ | 
|  | struct control_sub_pane { | 
|  | u_int				 pane; | 
|  | u_int				 idx; | 
|  | char				*last; | 
|  |  | 
|  | RB_ENTRY(control_sub_pane)	 entry; | 
|  | }; | 
|  | RB_HEAD(control_sub_panes, control_sub_pane); | 
|  |  | 
|  | /* Subscription window. */ | 
|  | struct control_sub_window { | 
|  | u_int				 window; | 
|  | u_int				 idx; | 
|  | char				*last; | 
|  |  | 
|  | RB_ENTRY(control_sub_window)	 entry; | 
|  | }; | 
|  | RB_HEAD(control_sub_windows, control_sub_window); | 
|  |  | 
|  | /* Control client subscription. */ | 
|  | struct control_sub { | 
|  | char				*name; | 
|  | char				*format; | 
|  |  | 
|  | enum control_sub_type		 type; | 
|  | u_int				 id; | 
|  |  | 
|  | char				*last; | 
|  | struct control_sub_panes	 panes; | 
|  | struct control_sub_windows	 windows; | 
|  |  | 
|  | RB_ENTRY(control_sub)		 entry; | 
|  | }; | 
|  | RB_HEAD(control_subs, control_sub); | 
|  |  | 
|  | /* 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; | 
|  |  | 
|  | struct control_subs		 subs; | 
|  | struct event			 subs_timer; | 
|  | }; | 
|  |  | 
|  | /* Low and high watermarks. */ | 
|  | #define CONTROL_BUFFER_LOW 512 | 
|  | #define CONTROL_BUFFER_HIGH 8192 | 
|  |  | 
|  | /* Minimum to write to each client. */ | 
|  | #define CONTROL_WRITE_MINIMUM 32 | 
|  |  | 
|  | /* Maximum age for clients that are not using pause mode. */ | 
|  | #define CONTROL_MAXIMUM_AGE 300000 | 
|  |  | 
|  | /* 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); | 
|  |  | 
|  | /* Compare client subs. */ | 
|  | static int | 
|  | control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2) | 
|  | { | 
|  | return (strcmp(csub1->name, csub2->name)); | 
|  | } | 
|  | RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp); | 
|  |  | 
|  | /* Compare client subscription panes. */ | 
|  | static int | 
|  | control_sub_pane_cmp(struct control_sub_pane *csp1, | 
|  | struct control_sub_pane *csp2) | 
|  | { | 
|  | if (csp1->pane < csp2->pane) | 
|  | return (-1); | 
|  | if (csp1->pane > csp2->pane) | 
|  | return (1); | 
|  | if (csp1->idx < csp2->idx) | 
|  | return (-1); | 
|  | if (csp1->idx > csp2->idx) | 
|  | return (1); | 
|  | return (0); | 
|  | } | 
|  | RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry, | 
|  | control_sub_pane_cmp); | 
|  |  | 
|  | /* Compare client subscription windows. */ | 
|  | static int | 
|  | control_sub_window_cmp(struct control_sub_window *csw1, | 
|  | struct control_sub_window *csw2) | 
|  | { | 
|  | if (csw1->window < csw2->window) | 
|  | return (-1); | 
|  | if (csw1->window > csw2->window) | 
|  | return (1); | 
|  | if (csw1->idx < csw2->idx) | 
|  | return (-1); | 
|  | if (csw1->idx > csw2->idx) | 
|  | return (1); | 
|  | return (0); | 
|  | } | 
|  | RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry, | 
|  | control_sub_window_cmp); | 
|  |  | 
|  | /* Free a subscription. */ | 
|  | static void | 
|  | control_free_sub(struct control_state *cs, struct control_sub *csub) | 
|  | { | 
|  | struct control_sub_pane		*csp, *csp1; | 
|  | struct control_sub_window	*csw, *csw1; | 
|  |  | 
|  | RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) { | 
|  | RB_REMOVE(control_sub_panes, &csub->panes, csp); | 
|  | free(csp); | 
|  | } | 
|  | RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) { | 
|  | RB_REMOVE(control_sub_windows, &csub->windows, csw); | 
|  | free(csw); | 
|  | } | 
|  | free(csub->last); | 
|  |  | 
|  | RB_REMOVE(control_subs, &cs->subs, csub); | 
|  | free(csub->name); | 
|  | free(csub->format); | 
|  | free(csub); | 
|  | } | 
|  |  | 
|  | /* 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); | 
|  | } | 
|  |  | 
|  | /* Discard output for a pane. */ | 
|  | static void | 
|  | control_discard_pane(struct client *c, struct control_pane *cp) | 
|  | { | 
|  | struct control_state	*cs = c->control_state; | 
|  | struct control_block	*cb, *cb1; | 
|  |  | 
|  | TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { | 
|  | TAILQ_REMOVE(&cp->blocks, cb, entry); | 
|  | control_free_block(cs, cb); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Get actual pane for this client. */ | 
|  | static struct window_pane * | 
|  | control_window_pane(struct client *c, u_int pane) | 
|  | { | 
|  | struct window_pane	*wp; | 
|  |  | 
|  | if (c->session == NULL) | 
|  | return (NULL); | 
|  | if ((wp = window_pane_find_by_id(pane)) == NULL) | 
|  | return (NULL); | 
|  | if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) | 
|  | return (NULL); | 
|  | return (wp); | 
|  | } | 
|  |  | 
|  | /* 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 || (cp->flags & CONTROL_PANE_PAUSED)) { | 
|  | *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)) { | 
|  | 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; | 
|  | } | 
|  |  | 
|  | /* Continue a paused pane. */ | 
|  | void | 
|  | control_continue_pane(struct client *c, struct window_pane *wp) | 
|  | { | 
|  | struct control_pane	*cp; | 
|  |  | 
|  | cp = control_get_pane(c, wp); | 
|  | if (cp != NULL && (cp->flags & CONTROL_PANE_PAUSED)) { | 
|  | cp->flags &= ~CONTROL_PANE_PAUSED; | 
|  | memcpy(&cp->offset, &wp->offset, sizeof cp->offset); | 
|  | memcpy(&cp->queued, &wp->offset, sizeof cp->queued); | 
|  | control_write(c, "%%continue %%%u", wp->id); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Pause a pane. */ | 
|  | void | 
|  | control_pause_pane(struct client *c, struct window_pane *wp) | 
|  | { | 
|  | struct control_pane	*cp; | 
|  |  | 
|  | cp = control_add_pane(c, wp); | 
|  | if (~cp->flags & CONTROL_PANE_PAUSED) { | 
|  | cp->flags |= CONTROL_PANE_PAUSED; | 
|  | control_discard_pane(c, cp); | 
|  | control_write(c, "%%pause %%%u", wp->id); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Write a line. */ | 
|  | static void printflike(2, 0) | 
|  | 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); | 
|  | cb->t = get_timer(); | 
|  |  | 
|  | log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); | 
|  | bufferevent_enable(cs->write_event, EV_WRITE); | 
|  |  | 
|  | va_end(ap); | 
|  | } | 
|  |  | 
|  | /* Check age for this pane. */ | 
|  | static int | 
|  | control_check_age(struct client *c, struct window_pane *wp, | 
|  | struct control_pane *cp) | 
|  | { | 
|  | struct control_block	*cb; | 
|  | uint64_t		 t, age; | 
|  |  | 
|  | cb = TAILQ_FIRST(&cp->blocks); | 
|  | if (cb == NULL) | 
|  | return (0); | 
|  | t = get_timer(); | 
|  | if (cb->t >= t) | 
|  | return (0); | 
|  |  | 
|  | age = t - cb->t; | 
|  | log_debug("%s: %s: %%%u is %llu behind", __func__, c->name, wp->id, | 
|  | (unsigned long long)age); | 
|  |  | 
|  | if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { | 
|  | if (age < c->pause_age) | 
|  | return (0); | 
|  | cp->flags |= CONTROL_PANE_PAUSED; | 
|  | control_discard_pane(c, cp); | 
|  | control_write(c, "%%pause %%%u", wp->id); | 
|  | } else { | 
|  | if (age < CONTROL_MAXIMUM_AGE) | 
|  | return (0); | 
|  | c->exit_message = xstrdup("too far behind"); | 
|  | c->flags |= CLIENT_EXIT; | 
|  | control_discard(c); | 
|  | } | 
|  | return (1); | 
|  | } | 
|  |  | 
|  | /* 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|CONTROL_PANE_PAUSED)) | 
|  | goto ignore; | 
|  | if (control_check_age(c, wp, cp)) | 
|  | return; | 
|  |  | 
|  | 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); | 
|  | cb->t = get_timer(); | 
|  |  | 
|  | 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 client *c, struct control_pane *cp, uint64_t age, | 
|  | 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"); | 
|  | if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { | 
|  | evbuffer_add_printf(message, | 
|  | "%%extended-output %%%u %llu : ", wp->id, | 
|  | (unsigned long long)age); | 
|  | } else | 
|  | 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 window_pane	*wp = NULL; | 
|  | struct evbuffer		*message = NULL; | 
|  | size_t			 used = 0, size; | 
|  | struct control_block	*cb, *cb1; | 
|  | uint64_t		 age, t = get_timer(); | 
|  |  | 
|  | wp = control_window_pane(c, cp->pane); | 
|  | if (wp == NULL || wp->fd == -1) { | 
|  | 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)) { | 
|  | if (control_check_age(c, wp, cp)) { | 
|  | if (message != NULL) | 
|  | evbuffer_free(message); | 
|  | message = NULL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | cb = TAILQ_FIRST(&cp->blocks); | 
|  | if (cb->t < t) | 
|  | age = t - cb->t; | 
|  | else | 
|  | age = 0; | 
|  | log_debug("%s: %s: output block %zu (age %llu) for %%%u " | 
|  | "(used %zu/%zu)", __func__, c->name, cb->size, | 
|  | (unsigned long long)age, cp->pane, used, limit); | 
|  |  | 
|  | size = cb->size; | 
|  | if (size > limit - used) | 
|  | size = limit - used; | 
|  | used += size; | 
|  |  | 
|  | message = control_append_data(c, cp, age, 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); | 
|  | RB_INIT(&cs->subs); | 
|  |  | 
|  | 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Discard all output for a client. */ | 
|  | void | 
|  | control_discard(struct client *c) | 
|  | { | 
|  | struct control_state	*cs = c->control_state; | 
|  | struct control_pane	*cp; | 
|  |  | 
|  | RB_FOREACH(cp, control_panes, &cs->panes) | 
|  | control_discard_pane(c, cp); | 
|  | bufferevent_disable(cs->read_event, EV_READ); | 
|  | } | 
|  |  | 
|  | /* Stop control mode. */ | 
|  | void | 
|  | control_stop(struct client *c) | 
|  | { | 
|  | struct control_state	*cs = c->control_state; | 
|  | struct control_block	*cb, *cb1; | 
|  | struct control_sub	*csub, *csub1; | 
|  |  | 
|  | if (~c->flags & CLIENT_CONTROLCONTROL) | 
|  | bufferevent_free(cs->write_event); | 
|  | bufferevent_free(cs->read_event); | 
|  |  | 
|  | RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) | 
|  | control_free_sub(cs, csub); | 
|  | if (evtimer_initialized(&cs->subs_timer)) | 
|  | evtimer_del(&cs->subs_timer); | 
|  |  | 
|  | TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) | 
|  | control_free_block(cs, cb); | 
|  | control_reset_offsets(c); | 
|  |  | 
|  | free(cs); | 
|  | } | 
|  |  | 
|  | /* Check session subscription. */ | 
|  | static void | 
|  | control_check_subs_session(struct client *c, struct control_sub *csub) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | struct format_tree	*ft; | 
|  | char			*value; | 
|  |  | 
|  | ft = format_create_defaults(NULL, c, s, NULL, NULL); | 
|  | value = format_expand(ft, csub->format); | 
|  | format_free(ft); | 
|  |  | 
|  | if (csub->last != NULL && strcmp(value, csub->last) == 0) { | 
|  | free(value); | 
|  | return; | 
|  | } | 
|  | control_write(c, | 
|  | "%%subscription-changed %s $%u - - - : %s", | 
|  | csub->name, s->id, value); | 
|  | free(csub->last); | 
|  | csub->last = value; | 
|  | } | 
|  |  | 
|  | /* Check pane subscription. */ | 
|  | static void | 
|  | control_check_subs_pane(struct client *c, struct control_sub *csub) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | struct window_pane	*wp; | 
|  | struct window		*w; | 
|  | struct winlink		*wl; | 
|  | struct format_tree	*ft; | 
|  | char			*value; | 
|  | struct control_sub_pane	*csp, find; | 
|  |  | 
|  | wp = window_pane_find_by_id(csub->id); | 
|  | if (wp == NULL || wp->fd == -1) | 
|  | return; | 
|  | w = wp->window; | 
|  |  | 
|  | TAILQ_FOREACH(wl, &w->winlinks, wentry) { | 
|  | if (wl->session != s) | 
|  | continue; | 
|  |  | 
|  | ft = format_create_defaults(NULL, c, s, wl, wp); | 
|  | value = format_expand(ft, csub->format); | 
|  | format_free(ft); | 
|  |  | 
|  | find.pane = wp->id; | 
|  | find.idx = wl->idx; | 
|  |  | 
|  | csp = RB_FIND(control_sub_panes, &csub->panes, &find); | 
|  | if (csp == NULL) { | 
|  | csp = xcalloc(1, sizeof *csp); | 
|  | csp->pane = wp->id; | 
|  | csp->idx = wl->idx; | 
|  | RB_INSERT(control_sub_panes, &csub->panes, csp); | 
|  | } | 
|  |  | 
|  | if (csp->last != NULL && strcmp(value, csp->last) == 0) { | 
|  | free(value); | 
|  | continue; | 
|  | } | 
|  | control_write(c, | 
|  | "%%subscription-changed %s $%u @%u %u %%%u : %s", | 
|  | csub->name, s->id, w->id, wl->idx, wp->id, value); | 
|  | free(csp->last); | 
|  | csp->last = value; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check all panes subscription. */ | 
|  | static void | 
|  | control_check_subs_all_panes(struct client *c, struct control_sub *csub) | 
|  | { | 
|  | struct session		*s = c->session; | 
|  | struct window_pane	*wp; | 
|  | struct window		*w; | 
|  | struct winlink		*wl; | 
|  | struct format_tree	*ft; | 
|  | char			*value; | 
|  | struct control_sub_pane	*csp, find; | 
|  |  | 
|  | RB_FOREACH(wl, winlinks, &s->windows) { | 
|  | w = wl->window; | 
|  | TAILQ_FOREACH(wp, &w->panes, entry) { | 
|  | ft = format_create_defaults(NULL, c, s, wl, wp); | 
|  | value = format_expand(ft, csub->format); | 
|  | format_free(ft); | 
|  |  | 
|  | find.pane = wp->id; | 
|  | find.idx = wl->idx; | 
|  |  | 
|  | csp = RB_FIND(control_sub_panes, &csub->panes, &find); | 
|  | if (csp == NULL) { | 
|  | csp = xcalloc(1, sizeof *csp); | 
|  | csp->pane = wp->id; | 
|  | csp->idx = wl->idx; | 
|  | RB_INSERT(control_sub_panes, &csub->panes, csp); | 
|  | } | 
|  |  | 
|  | if (csp->last != NULL && | 
|  | strcmp(value, csp->last) == 0) { | 
|  | free(value); | 
|  | continue; | 
|  | } | 
|  | control_write(c, | 
|  | "%%subscription-changed %s $%u @%u %u %%%u : %s", | 
|  | csub->name, s->id, w->id, wl->idx, wp->id, value); | 
|  | free(csp->last); | 
|  | csp->last = value; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check window subscription. */ | 
|  | static void | 
|  | control_check_subs_window(struct client *c, struct control_sub *csub) | 
|  | { | 
|  | struct session			*s = c->session; | 
|  | struct window			*w; | 
|  | struct winlink			*wl; | 
|  | struct format_tree		*ft; | 
|  | char				*value; | 
|  | struct control_sub_window	*csw, find; | 
|  |  | 
|  | w = window_find_by_id(csub->id); | 
|  | if (w == NULL) | 
|  | return; | 
|  |  | 
|  | TAILQ_FOREACH(wl, &w->winlinks, wentry) { | 
|  | if (wl->session != s) | 
|  | continue; | 
|  |  | 
|  | ft = format_create_defaults(NULL, c, s, wl, NULL); | 
|  | value = format_expand(ft, csub->format); | 
|  | format_free(ft); | 
|  |  | 
|  | find.window = w->id; | 
|  | find.idx = wl->idx; | 
|  |  | 
|  | csw = RB_FIND(control_sub_windows, &csub->windows, &find); | 
|  | if (csw == NULL) { | 
|  | csw = xcalloc(1, sizeof *csw); | 
|  | csw->window = w->id; | 
|  | csw->idx = wl->idx; | 
|  | RB_INSERT(control_sub_windows, &csub->windows, csw); | 
|  | } | 
|  |  | 
|  | if (csw->last != NULL && strcmp(value, csw->last) == 0) { | 
|  | free(value); | 
|  | continue; | 
|  | } | 
|  | control_write(c, | 
|  | "%%subscription-changed %s $%u @%u %u - : %s", | 
|  | csub->name, s->id, w->id, wl->idx, value); | 
|  | free(csw->last); | 
|  | csw->last = value; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check all windows subscription. */ | 
|  | static void | 
|  | control_check_subs_all_windows(struct client *c, struct control_sub *csub) | 
|  | { | 
|  | struct session			*s = c->session; | 
|  | struct window			*w; | 
|  | struct winlink			*wl; | 
|  | struct format_tree		*ft; | 
|  | char				*value; | 
|  | struct control_sub_window	*csw, find; | 
|  |  | 
|  | RB_FOREACH(wl, winlinks, &s->windows) { | 
|  | w = wl->window; | 
|  |  | 
|  | ft = format_create_defaults(NULL, c, s, wl, NULL); | 
|  | value = format_expand(ft, csub->format); | 
|  | format_free(ft); | 
|  |  | 
|  | find.window = w->id; | 
|  | find.idx = wl->idx; | 
|  |  | 
|  | csw = RB_FIND(control_sub_windows, &csub->windows, &find); | 
|  | if (csw == NULL) { | 
|  | csw = xcalloc(1, sizeof *csw); | 
|  | csw->window = w->id; | 
|  | csw->idx = wl->idx; | 
|  | RB_INSERT(control_sub_windows, &csub->windows, csw); | 
|  | } | 
|  |  | 
|  | if (csw->last != NULL && strcmp(value, csw->last) == 0) { | 
|  | free(value); | 
|  | continue; | 
|  | } | 
|  | control_write(c, | 
|  | "%%subscription-changed %s $%u @%u %u - : %s", | 
|  | csub->name, s->id, w->id, wl->idx, value); | 
|  | free(csw->last); | 
|  | csw->last = value; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Check subscriptions timer. */ | 
|  | static void | 
|  | control_check_subs_timer(__unused int fd, __unused short events, void *data) | 
|  | { | 
|  | struct client		*c = data; | 
|  | struct control_state	*cs = c->control_state; | 
|  | struct control_sub	*csub, *csub1; | 
|  | struct timeval		 tv = { .tv_sec = 1 }; | 
|  |  | 
|  | log_debug("%s: timer fired", __func__); | 
|  | evtimer_add(&cs->subs_timer, &tv); | 
|  |  | 
|  | RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) { | 
|  | switch (csub->type) { | 
|  | case CONTROL_SUB_SESSION: | 
|  | control_check_subs_session(c, csub); | 
|  | break; | 
|  | case CONTROL_SUB_PANE: | 
|  | control_check_subs_pane(c, csub); | 
|  | break; | 
|  | case CONTROL_SUB_ALL_PANES: | 
|  | control_check_subs_all_panes(c, csub); | 
|  | break; | 
|  | case CONTROL_SUB_WINDOW: | 
|  | control_check_subs_window(c, csub); | 
|  | break; | 
|  | case CONTROL_SUB_ALL_WINDOWS: | 
|  | control_check_subs_all_windows(c, csub); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Add a subscription. */ | 
|  | void | 
|  | control_add_sub(struct client *c, const char *name, enum control_sub_type type, | 
|  | int id, const char *format) | 
|  | { | 
|  | struct control_state	*cs = c->control_state; | 
|  | struct control_sub	*csub, find; | 
|  | struct timeval		 tv = { .tv_sec = 1 }; | 
|  |  | 
|  | find.name = (char *)name; | 
|  | if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL) | 
|  | control_free_sub(cs, csub); | 
|  |  | 
|  | csub = xcalloc(1, sizeof *csub); | 
|  | csub->name = xstrdup(name); | 
|  | csub->type = type; | 
|  | csub->id = id; | 
|  | csub->format = xstrdup(format); | 
|  | RB_INSERT(control_subs, &cs->subs, csub); | 
|  |  | 
|  | RB_INIT(&csub->panes); | 
|  | RB_INIT(&csub->windows); | 
|  |  | 
|  | if (!evtimer_initialized(&cs->subs_timer)) | 
|  | evtimer_set(&cs->subs_timer, control_check_subs_timer, c); | 
|  | if (!evtimer_pending(&cs->subs_timer, NULL)) | 
|  | evtimer_add(&cs->subs_timer, &tv); | 
|  | } | 
|  |  | 
|  | /* Remove a subscription. */ | 
|  | void | 
|  | control_remove_sub(struct client *c, const char *name) | 
|  | { | 
|  | struct control_state	*cs = c->control_state; | 
|  | struct control_sub	*csub, find; | 
|  |  | 
|  | find.name = (char *)name; | 
|  | if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL) | 
|  | control_free_sub(cs, csub); | 
|  | if (RB_EMPTY(&cs->subs)) | 
|  | evtimer_del(&cs->subs_timer); | 
|  | } |