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