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