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