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