GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/tmux/session.c Lines: 0 319 0.0 %
Date: 2017-11-07 Branches: 0 550 0.0 %

Line Branch Exec Source
1
/* $OpenBSD: session.c,v 1.77 2017/07/09 22:33:09 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
#include <sys/time.h>
21
22
#include <paths.h>
23
#include <string.h>
24
#include <stdlib.h>
25
#include <unistd.h>
26
#include <time.h>
27
28
#include "tmux.h"
29
30
struct sessions		sessions;
31
static u_int		next_session_id;
32
struct session_groups	session_groups;
33
34
static void	session_free(int, short, void *);
35
36
static void	session_lock_timer(int, short, void *);
37
38
static struct winlink *session_next_alert(struct winlink *);
39
static struct winlink *session_previous_alert(struct winlink *);
40
41
static void	session_group_remove(struct session *);
42
static u_int	session_group_count(struct session_group *);
43
static void	session_group_synchronize1(struct session *, struct session *);
44
45
static u_int	session_group_count(struct session_group *);
46
static void	session_group_synchronize1(struct session *, struct session *);
47
48
RB_GENERATE(sessions, session, entry, session_cmp);
49
50
int
51
session_cmp(struct session *s1, struct session *s2)
52
{
53
	return (strcmp(s1->name, s2->name));
54
}
55
56
RB_GENERATE(session_groups, session_group, entry, session_group_cmp);
57
58
int
59
session_group_cmp(struct session_group *s1, struct session_group *s2)
60
{
61
	return (strcmp(s1->name, s2->name));
62
}
63
64
/*
65
 * Find if session is still alive. This is true if it is still on the global
66
 * sessions list.
67
 */
68
int
69
session_alive(struct session *s)
70
{
71
	struct session *s_loop;
72
73
	RB_FOREACH(s_loop, sessions, &sessions) {
74
		if (s_loop == s)
75
			return (1);
76
	}
77
	return (0);
78
}
79
80
/* Find session by name. */
81
struct session *
82
session_find(const char *name)
83
{
84
	struct session	s;
85
86
	s.name = (char *) name;
87
	return (RB_FIND(sessions, &sessions, &s));
88
}
89
90
/* Find session by id parsed from a string. */
91
struct session *
92
session_find_by_id_str(const char *s)
93
{
94
	const char	*errstr;
95
	u_int		 id;
96
97
	if (*s != '$')
98
		return (NULL);
99
100
	id = strtonum(s + 1, 0, UINT_MAX, &errstr);
101
	if (errstr != NULL)
102
		return (NULL);
103
	return (session_find_by_id(id));
104
}
105
106
/* Find session by id. */
107
struct session *
108
session_find_by_id(u_int id)
109
{
110
	struct session	*s;
111
112
	RB_FOREACH(s, sessions, &sessions) {
113
		if (s->id == id)
114
			return (s);
115
	}
116
	return (NULL);
117
}
118
119
/* Create a new session. */
120
struct session *
121
session_create(const char *prefix, const char *name, int argc, char **argv,
122
    const char *path, const char *cwd, struct environ *env, struct termios *tio,
123
    int idx, u_int sx, u_int sy, char **cause)
