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