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