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