124
{
125
	struct session	*s;
126
	struct winlink	*wl;
127
128
	s = xcalloc(1, sizeof *s);
129
	s->references = 1;
130
	s->flags = 0;
131
132
	s->cwd = xstrdup(cwd);
133
134
	s->curw = NULL;
135
	TAILQ_INIT(&s->lastw);
136
	RB_INIT(&s->windows);
137
138
	s->environ = environ_create();
139
	if (env != NULL)
140
		environ_copy(env, s->environ);
141
142
	s->options = options_create(global_s_options);
143
	s->hooks = hooks_create(global_hooks);
144
145
	status_update_saved(s);
146
147
	s->tio = NULL;
148
	if (tio != NULL) {
149
		s->tio = xmalloc(sizeof *s->tio);
150
		memcpy(s->tio, tio, sizeof *s->tio);
151
	}
152
153
	s->sx = sx;
154
	s->sy = sy;
155
156
	if (name != NULL) {
157
		s->name = xstrdup(name);
158
		s->id = next_session_id++;
159
	} else {
160
		s->name = NULL;
161
		do {
162
			s->id = next_session_id++;
163
			free(s->name);
164
			if (prefix != NULL)
165
				xasprintf(&s->name, "%s-%u", prefix, s->id);
166
			else
167
				xasprintf(&s->name, "%u", s->id);
168
		} while (RB_FIND(sessions, &sessions, s) != NULL);
169
	}
170
	RB_INSERT(sessions, &sessions, s);
171
172
	log_debug("new session %s $%u", s->name, s->id);
173
174
	if (gettimeofday(&s->creation_time, NULL) != 0)
175
		fatal("gettimeofday failed");
176
	session_update_activity(s, &s->creation_time);
177
178
	if (argc >= 0) {
179
		wl = session_new(s, NULL, argc, argv, path, cwd, idx, cause);
180
		if (wl == NULL) {
181
			session_destroy(s, __func__);
182
			return (NULL);
183
		}
184
		session_select(s, RB_ROOT(&s->windows)->idx);
185
	}
186
187
	log_debug("session %s created", s->name);
188
189
	return (s);
190
}
191
192
/* Add a reference to a session. */
193
void
194
session_add_ref(struct session *s, const char *from)
195
{
196
	s->references++;
197
	log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references);
198
}
199
200
/* Remove a reference from a session. */
201
void
202
session_remove_ref(struct session *s, const char *from)
203
{
204
	s->references--;
205
	log_debug("%s: %s %s, now %d", __func__, s->name, from, s->references);
206
207
	if (s->references == 0)
208
		event_once(-1, EV_TIMEOUT, session_free, s, NULL);
209
}
210
211
/* Free session. */
212
static void
213
session_free(__unused int fd, __unused short events, void *arg)
214
{
215
	struct session	*s = arg;
216
217
	log_debug("session %s freed (%d references)", s->name, s->references);
218
219
	if (s->references == 0) {
220
		environ_free(s->environ);
221
222
		options_free(s->options);
223
		hooks_free(s->hooks);
224
225
		free(s->name);
226
		free(s);
227
	}
228
}
229
230
/* Destroy a session. */
231
void
232
session_destroy(struct session *s, const char *from)
233
{
234
	struct winlink	*wl;
235
236
	log_debug("session %s destroyed (%s)", s->name, from);
237
	s->curw = NULL;
238
239
	RB_REMOVE(sessions, &sessions, s);
240
	notify_session("session-closed", s);
241
242
	free(s->tio);
243
244
	if (event_initialized(&s->lock_timer))
245
		event_del(&s->lock_timer);
246
247
	session_group_remove(s);
248
249
	while (!TAILQ_EMPTY(&s->lastw))
250
		winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw));
251
	while (!RB_EMPTY(&s->windows)) {
252
		wl = RB_ROOT(&s->windows);
253
		notify_session_window("window-unlinked", s, wl->window);
254
		winlink_remove(&s->windows, wl);
255
	}
256
257
	free((void *)s->cwd);
258
259
	session_remove_ref(s, __func__);
