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