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