260
}
261
262
/* Check a session name is valid: not empty and no colons or periods. */
263
int
264
session_check_name(const char *name)
265
{
266
	return (*name != '\0' && name[strcspn(name, ":.")] == '\0');
267
}
268
269
/* Lock session if it has timed out. */
270
static void
271
session_lock_timer(__unused int fd, __unused short events, void *arg)
272
{
273
	struct session	*s = arg;
274
275
	if (s->flags & SESSION_UNATTACHED)
276
		return;
277
278
	log_debug("session %s locked, activity time %lld", s->name,
279
	    (long long)s->activity_time.tv_sec);
280
281
	server_lock_session(s);
282
	recalculate_sizes();
283
}
284
285
/* Update activity time. */
286
void
287
session_update_activity(struct session *s, struct timeval *from)
288
{
289
	struct timeval	*last = &s->last_activity_time;
290
	struct timeval	 tv;
291
292
	memcpy(last, &s->activity_time, sizeof *last);
293
	if (from == NULL)
294
		gettimeofday(&s->activity_time, NULL);
295
	else
296
		memcpy(&s->activity_time, from, sizeof s->activity_time);
297
298
	log_debug("session %s activity %lld.%06d (last %lld.%06d)", s->name,
299
	    (long long)s->activity_time.tv_sec, (int)s->activity_time.tv_usec,
300
	    (long long)last->tv_sec, (int)last->tv_usec);
301
302
	if (evtimer_initialized(&s->lock_timer))
303
		evtimer_del(&s->lock_timer);
304
	else
305
		evtimer_set(&s->lock_timer, session_lock_timer, s);
306
307
	if (~s->flags & SESSION_UNATTACHED) {
308
		timerclear(&tv);
309
		tv.tv_sec = options_get_number(s->options, "lock-after-time");
310
		if (tv.tv_sec != 0)
311
			evtimer_add(&s->lock_timer, &tv);
312
	}
313
}
314
315
/* Find the next usable session. */
316
struct session *
317
session_next_session(struct session *s)
318
{
319
	struct session *s2;
320
321
	if (RB_EMPTY(&sessions) || !session_alive(s))
322
		return (NULL);
323
324
	s2 = RB_NEXT(sessions, &sessions, s);
325
	if (s2 == NULL)
326
		s2 = RB_MIN(sessions, &sessions);
327
	if (s2 == s)
328
		return (NULL);
329
	return (s2);
330
}
331
332
/* Find the previous usable session. */
333
struct session *
334
session_previous_session(struct session *s)
335
{
336
	struct session *s2;
337
338
	if (RB_EMPTY(&sessions) || !session_alive(s))
339
		return (NULL);
340
341
	s2 = RB_PREV(sessions, &sessions, s);
342
	if (s2 == NULL)
343
		s2 = RB_MAX(sessions, &sessions);
344
	if (s2 == s)
345
		return (NULL);
346
	return (s2);
347
}
348
349
/* Create a new window on a session. */
350
struct winlink *
351
session_new(struct session *s, const char *name, int argc, char **argv,
352
    const char *path, const char *cwd, int idx, char **cause)
353
{
354
	struct window	*w;
355
	struct winlink	*wl;
356
	struct environ	*env;
357
	const char	*shell;
358
	u_int		 hlimit;
359
360
	if ((wl = winlink_add(&s->windows, idx)) == NULL) {
361
		xasprintf(cause, "index in use: %d", idx);
362
		return (NULL);
363
	}
364
	wl->session = s;
365
366
	shell = options_get_string(s->options, "default-shell");
367
	if (*shell == '\0' || areshell(shell))
368
		shell = _PATH_BSHELL;
369
370
	hlimit = options_get_number(s->options, "history-limit");
371
	env = environ_for_session(s, 0);
372
	w = window_create_spawn(name, argc, argv, path, shell, cwd, env, s->tio,
373
	    s->sx, s->sy, hlimit, cause);
374
	if (w == NULL) {
375
		winlink_remove(&s->windows, wl);
376
		environ_free(env);
377
		return (NULL);
378
	}
379
	winlink_set_window(wl, w);
380
	environ_free(env);
381
	notify_session_window("window-linked", s, w);
382
383
	session_group_synchronize_from(s);
384
	return (wl);
385
}
386
387
/* Attach a window to a session. */
388
struct winlink *
389
session_attach(struct session *s, struct window *w, int idx, char **cause)
390
{
391
	struct winlink	*wl;
392
393
	if ((wl = winlink_add(&s->windows, idx)) == NULL) {
394
		xasprintf(cause, "index in use: %d", idx);
395
		return (NULL);
396
	}
397
	wl->session = s;
398
	winlink_set_window(wl, w);
399
	notify_session_window("window-linked", s, w);
400
401
	session_group_synchronize_from(s);
402
	return (wl);
403
}
404
405
/* Detach a window from a session. */
406
int
407
session_detach(struct session *s, struct winlink *wl)
408
{
409
	if (s->curw == wl &&
410
	    session_last(s) != 0 &&
411
	    session_previous(s, 0) != 0)
412
		session_next(s, 0);
413
414
	wl->flags &= ~WINLINK_ALERTFLAGS;
415
	notify_session_window("window-unlinked", s, wl->window);
416
	winlink_stack_remove(&s->lastw, wl);
417
	winlink_remove(&s->windows, wl);
418
419
	session_group_synchronize_from(s);
420
421
	if (RB_EMPTY(&s->windows)) {
422
		session_destroy(s, __func__);
423
		return (1);
424
	}
425
	return (0);
426
}
427
428
/* Return if session has window. */
429
int
430
session_has(struct session *s, struct window *w)
431
{
432
	struct winlink	*wl;
433
434
	TAILQ_FOREACH(wl, &w->winlinks, wentry) {
435
		if (wl->session == s)
436
			return (1);
437
	}
438
	return (0);
439
}
440
441
/*
442
 * Return 1 if a window is linked outside this session (not including session
443
 * groups). The window must be in this session!
444
 */
