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