Annotation of src/usr.bin/tmux/control.c, Revision 1.35
1.35 ! nicm 1: /* $OpenBSD: control.c,v 1.34 2020/05/26 08:41:47 nicm Exp $ */
1.1 nicm 2:
3: /*
1.17 nicm 4: * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
1.1 nicm 5: * Copyright (c) 2012 George Nachman <tmux@georgester.com>
6: *
7: * Permission to use, copy, modify, and distribute this software for any
8: * purpose with or without fee is hereby granted, provided that the above
9: * copyright notice and this permission notice appear in all copies.
10: *
11: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15: * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16: * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17: * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18: */
19:
20: #include <sys/types.h>
21:
22: #include <event.h>
1.2 nicm 23: #include <stdlib.h>
1.1 nicm 24: #include <string.h>
1.10 nicm 25: #include <time.h>
1.34 nicm 26: #include <unistd.h>
1.1 nicm 27:
28: #include "tmux.h"
29:
1.35 ! nicm 30: /*
! 31: * Block of data to output. Each client has one "all" queue of blocks and
! 32: * another queue for each pane (in struct client_offset). %output blocks are
! 33: * added to both queues and other output lines (notifications) added only to
! 34: * the client queue.
! 35: *
! 36: * When a client becomes writeable, data from blocks on the pane queue are sent
! 37: * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written,
! 38: * it is removed from both pane and client queues and if this means non-%output
! 39: * blocks are now at the head of the client queue, they are written.
! 40: *
! 41: * This means a %output block holds up any subsequent non-%output blocks until
! 42: * it is written which enforces ordering even if the client cannot accept the
! 43: * entire block in one go.
! 44: */
! 45: struct control_block {
! 46: size_t size;
! 47: char *line;
! 48:
! 49: TAILQ_ENTRY(control_block) entry;
! 50: TAILQ_ENTRY(control_block) all_entry;
! 51: };
! 52:
! 53: /* Control client pane. */
! 54: struct control_pane {
! 55: u_int pane;
! 56:
! 57: /*
! 58: * Offsets into the pane data. The first (offset) is the data we have
! 59: * written; the second (queued) the data we have queued (pointed to by
! 60: * a block).
! 61: */
! 62: struct window_pane_offset offset;
! 63: struct window_pane_offset queued;
! 64:
! 65: int flags;
! 66: #define CONTROL_PANE_OFF 0x1
1.32 nicm 67:
1.35 ! nicm 68: int pending_flag;
! 69: TAILQ_ENTRY(control_pane) pending_entry;
! 70:
! 71: TAILQ_HEAD(, control_block) blocks;
! 72:
! 73: RB_ENTRY(control_pane) entry;
1.32 nicm 74: };
1.35 ! nicm 75: RB_HEAD(control_panes, control_pane);
1.32 nicm 76:
1.34 nicm 77: /* Control client state. */
1.33 nicm 78: struct control_state {
1.35 ! nicm 79: struct control_panes panes;
! 80:
! 81: TAILQ_HEAD(, control_pane) pending_list;
! 82: u_int pending_count;
! 83:
! 84: TAILQ_HEAD(, control_block) all_blocks;
1.34 nicm 85:
1.35 ! nicm 86: struct bufferevent *read_event;
! 87: struct bufferevent *write_event;
1.33 nicm 88: };
89:
1.35 ! nicm 90: /* Low watermark. */
! 91: #define CONTROL_BUFFER_LOW 512
! 92: #define CONTROL_BUFFER_HIGH 8192
! 93:
! 94: /* Minimum to write to each client. */
! 95: #define CONTROL_WRITE_MINIMUM 32
! 96:
! 97: /* Flags to ignore client. */
! 98: #define CONTROL_IGNORE_FLAGS \
! 99: (CLIENT_CONTROL_NOOUTPUT| \
! 100: CLIENT_UNATTACHEDFLAGS)
! 101:
! 102: /* Compare client panes. */
1.32 nicm 103: static int
1.35 ! nicm 104: control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
1.32 nicm 105: {
1.35 ! nicm 106: if (cp1->pane < cp2->pane)
1.32 nicm 107: return (-1);
1.35 ! nicm 108: if (cp1->pane > cp2->pane)
1.32 nicm 109: return (1);
110: return (0);
111: }
1.35 ! nicm 112: RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
! 113:
! 114: /* Free a block. */
! 115: static void
! 116: control_free_block(struct control_state *cs, struct control_block *cb)
! 117: {
! 118: free(cb->line);
! 119: TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
! 120: free(cb);
! 121: }
1.32 nicm 122:
123: /* Get pane offsets for this client. */
1.35 ! nicm 124: static struct control_pane *
! 125: control_get_pane(struct client *c, struct window_pane *wp)
1.32 nicm 126: {
1.33 nicm 127: struct control_state *cs = c->control_state;
1.35 ! nicm 128: struct control_pane cp = { .pane = wp->id };
1.32 nicm 129:
1.35 ! nicm 130: return (RB_FIND(control_panes, &cs->panes, &cp));
1.32 nicm 131: }
132:
133: /* Add pane offsets for this client. */
1.35 ! nicm 134: static struct control_pane *
! 135: control_add_pane(struct client *c, struct window_pane *wp)
1.32 nicm 136: {
1.33 nicm 137: struct control_state *cs = c->control_state;
1.35 ! nicm 138: struct control_pane *cp;
! 139:
! 140: cp = control_get_pane(c, wp);
! 141: if (cp != NULL)
! 142: return (cp);
1.32 nicm 143:
1.35 ! nicm 144: cp = xcalloc(1, sizeof *cp);
! 145: cp->pane = wp->id;
! 146: RB_INSERT(control_panes, &cs->panes, cp);
1.32 nicm 147:
1.35 ! nicm 148: memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
! 149: memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
! 150: TAILQ_INIT(&cp->blocks);
! 151:
! 152: return (cp);
1.32 nicm 153: }
154:
1.35 ! nicm 155: /* Reset control offsets. */
1.32 nicm 156: void
1.35 ! nicm 157: control_reset_offsets(struct client *c)
1.32 nicm 158: {
1.33 nicm 159: struct control_state *cs = c->control_state;
1.35 ! nicm 160: struct control_pane *cp, *cp1;
1.32 nicm 161:
1.35 ! nicm 162: RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) {
! 163: RB_REMOVE(control_panes, &cs->panes, cp);
! 164: free(cp);
1.32 nicm 165: }
1.35 ! nicm 166:
! 167: TAILQ_INIT(&cs->pending_list);
! 168: cs->pending_count = 0;
1.32 nicm 169: }
170:
171: /* Get offsets for client. */
172: struct window_pane_offset *
173: control_pane_offset(struct client *c, struct window_pane *wp, int *off)
174: {
1.35 ! nicm 175: struct control_state *cs = c->control_state;
! 176: struct control_pane *cp;
1.32 nicm 177:
178: if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
179: *off = 0;
180: return (NULL);
181: }
182:
1.35 ! nicm 183: cp = control_get_pane(c, wp);
! 184: if (cp == NULL) {
1.32 nicm 185: *off = 0;
186: return (NULL);
187: }
1.35 ! nicm 188: if (cp->flags & CONTROL_PANE_OFF) {
1.32 nicm 189: *off = 1;
190: return (NULL);
191: }
1.35 ! nicm 192: *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW);
! 193: return (&cp->offset);
1.32 nicm 194: }
195:
196: /* Set pane as on. */
197: void
198: control_set_pane_on(struct client *c, struct window_pane *wp)
199: {
1.35 ! nicm 200: struct control_pane *cp;
1.32 nicm 201:
1.35 ! nicm 202: cp = control_get_pane(c, wp);
! 203: if (cp != NULL) {
! 204: cp->flags &= ~CONTROL_PANE_OFF;
! 205: memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
! 206: memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
1.32 nicm 207: }
208: }
209:
210: /* Set pane as off. */
211: void
212: control_set_pane_off(struct client *c, struct window_pane *wp)
213: {
1.35 ! nicm 214: struct control_pane *cp;
1.32 nicm 215:
1.35 ! nicm 216: cp = control_add_pane(c, wp);
! 217: cp->flags |= CONTROL_PANE_OFF;
1.32 nicm 218: }
219:
1.1 nicm 220: /* Write a line. */
1.35 ! nicm 221: static void
! 222: control_vwrite(struct client *c, const char *fmt, va_list ap)
1.1 nicm 223: {
1.34 nicm 224: struct control_state *cs = c->control_state;
225: char *s;
1.1 nicm 226:
1.34 nicm 227: xvasprintf(&s, fmt, ap);
1.35 ! nicm 228: log_debug("%s: %s: writing line: %s", __func__, c->name, s);
1.34 nicm 229:
230: bufferevent_write(cs->write_event, s, strlen(s));
231: bufferevent_write(cs->write_event, "\n", 1);
1.35 ! nicm 232:
! 233: bufferevent_enable(cs->write_event, EV_WRITE);
1.34 nicm 234: free(s);
1.31 nicm 235: }
236:
1.35 ! nicm 237: /* Write a line. */
! 238: void
! 239: control_write(struct client *c, const char *fmt, ...)
! 240: {
! 241: struct control_state *cs = c->control_state;
! 242: struct control_block *cb;
! 243: va_list ap;
! 244:
! 245: va_start(ap, fmt);
! 246:
! 247: if (TAILQ_EMPTY(&cs->all_blocks)) {
! 248: control_vwrite(c, fmt, ap);
! 249: va_end(ap);
! 250: return;
! 251: }
! 252:
! 253: cb = xcalloc(1, sizeof *cb);
! 254: xvasprintf(&cb->line, fmt, ap);
! 255: TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
! 256:
! 257: log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
! 258: bufferevent_enable(cs->write_event, EV_WRITE);
! 259:
! 260: va_end(ap);
! 261: }
! 262:
1.31 nicm 263: /* Write output from a pane. */
264: void
265: control_write_output(struct client *c, struct window_pane *wp)
266: {
1.34 nicm 267: struct control_state *cs = c->control_state;
1.35 ! nicm 268: struct control_pane *cp;
! 269: struct control_block *cb;
! 270: size_t new_size;
1.31 nicm 271:
272: if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
273: return;
274:
1.35 ! nicm 275: if (c->flags & CONTROL_IGNORE_FLAGS) {
! 276: cp = control_get_pane(c, wp);
! 277: if (cp != NULL)
! 278: goto ignore;
1.31 nicm 279: return;
280: }
1.35 ! nicm 281: cp = control_add_pane(c, wp);
! 282: if (cp->flags & CONTROL_PANE_OFF)
! 283: goto ignore;
! 284:
! 285: window_pane_get_new_data(wp, &cp->queued, &new_size);
1.31 nicm 286: if (new_size == 0)
287: return;
1.35 ! nicm 288: window_pane_update_used_data(wp, &cp->queued, new_size);
1.31 nicm 289:
1.35 ! nicm 290: cb = xcalloc(1, sizeof *cb);
! 291: cb->size = new_size;
! 292: TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
! 293:
! 294: TAILQ_INSERT_TAIL(&cp->blocks, cb, entry);
! 295: log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name,
! 296: cb->size, wp->id);
! 297:
! 298: if (!cp->pending_flag) {
! 299: log_debug("%s: %s: %%%u now pending", __func__, c->name,
! 300: wp->id);
! 301: TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry);
! 302: cp->pending_flag = 1;
! 303: cs->pending_count++;
1.31 nicm 304: }
1.35 ! nicm 305: bufferevent_enable(cs->write_event, EV_WRITE);
! 306: return;
1.31 nicm 307:
1.35 ! nicm 308: ignore:
! 309: log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id);
! 310: window_pane_update_used_data(wp, &cp->offset, SIZE_MAX);
! 311: window_pane_update_used_data(wp, &cp->queued, SIZE_MAX);
1.1 nicm 312: }
313:
1.34 nicm 314: /* Control client error callback. */
1.18 nicm 315: static enum cmd_retval
1.19 nicm 316: control_error(struct cmdq_item *item, void *data)
1.18 nicm 317: {
1.26 nicm 318: struct client *c = cmdq_get_client(item);
1.18 nicm 319: char *error = data;
320:
1.19 nicm 321: cmdq_guard(item, "begin", 1);
1.18 nicm 322: control_write(c, "parse error: %s", error);
1.19 nicm 323: cmdq_guard(item, "error", 1);
1.18 nicm 324:
325: free(error);
326: return (CMD_RETURN_NORMAL);
327: }
328:
1.34 nicm 329: /* Control client error callback. */
1.25 nicm 330: static void
1.34 nicm 331: control_error_callback(__unused struct bufferevent *bufev,
332: __unused short what, void *data)
1.1 nicm 333: {
1.34 nicm 334: struct client *c = data;
335:
336: c->flags |= CLIENT_EXIT;
337: }
338:
339: /* Control client input callback. Read lines and fire commands. */
340: static void
341: control_read_callback(__unused struct bufferevent *bufev, void *data)
342: {
343: struct client *c = data;
344: struct control_state *cs = c->control_state;
345: struct evbuffer *buffer = cs->read_event->input;
1.30 nicm 346: char *line, *error;
1.29 nicm 347: struct cmdq_state *state;
1.30 nicm 348: enum cmd_parse_status status;
1.1 nicm 349:
350: for (;;) {
1.25 nicm 351: line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
1.1 nicm 352: if (line == NULL)
353: break;
1.35 ! nicm 354: log_debug("%s: %s: %s", __func__, c->name, line);
! 355: if (*line == '\0') { /* empty line detach */
1.24 nicm 356: free(line);
1.1 nicm 357: c->flags |= CLIENT_EXIT;
358: break;
359: }
360:
1.30 nicm 361: state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
362: status = cmd_parse_and_append(line, NULL, c, state, &error);
363: if (status == CMD_PARSE_ERROR)
364: cmdq_append(c, cmdq_get_callback(control_error, error));
365: cmdq_free_state(state);
1.1 nicm 366:
1.2 nicm 367: free(line);
1.1 nicm 368: }
1.25 nicm 369: }
370:
1.35 ! nicm 371: /* Does this control client have outstanding data to write? */
! 372: int
! 373: control_all_done(struct client *c)
! 374: {
! 375: struct control_state *cs = c->control_state;
! 376:
! 377: if (!TAILQ_EMPTY(&cs->all_blocks))
! 378: return (0);
! 379: return (EVBUFFER_LENGTH(cs->write_event->output) == 0);
! 380: }
! 381:
! 382: /* Flush all blocks until output. */
! 383: static void
! 384: control_flush_all_blocks(struct client *c)
! 385: {
! 386: struct control_state *cs = c->control_state;
! 387: struct control_block *cb, *cb1;
! 388:
! 389: TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) {
! 390: if (cb->size != 0)
! 391: break;
! 392: log_debug("%s: %s: flushing line: %s", __func__, c->name,
! 393: cb->line);
! 394:
! 395: bufferevent_write(cs->write_event, cb->line, strlen(cb->line));
! 396: bufferevent_write(cs->write_event, "\n", 1);
! 397: control_free_block(cs, cb);
! 398: }
! 399: }
! 400:
! 401: /* Append data to buffer. */
! 402: static struct evbuffer *
! 403: control_append_data(struct control_pane *cp, struct evbuffer *message,
! 404: struct window_pane *wp, size_t size)
! 405: {
! 406: u_char *new_data;
! 407: size_t new_size;
! 408: u_int i;
! 409:
! 410: if (message == NULL) {
! 411: message = evbuffer_new();
! 412: if (message == NULL)
! 413: fatalx("out of memory");
! 414: evbuffer_add_printf(message, "%%output %%%u ", wp->id);
! 415: }
! 416:
! 417: new_data = window_pane_get_new_data(wp, &cp->offset, &new_size);
! 418: if (new_size < size)
! 419: fatalx("not enough data: %zu < %zu", new_size, size);
! 420: for (i = 0; i < size; i++) {
! 421: if (new_data[i] < ' ' || new_data[i] == '\\')
! 422: evbuffer_add_printf(message, "\\%03o", new_data[i]);
! 423: else
! 424: evbuffer_add_printf(message, "%c", new_data[i]);
! 425: }
! 426: window_pane_update_used_data(wp, &cp->offset, size);
! 427: return (message);
! 428: }
! 429:
! 430: /* Write buffer. */
! 431: static void
! 432: control_write_data(struct client *c, struct evbuffer *message)
! 433: {
! 434: struct control_state *cs = c->control_state;
! 435:
! 436: log_debug("%s: %s: %.*s", __func__, c->name,
! 437: (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message));
! 438:
! 439: evbuffer_add(message, "\n", 1);
! 440: bufferevent_write_buffer(cs->write_event, message);
! 441: evbuffer_free(message);
! 442: }
! 443:
! 444: /* Write output to client. */
! 445: static int
! 446: control_write_pending(struct client *c, struct control_pane *cp, size_t limit)
! 447: {
! 448: struct control_state *cs = c->control_state;
! 449: struct session *s = c->session;
! 450: struct window_pane *wp = NULL;
! 451: struct evbuffer *message = NULL;
! 452: size_t used = 0, size;
! 453: struct control_block *cb, *cb1;
! 454:
! 455: if (s == NULL ||
! 456: (wp = window_pane_find_by_id(cp->pane)) == NULL ||
! 457: winlink_find_by_window(&s->windows, wp->window) == NULL) {
! 458: TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1)
! 459: control_free_block(cs, cb);
! 460: control_flush_all_blocks(c);
! 461: return (0);
! 462: }
! 463:
! 464: while (used != limit && !TAILQ_EMPTY(&cp->blocks)) {
! 465: cb = TAILQ_FIRST(&cp->blocks);
! 466: log_debug("%s: %s: output block %zu for %%%u (used %zu/%zu)",
! 467: __func__, c->name, cb->size, cp->pane, used, limit);
! 468:
! 469: size = cb->size;
! 470: if (size > limit - used)
! 471: size = limit - used;
! 472: used += size;
! 473:
! 474: message = control_append_data(cp, message, wp, size);
! 475:
! 476: cb->size -= size;
! 477: if (cb->size == 0) {
! 478: TAILQ_REMOVE(&cp->blocks, cb, entry);
! 479: control_free_block(cs, cb);
! 480:
! 481: cb = TAILQ_FIRST(&cs->all_blocks);
! 482: if (cb != NULL && cb->size == 0) {
! 483: if (wp != NULL && message != NULL) {
! 484: control_write_data(c, message);
! 485: message = NULL;
! 486: }
! 487: control_flush_all_blocks(c);
! 488: }
! 489: }
! 490: }
! 491: if (message != NULL)
! 492: control_write_data(c, message);
! 493: return (!TAILQ_EMPTY(&cp->blocks));
! 494: }
! 495:
! 496: /* Control client write callback. */
! 497: static void
! 498: control_write_callback(__unused struct bufferevent *bufev, void *data)
! 499: {
! 500: struct client *c = data;
! 501: struct control_state *cs = c->control_state;
! 502: struct control_pane *cp, *cp1;
! 503: struct evbuffer *evb = cs->write_event->output;
! 504: size_t space, limit;
! 505:
! 506: control_flush_all_blocks(c);
! 507:
! 508: while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) {
! 509: if (cs->pending_count == 0)
! 510: break;
! 511: space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb);
! 512: log_debug("%s: %s: %zu bytes available, %u panes", __func__,
! 513: c->name, space, cs->pending_count);
! 514:
! 515: limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */
! 516: if (limit < CONTROL_WRITE_MINIMUM)
! 517: limit = CONTROL_WRITE_MINIMUM;
! 518:
! 519: TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) {
! 520: if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH)
! 521: break;
! 522: if (control_write_pending(c, cp, limit))
! 523: continue;
! 524: TAILQ_REMOVE(&cs->pending_list, cp, pending_entry);
! 525: cp->pending_flag = 0;
! 526: cs->pending_count--;
! 527: }
! 528: }
! 529: if (EVBUFFER_LENGTH(evb) == 0)
! 530: bufferevent_disable(cs->write_event, EV_WRITE);
! 531: }
! 532:
1.32 nicm 533: /* Initialize for control mode. */
1.25 nicm 534: void
535: control_start(struct client *c)
536: {
1.33 nicm 537: struct control_state *cs;
538:
1.34 nicm 539: if (c->flags & CLIENT_CONTROLCONTROL) {
540: close(c->out_fd);
541: c->out_fd = -1;
542: } else
543: setblocking(c->out_fd, 0);
544: setblocking(c->fd, 0);
545:
1.33 nicm 546: cs = c->control_state = xcalloc(1, sizeof *cs);
1.35 ! nicm 547: RB_INIT(&cs->panes);
! 548: TAILQ_INIT(&cs->pending_list);
! 549: TAILQ_INIT(&cs->all_blocks);
1.33 nicm 550:
1.35 ! nicm 551: cs->read_event = bufferevent_new(c->fd, control_read_callback,
! 552: control_write_callback, control_error_callback, c);
1.34 nicm 553: bufferevent_enable(cs->read_event, EV_READ);
1.25 nicm 554:
555: if (c->flags & CLIENT_CONTROLCONTROL)
1.34 nicm 556: cs->write_event = cs->read_event;
557: else {
1.35 ! nicm 558: cs->write_event = bufferevent_new(c->out_fd, NULL,
! 559: control_write_callback, control_error_callback, c);
! 560: }
! 561: bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW,
! 562: 0);
! 563:
! 564: if (c->flags & CLIENT_CONTROLCONTROL) {
! 565: bufferevent_write(cs->write_event, "\033P1000p", 7);
! 566: bufferevent_enable(cs->write_event, EV_WRITE);
1.34 nicm 567: }
1.35 ! nicm 568: }
! 569:
! 570: /* Flush all output for a client that is detaching. */
! 571: void
! 572: control_flush(struct client *c)
! 573: {
! 574: struct control_state *cs = c->control_state;
! 575: struct control_pane *cp;
! 576: struct control_block *cb, *cb1;
1.34 nicm 577:
1.35 ! nicm 578: RB_FOREACH(cp, control_panes, &cs->panes) {
! 579: TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
! 580: TAILQ_REMOVE(&cp->blocks, cb, entry);
! 581: control_free_block(cs, cb);
! 582: }
! 583: }
1.33 nicm 584: }
585:
586: /* Stop control mode. */
587: void
588: control_stop(struct client *c)
589: {
590: struct control_state *cs = c->control_state;
1.35 ! nicm 591: struct control_block *cb, *cb1;
1.34 nicm 592:
593: if (~c->flags & CLIENT_CONTROLCONTROL)
594: bufferevent_free(cs->write_event);
595: bufferevent_free(cs->read_event);
1.33 nicm 596:
1.35 ! nicm 597: TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
! 598: control_free_block(cs, cb);
! 599: control_reset_offsets(c);
! 600:
1.33 nicm 601: free(cs);
1.1 nicm 602: }