445
int
446
session_is_linked(struct session *s, struct window *w)
447
{
448
	struct session_group	*sg;
449
450
	if ((sg = session_group_contains(s)) != NULL)
451
		return (w->references != session_group_count(sg));
452
	return (w->references != 1);
453
}
454
455
static struct winlink *
456
session_next_alert(struct winlink *wl)
457
{
458
	while (wl != NULL) {
459
		if (wl->flags & WINLINK_ALERTFLAGS)
460
			break;
461
		wl = winlink_next(wl);
462
	}
463
	return (wl);
464
}
465
466
/* Move session to next window. */
467
int
468
session_next(struct session *s, int alert)
469
{
470
	struct winlink	*wl;
471
472
	if (s->curw == NULL)
473
		return (-1);
474
475
	wl = winlink_next(s->curw);
476
	if (alert)
477
		wl = session_next_alert(wl);
478
	if (wl == NULL) {
479
		wl = RB_MIN(winlinks, &s->windows);
480
		if (alert && ((wl = session_next_alert(wl)) == NULL))
481
			return (-1);
482
	}
483
	return (session_set_current(s, wl));
484
}
485
486
static struct winlink *
487
session_previous_alert(struct winlink *wl)
488
{
489
	while (wl != NULL) {
490
		if (wl->flags & WINLINK_ALERTFLAGS)
491
			break;
492
		wl = winlink_previous(wl);
493
	}
494
	return (wl);
495
}
496
497
/* Move session to previous window. */
498
int
499
session_previous(struct session *s, int alert)
500
{
501
	struct winlink	*wl;
502
503
	if (s->curw == NULL)
504
		return (-1);
505
506
	wl = winlink_previous(s->curw);
507
	if (alert)
508
		wl = session_previous_alert(wl);
509
	if (wl == NULL) {
510
		wl = RB_MAX(winlinks, &s->windows);
511
		if (alert && (wl = session_previous_alert(wl)) == NULL)
512
			return (-1);
513
	}
514
	return (session_set_current(s, wl));
515
}
516
517
/* Move session to specific window. */
518
int
519
session_select(struct session *s, int idx)
520
{
521
	struct winlink	*wl;
522
523
	wl = winlink_find_by_index(&s->windows, idx);
524
	return (session_set_current(s, wl));
525
}
526
527
/* Move session to last used window. */
528
int
529
session_last(struct session *s)
530
{
531
	struct winlink	*wl;
532
533
	wl = TAILQ_FIRST(&s->lastw);
534
	if (wl == NULL)
535
		return (-1);
536
	if (wl == s->curw)
537
		return (1);
538
539
	return (session_set_current(s, wl));
540
}
541
542
/* Set current winlink to wl .*/
543
int
544
session_set_current(struct session *s, struct winlink *wl)
545
{
546
	if (wl == NULL)
547
		return (-1);
548
	if (wl == s->curw)
549
		return (1);
550
551
	winlink_stack_remove(&s->lastw, wl);
552
	winlink_stack_push(&s->lastw, s->curw);
553
	s->curw = wl;
554
	winlink_clear_flags(wl);
555
	window_update_activity(wl->window);
556
	notify_session("session-window-changed", s);
557
	return (0);
558
}
559
560
/* Find the session group containing a session. */
561
struct session_group *
562
session_group_contains(struct session *target)
563
{
564
	struct session_group	*sg;
565
	struct session		*s;
566
567
	RB_FOREACH(sg, session_groups, &session_groups) {
568
		TAILQ_FOREACH(s, &sg->sessions, gentry) {
569
			if (s == target)
570
				return (sg);
571
		}
572
	}
573
	return (NULL);
574
}
575
576
/* Find session group by name. */
577
struct session_group *
578
session_group_find(const char *name)
579
{
580
	struct session_group	sg;
581
582
	sg.name = name;
583
	return (RB_FIND(session_groups, &session_groups, &sg));
584
}
585
586
/* Create a new session group. */
587
struct session_group *
588
session_group_new(const char *name)
589
{
590
	struct session_group	*sg;
591
592
	if ((sg = session_group_find(name)) != NULL)
593
		return (sg);
594
595
	sg = xcalloc(1, sizeof *sg);
596
	sg->name = xstrdup(name);
597
	TAILQ_INIT(&sg->sessions);
598
599
	RB_INSERT(session_groups, &session_groups, sg);
600
	return (sg);
601
}
602
603
/* Add a session to a session group. */
604
void
605
session_group_add(struct session_group *sg, struct session *s)
606
{
607
	if (session_group_contains(s) == NULL)
608
		TAILQ_INSERT_TAIL(&sg->sessions, s, gentry);
609
}
610
611
/* Remove a session from its group and destroy the group if empty. */
612
static void
613
session_group_remove(struct session *s)
614
{
615
	struct session_group	*sg;
616
617
	if ((sg = session_group_contains(s)) == NULL)
618
		return;
619
	TAILQ_REMOVE(&sg->sessions, s, gentry);
620
	if (TAILQ_EMPTY(&sg->sessions)) {
621
		RB_REMOVE(session_groups, &session_groups, sg);
622
		free(sg);
623
	}
624
}
625
626
/* Count number of sessions in session group. */
627
static u_int
628
session_group_count(struct session_group *sg)
629
{
630
	struct session	*s;
631
	u_int		 n;
632
633
	n = 0;
634
	TAILQ_FOREACH(s, &sg->sessions, gentry)
635
	    n++;
636
	return (n);
637
}
638
639
/* Synchronize a session to its session group. */
640
void
641
session_group_synchronize_to(struct session *s)
642
{
643
	struct session_group	*sg;
644
	struct session		*target;
645
646
	if ((sg = session_group_contains(s)) == NULL)
647
		return;
648
649
	target = NULL;
650
	TAILQ_FOREACH(target, &sg->sessions, gentry) {
651
		if (target != s)
652
			break;
653
	}
654
	if (target != NULL)
655
		session_group_synchronize1(target, s);
656
}
657
658
/* Synchronize a session group to a session. */
659
void
660
session_group_synchronize_from(struct session *target)
661
{
662
	struct session_group	*sg;
663
	struct session		*s;
664
665
	if ((sg = session_group_contains(target)) == NULL)
666
		return;
667
668
	TAILQ_FOREACH(s, &sg->sessions, gentry) {
669
		if (s != target)
670
			session_group_synchronize1(target, s);
671
	}
672
}
673
674
/*
675
 * Synchronize a session with a target session. This means destroying all
676
 * winlinks then recreating them, then updating the current window, last window
677
 * stack and alerts.
678
 */
