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