| /* $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; | 
 | 	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 | 
 | 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) { | 
 | 		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) | 
 | 		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); | 
 | } |