679
static void
680
session_group_synchronize1(struct session *target, struct session *s)
681
{
682
	struct winlinks		 old_windows, *ww;
683
	struct winlink_stack	 old_lastw;
684
	struct winlink		*wl, *wl2;
685
686
	/* Don't do anything if the session is empty (it'll be destroyed). */
687
	ww = &target->windows;
688
	if (RB_EMPTY(ww))
689
		return;
690
691
	/* If the current window has vanished, move to the next now. */
692
	if (s->curw != NULL &&
693
	    winlink_find_by_index(ww, s->curw->idx) == NULL &&
694
	    session_last(s) != 0 && session_previous(s, 0) != 0)
695
		session_next(s, 0);
696
697
	/* Save the old pointer and reset it. */
698
	memcpy(&old_windows, &s->windows, sizeof old_windows);
699
	RB_INIT(&s->windows);
700
701
	/* Link all the windows from the target. */
702
	RB_FOREACH(wl, winlinks, ww) {
703
		wl2 = winlink_add(&s->windows, wl->idx);
704
		wl2->session = s;
705
		winlink_set_window(wl2, wl->window);
706
		notify_session_window("window-linked", s, wl2->window);
707
		wl2->flags |= wl->flags & WINLINK_ALERTFLAGS;
708
	}
709
710
	/* Fix up the current window. */
711
	if (s->curw != NULL)
712
		s->curw = winlink_find_by_index(&s->windows, s->curw->idx);
713
	else
714
		s->curw = winlink_find_by_index(&s->windows, target->curw->idx);
715
716
	/* Fix up the last window stack. */
717
	memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
718
	TAILQ_INIT(&s->lastw);
719
	TAILQ_FOREACH(wl, &old_lastw, sentry) {
720
		wl2 = winlink_find_by_index(&s->windows, wl->idx);
721
		if (wl2 != NULL)
722
			TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry);
723
	}
