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