xref: /openbsd-src/usr.bin/tmux/session.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /* $OpenBSD: session.c,v 1.11 2009/10/10 10:02:48 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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 /* Global session list. */
31 struct sessions	sessions;
32 struct sessions dead_sessions;
33 struct session_groups session_groups;
34 
35 struct winlink *session_next_activity(struct session *, struct winlink *);
36 struct winlink *session_previous_activity(struct session *, struct winlink *);
37 
38 void
39 session_alert_cancel(struct session *s, struct winlink *wl)
40 {
41 	struct session_alert	*sa, *sb;
42 
43 	sa = SLIST_FIRST(&s->alerts);
44 	while (sa != NULL) {
45 		sb = sa;
46 		sa = SLIST_NEXT(sa, entry);
47 
48 		if (wl == NULL || sb->wl == wl) {
49 			SLIST_REMOVE(&s->alerts, sb, session_alert, entry);
50 			xfree(sb);
51 		}
52 	}
53 }
54 
55 void
56 session_alert_add(struct session *s, struct window *w, int type)
57 {
58 	struct session_alert	*sa;
59 	struct winlink		*wl;
60 
61 	RB_FOREACH(wl, winlinks, &s->windows) {
62 		if (wl == s->curw)
63 			continue;
64 
65 		if (wl->window == w &&
66 		    !session_alert_has(s, wl, type)) {
67 			sa = xmalloc(sizeof *sa);
68 			sa->wl = wl;
69 			sa->type = type;
70 			SLIST_INSERT_HEAD(&s->alerts, sa, entry);
71 		}
72 	}
73 }
74 
75 int
76 session_alert_has(struct session *s, struct winlink *wl, int type)
77 {
78 	struct session_alert	*sa;
79 
80 	SLIST_FOREACH(sa, &s->alerts, entry) {
81 		if (sa->wl == wl && sa->type == type)
82 			return (1);
83 	}
84 
85 	return (0);
86 }
87 
88 int
89 session_alert_has_window(struct session *s, struct window *w, int type)
90 {
91 	struct session_alert	*sa;
92 
93 	SLIST_FOREACH(sa, &s->alerts, entry) {
94 		if (sa->wl->window == w && sa->type == type)
95 			return (1);
96 	}
97 
98 	return (0);
99 }
100 
101 /* Find session by name. */
102 struct session *
103 session_find(const char *name)
104 {
105 	struct session	*s;
106 	u_int		 i;
107 
108 	for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
109 		s = ARRAY_ITEM(&sessions, i);
110 		if (s != NULL && strcmp(s->name, name) == 0)
111 			return (s);
112 	}
113 
114 	return (NULL);
115 }
116 
117 /* Create a new session. */
118 struct session *
119 session_create(const char *name, const char *cmd, const char *cwd,
120     struct environ *env, struct termios *tio, int idx, u_int sx, u_int sy,
121     char **cause)
122 {
123 	struct session	*s;
124 	u_int		 i;
125 
126 	s = xmalloc(sizeof *s);
127 	s->references = 0;
128 	s->flags = 0;
129 	s->activity = time(NULL);
130 
131 	if (gettimeofday(&s->tv, NULL) != 0)
132 		fatal("gettimeofday failed");
133 
134 	s->curw = NULL;
135 	TAILQ_INIT(&s->lastw);
136 	RB_INIT(&s->windows);
137 	SLIST_INIT(&s->alerts);
138 
139 	paste_init_stack(&s->buffers);
140 
141 	options_init(&s->options, &global_s_options);
142 	environ_init(&s->environ);
143 	if (env != NULL)
144 		environ_copy(env, &s->environ);
145 
146 	s->tio = NULL;
147 	if (tio != NULL) {
148 		s->tio = xmalloc(sizeof *s->tio);
149 		memcpy(s->tio, tio, sizeof *s->tio);
150 	}
151 
152 	s->sx = sx;
153 	s->sy = sy;
154 
155 	for (i = 0; i < ARRAY_LENGTH(&sessions); i++) {
156 		if (ARRAY_ITEM(&sessions, i) == NULL) {
157 			ARRAY_SET(&sessions, i, s);
158 			break;
159 		}
160 	}
161 	if (i == ARRAY_LENGTH(&sessions))
162 		ARRAY_ADD(&sessions, s);
163 
164 	if (name != NULL)
165 		s->name = xstrdup(name);
166 	else
167 		xasprintf(&s->name, "%u", i);
168 
169 	if (cmd != NULL) {
170 		if (session_new(s, NULL, cmd, cwd, idx, cause) == NULL) {
171 			session_destroy(s);
172 			return (NULL);
173 		}
174 		session_select(s, RB_ROOT(&s->windows)->idx);
175 	}
176 
177 	log_debug("session %s created", s->name);
178 
179 	return (s);
180 }
181 
182 /* Destroy a session. */
183 void
184 session_destroy(struct session *s)
185 {
186 	u_int	i;
187 
188 	log_debug("session %s destroyed", s->name);
189 
190 	if (session_index(s, &i) != 0)
191 		fatalx("session not found");
192 	ARRAY_SET(&sessions, i, NULL);
193 	while (!ARRAY_EMPTY(&sessions) && ARRAY_LAST(&sessions) == NULL)
194 		ARRAY_TRUNC(&sessions, 1);
195 
196 	if (s->tio != NULL)
197 		xfree(s->tio);
198 
199 	session_group_remove(s);
200 	session_alert_cancel(s, NULL);
201 	environ_free(&s->environ);
202 	options_free(&s->options);
203 	paste_free_stack(&s->buffers);
204 
205 	while (!TAILQ_EMPTY(&s->lastw))
206 		winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw));
207 	while (!RB_EMPTY(&s->windows))
208 		winlink_remove(&s->windows, RB_ROOT(&s->windows));
209 
210 	xfree(s->name);
211 
212 	for (i = 0; i < ARRAY_LENGTH(&dead_sessions); i++) {
213 		if (ARRAY_ITEM(&dead_sessions, i) == NULL) {
214 			ARRAY_SET(&dead_sessions, i, s);
215 			break;
216 		}
217 	}
218 	if (i == ARRAY_LENGTH(&dead_sessions))
219 		ARRAY_ADD(&dead_sessions, s);
220 	s->flags |= SESSION_DEAD;
221 }
222 
223 /* Find session index. */
224 int
225 session_index(struct session *s, u_int *i)
226 {
227 	for (*i = 0; *i < ARRAY_LENGTH(&sessions); (*i)++) {
228 		if (s == ARRAY_ITEM(&sessions, *i))
229 			return (0);
230 	}
231 	return (-1);
232 }
233 
234 /* Create a new window on a session. */
235 struct winlink *
236 session_new(struct session *s,
237     const char *name, const char *cmd, const char *cwd, int idx, char **cause)
238 {
239 	struct window	*w;
240 	struct environ	 env;
241 	const char	*shell;
242 	u_int		 hlimit;
243 
244 	environ_init(&env);
245 	environ_copy(&global_environ, &env);
246 	environ_copy(&s->environ, &env);
247 	server_fill_environ(s, &env);
248 
249 	shell = options_get_string(&s->options, "default-shell");
250 	if (*shell == '\0' || areshell(shell))
251 		shell = _PATH_BSHELL;
252 
253 	hlimit = options_get_number(&s->options, "history-limit");
254 	w = window_create(
255 	    name, cmd, shell, cwd, &env, s->tio, s->sx, s->sy, hlimit, cause);
256 	if (w == NULL) {
257 		environ_free(&env);
258 		return (NULL);
259 	}
260 	environ_free(&env);
261 
262 	if (options_get_number(&s->options, "set-remain-on-exit"))
263 		options_set_number(&w->options, "remain-on-exit", 1);
264 
265 	return (session_attach(s, w, idx, cause));
266 }
267 
268 /* Attach a window to a session. */
269 struct winlink *
270 session_attach(struct session *s, struct window *w, int idx, char **cause)
271 {
272 	struct winlink	*wl;
273 
274 	if ((wl = winlink_add(&s->windows, w, idx)) == NULL)
275 		xasprintf(cause, "index in use: %d", idx);
276 	session_group_synchronize_from(s);
277 	return (wl);
278 }
279 
280 /* Detach a window from a session. */
281 int
282 session_detach(struct session *s, struct winlink *wl)
283 {
284 	if (s->curw == wl &&
285 	    session_last(s) != 0 && session_previous(s, 0) != 0)
286 		session_next(s, 0);
287 
288 	session_alert_cancel(s, wl);
289 	winlink_stack_remove(&s->lastw, wl);
290 	winlink_remove(&s->windows, wl);
291 	session_group_synchronize_from(s);
292 	if (RB_EMPTY(&s->windows)) {
293 		session_destroy(s);
294 		return (1);
295 	}
296 	return (0);
297 }
298 
299 /* Return if session has window. */
300 int
301 session_has(struct session *s, struct window *w)
302 {
303 	struct winlink	*wl;
304 
305 	RB_FOREACH(wl, winlinks, &s->windows) {
306 		if (wl->window == w)
307 			return (1);
308 	}
309 	return (0);
310 }
311 
312 struct winlink *
313 session_next_activity(struct session *s, struct winlink *wl)
314 {
315 	while (wl != NULL) {
316 		if (session_alert_has(s, wl, WINDOW_BELL))
317 			break;
318 		if (session_alert_has(s, wl, WINDOW_ACTIVITY))
319 			break;
320 		if (session_alert_has(s, wl, WINDOW_CONTENT))
321 			break;
322 		wl = winlink_next(&s->windows, wl);
323 	}
324 	return (wl);
325 }
326 
327 /* Move session to next window. */
328 int
329 session_next(struct session *s, int activity)
330 {
331 	struct winlink	*wl;
332 
333 	if (s->curw == NULL)
334 		return (-1);
335 
336 	wl = winlink_next(&s->windows, s->curw);
337 	if (activity)
338 		wl = session_next_activity(s, wl);
339 	if (wl == NULL) {
340 		wl = RB_MIN(winlinks, &s->windows);
341 		if (activity && ((wl = session_next_activity(s, wl)) == NULL))
342 			return (-1);
343 	}
344 	if (wl == s->curw)
345 		return (1);
346 	winlink_stack_remove(&s->lastw, wl);
347 	winlink_stack_push(&s->lastw, s->curw);
348 	s->curw = wl;
349 	session_alert_cancel(s, wl);
350 	return (0);
351 }
352 
353 struct winlink *
354 session_previous_activity(struct session *s, struct winlink *wl)
355 {
356 	while (wl != NULL) {
357 		if (session_alert_has(s, wl, WINDOW_BELL))
358 			break;
359 		if (session_alert_has(s, wl, WINDOW_ACTIVITY))
360 			break;
361 		if (session_alert_has(s, wl, WINDOW_CONTENT))
362 			break;
363 		wl = winlink_previous(&s->windows, wl);
364 	}
365 	return (wl);
366 }
367 
368 /* Move session to previous window. */
369 int
370 session_previous(struct session *s, int activity)
371 {
372 	struct winlink	*wl;
373 
374 	if (s->curw == NULL)
375 		return (-1);
376 
377 	wl = winlink_previous(&s->windows, s->curw);
378 	if (activity)
379 		wl = session_previous_activity(s, wl);
380 	if (wl == NULL) {
381 		wl = RB_MAX(winlinks, &s->windows);
382 		if (activity && (wl = session_previous_activity(s, wl)) == NULL)
383 			return (-1);
384 	}
385 	if (wl == s->curw)
386 		return (1);
387 	winlink_stack_remove(&s->lastw, wl);
388 	winlink_stack_push(&s->lastw, s->curw);
389 	s->curw = wl;
390 	session_alert_cancel(s, wl);
391 	return (0);
392 }
393 
394 /* Move session to specific window. */
395 int
396 session_select(struct session *s, int idx)
397 {
398 	struct winlink	*wl;
399 
400 	wl = winlink_find_by_index(&s->windows, idx);
401 	if (wl == NULL)
402 		return (-1);
403 	if (wl == s->curw)
404 		return (1);
405 	winlink_stack_remove(&s->lastw, wl);
406 	winlink_stack_push(&s->lastw, s->curw);
407 	s->curw = wl;
408 	session_alert_cancel(s, wl);
409 	return (0);
410 }
411 
412 /* Move session to last used window. */
413 int
414 session_last(struct session *s)
415 {
416 	struct winlink	*wl;
417 
418 	wl = TAILQ_FIRST(&s->lastw);
419 	if (wl == NULL)
420 		return (-1);
421 	if (wl == s->curw)
422 		return (1);
423 
424 	winlink_stack_remove(&s->lastw, wl);
425 	winlink_stack_push(&s->lastw, s->curw);
426 	s->curw = wl;
427 	session_alert_cancel(s, wl);
428 	return (0);
429 }
430 
431 /* Find the session group containing a session. */
432 struct session_group *
433 session_group_find(struct session *target)
434 {
435 	struct session_group	*sg;
436 	struct session		*s;
437 
438 	TAILQ_FOREACH(sg, &session_groups, entry) {
439 		TAILQ_FOREACH(s, &sg->sessions, gentry) {
440 			if (s == target)
441 				return (sg);
442 		}
443 	}
444 	return (NULL);
445 }
446 
447 /* Find session group index. */
448 u_int
449 session_group_index(struct session_group *sg)
450 {
451 	struct session_group   *sg2;
452 	u_int			i;
453 
454 	i = 0;
455 	TAILQ_FOREACH(sg2, &session_groups, entry) {
456 		if (sg == sg2)
457 			return (i);
458 		i++;
459 	}
460 
461 	fatalx("session group not found");
462 }
463 
464 /*
465  * Add a session to the session group containing target, creating it if
466  * necessary.
467  */
468 void
469 session_group_add(struct session *target, struct session *s)
470 {
471 	struct session_group	*sg;
472 
473 	if ((sg = session_group_find(target)) == NULL) {
474 		sg = xmalloc(sizeof *sg);
475 		TAILQ_INSERT_TAIL(&session_groups, sg, entry);
476 		TAILQ_INIT(&sg->sessions);
477 		TAILQ_INSERT_TAIL(&sg->sessions, target, gentry);
478 	}
479 	TAILQ_INSERT_TAIL(&sg->sessions, s, gentry);
480 }
481 
482 /* Remove a session from its group and destroy the group if empty. */
483 void
484 session_group_remove(struct session *s)
485 {
486 	struct session_group	*sg;
487 
488 	if ((sg = session_group_find(s)) == NULL)
489 		return;
490 	TAILQ_REMOVE(&sg->sessions, s, gentry);
491 	if (TAILQ_NEXT(TAILQ_FIRST(&sg->sessions), gentry) == NULL)
492 		TAILQ_REMOVE(&sg->sessions, TAILQ_FIRST(&sg->sessions), gentry);
493 	if (TAILQ_EMPTY(&sg->sessions)) {
494 		TAILQ_REMOVE(&session_groups, sg, entry);
495 		xfree(sg);
496 	}
497 }
498 
499 /* Synchronize a session to its session group. */
500 void
501 session_group_synchronize_to(struct session *s)
502 {
503 	struct session_group	*sg;
504 	struct session		*target;
505 
506 	if ((sg = session_group_find(s)) == NULL)
507 		return;
508 
509 	target = NULL;
510 	TAILQ_FOREACH(target, &sg->sessions, gentry) {
511 		if (target != s)
512 			break;
513 	}
514 	session_group_synchronize1(target, s);
515 }
516 
517 /* Synchronize a session group to a session. */
518 void
519 session_group_synchronize_from(struct session *target)
520 {
521 	struct session_group	*sg;
522 	struct session		*s;
523 
524 	if ((sg = session_group_find(target)) == NULL)
525 		return;
526 
527 	TAILQ_FOREACH(s, &sg->sessions, gentry) {
528 		if (s != target)
529 			session_group_synchronize1(target, s);
530 	}
531 }
532 
533 /*
534  * Synchronize a session with a target session. This means destroying all
535  * winlinks then recreating them, then updating the current window, last window
536  * stack and alerts.
537  */
538 void
539 session_group_synchronize1(struct session *target, struct session *s)
540 {
541 	struct winlinks		 old_windows, *ww;
542 	struct winlink_stack	 old_lastw;
543 	struct winlink		*wl, *wl2;
544 	struct session_alert	*sa;
545 
546 	/* Don't do anything if the session is empty (it'll be destroyed). */
547 	ww = &target->windows;
548 	if (RB_EMPTY(ww))
549 		return;
550 
551 	/* If the current window has vanished, move to the next now. */
552 	if (s->curw != NULL) {
553 		while (winlink_find_by_index(ww, s->curw->idx) == NULL)
554 			session_next(s, 0);
555 	}
556 
557 	/* Save the old pointer and reset it. */
558 	memcpy(&old_windows, &s->windows, sizeof old_windows);
559 	RB_INIT(&s->windows);
560 
561 	/* Link all the windows from the target. */
562 	RB_FOREACH(wl, winlinks, ww)
563 		winlink_add(&s->windows, wl->window, wl->idx);
564 
565 	/* Fix up the current window. */
566 	if (s->curw != NULL)
567 		s->curw = winlink_find_by_index(&s->windows, s->curw->idx);
568 	else
569 		s->curw = winlink_find_by_index(&s->windows, target->curw->idx);
570 
571 	/* Fix up the last window stack. */
572 	memcpy(&old_lastw, &s->lastw, sizeof old_lastw);
573 	TAILQ_INIT(&s->lastw);
574 	TAILQ_FOREACH(wl, &old_lastw, sentry) {
575 		wl2 = winlink_find_by_index(&s->windows, wl->idx);
576 		if (wl2 != NULL)
577 			TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry);
578 	}
579 
580 	/* And update the alerts list. */
581 	SLIST_FOREACH(sa, &s->alerts, entry) {
582 		wl = winlink_find_by_index(&s->windows, sa->wl->idx);
583 		if (wl == NULL)
584 			session_alert_cancel(s, sa->wl);
585 		else
586 			sa->wl = wl;
587 	}
588 
589 	/* Then free the old winlinks list. */
590 	while (!RB_EMPTY(&old_windows)) {
591 		wl = RB_ROOT(&old_windows);
592 		RB_REMOVE(winlinks, &old_windows, wl);
593 		xfree(wl);
594 	}
595 }
596