724
725
	/* Then free the old winlinks list. */
726
	while (!RB_EMPTY(&old_windows)) {
727
		wl = RB_ROOT(&old_windows);
728
		wl2 = winlink_find_by_window_id(&s->windows, wl->window->id);
729
		if (wl2 == NULL)
730
			notify_session_window("window-unlinked", s, wl->window);
731
		winlink_remove(&old_windows, wl);
732
	}
733
}
734
735
/* Renumber the windows across winlinks attached to a specific session. */
736
void
737
session_renumber_windows(struct session *s)
738
{
739
	struct winlink		*wl, *wl1, *wl_new;
740
	struct winlinks		 old_wins;
741
	struct winlink_stack	 old_lastw;
742
	int			 new_idx, new_curw_idx;
743
744
	/* Save and replace old window list. */
745
	memcpy(&old_wins, &s->windows, sizeof old_wins);
746
	RB_INIT(&s->windows);
747
748
	/* Start renumbering from the base-index if it's set. */
749
	new_idx = options_get_number(s->options, "base-index");
750
	new_curw_idx = 0;
751
752
	/* Go through the winlinks and assign new indexes. */
753
	RB_FOREACH(wl, winlinks, &old_wins) {
754
		wl_new = winlink_add(&s->windows, new_idx);
755
		wl_new->session = s;
756
		winlink_set_window(wl_new, wl->window);
757
		wl_new->flags |= wl->flags & WINLINK_ALERTFLAGS;
758
759
		if (wl == s->curw)
760
			new_curw_idx = wl_new->idx;
761
762
		new_idx++;
763
	}
764
765
	/* Fix the stack of last windows now. */
766
	memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
767
	TAILQ_INIT(&s->lastw);
768
	TAILQ_FOREACH(wl, &old_lastw, sentry) {
769
		wl_new = winlink_find_by_window(&s->windows, wl->window);
770
		if (wl_new != NULL)
771
			TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry);
772
	}
773
774
	/* Set the current window. */
775
	s->curw = winlink_find_by_index(&s->windows, new_curw_idx);
776
777
	/* Free the old winlinks (reducing window references too). */
778
	RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1)
779
		winlink_remove(&old_wins, wl);
780
}