1 /* $OpenBSD: session.c,v 1.42 2014/01/22 14:00:08 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, const char *cmd, int cwd, struct environ *env, 89 struct termios *tio, int idx, u_int sx, u_int sy, char **cause) 90 { 91 struct session *s; 92 93 s = xmalloc(sizeof *s); 94 s->references = 0; 95 s->flags = 0; 96 97 if (gettimeofday(&s->creation_time, NULL) != 0) 98 fatal("gettimeofday failed"); 99 session_update_activity(s); 100 101 s->cwd = dup(cwd); 102 103 s->curw = NULL; 104 TAILQ_INIT(&s->lastw); 105 RB_INIT(&s->windows); 106 107 options_init(&s->options, &global_s_options); 108 environ_init(&s->environ); 109 if (env != NULL) 110 environ_copy(env, &s->environ); 111 112 s->tio = NULL; 113 if (tio != NULL) { 114 s->tio = xmalloc(sizeof *s->tio); 115 memcpy(s->tio, tio, sizeof *s->tio); 116 } 117 118 s->sx = sx; 119 s->sy = sy; 120 121 if (name != NULL) { 122 s->name = xstrdup(name); 123 s->id = next_session_id++; 124 } else { 125 s->name = NULL; 126 do { 127 s->id = next_session_id++; 128 free (s->name); 129 xasprintf(&s->name, "%u", s->id); 130 } while (RB_FIND(sessions, &sessions, s) != NULL); 131 } 132 RB_INSERT(sessions, &sessions, s); 133 134 if (cmd != NULL) { 135 if (session_new(s, NULL, cmd, cwd, idx, cause) == NULL) { 136 session_destroy(s); 137 return (NULL); 138 } 139 session_select(s, RB_ROOT(&s->windows)->idx); 140 } 141 142 log_debug("session %s created", s->name); 143 notify_session_created(s); 144 145 return (s); 146 } 147 148 /* Destroy a session. */ 149 void 150 session_destroy(struct session *s) 151 { 152 struct winlink *wl; 153 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 close(s->cwd); 174 175 RB_INSERT(sessions, &dead_sessions, s); 176 } 177 178 /* Check a session name is valid: not empty and no colons or periods. */ 179 int 180 session_check_name(const char *name) 181 { 182 return (*name != '\0' && name[strcspn(name, ":.")] == '\0'); 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, const char *name, const char *cmd, int cwd, 230 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(name, cmd, shell, cwd, &env, s->tio, s->sx, s->sy, 254 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 return (session_set_current(s, wl)); 350 } 351 352 struct winlink * 353 session_previous_alert(struct winlink *wl) 354 { 355 while (wl != NULL) { 356 if (wl->flags & WINLINK_ALERTFLAGS) 357 break; 358 wl = winlink_previous(wl); 359 } 360 return (wl); 361 } 362 363 /* Move session to previous window. */ 364 int 365 session_previous(struct session *s, int alert) 366 { 367 struct winlink *wl; 368 369 if (s->curw == NULL) 370 return (-1); 371 372 wl = winlink_previous(s->curw); 373 if (alert) 374 wl = session_previous_alert(wl); 375 if (wl == NULL) { 376 wl = RB_MAX(winlinks, &s->windows); 377 if (alert && (wl = session_previous_alert(wl)) == NULL) 378 return (-1); 379 } 380 return (session_set_current(s, wl)); 381 } 382 383 /* Move session to specific window. */ 384 int 385 session_select(struct session *s, int idx) 386 { 387 struct winlink *wl; 388 389 wl = winlink_find_by_index(&s->windows, idx); 390 return (session_set_current(s, wl)); 391 } 392 393 /* Move session to last used window. */ 394 int 395 session_last(struct session *s) 396 { 397 struct winlink *wl; 398 399 wl = TAILQ_FIRST(&s->lastw); 400 if (wl == NULL) 401 return (-1); 402 if (wl == s->curw) 403 return (1); 404 405 return (session_set_current(s, wl)); 406 } 407 408 /* Set current winlink to wl .*/ 409 int 410 session_set_current(struct session *s, struct winlink *wl) 411 { 412 if (wl == NULL) 413 return (-1); 414 if (wl == s->curw) 415 return (1); 416 417 winlink_stack_remove(&s->lastw, wl); 418 winlink_stack_push(&s->lastw, s->curw); 419 s->curw = wl; 420 winlink_clear_flags(wl); 421 return (0); 422 } 423 424 /* Find the session group containing a session. */ 425 struct session_group * 426 session_group_find(struct session *target) 427 { 428 struct session_group *sg; 429 struct session *s; 430 431 TAILQ_FOREACH(sg, &session_groups, entry) { 432 TAILQ_FOREACH(s, &sg->sessions, gentry) { 433 if (s == target) 434 return (sg); 435 } 436 } 437 return (NULL); 438 } 439 440 /* Find session group index. */ 441 u_int 442 session_group_index(struct session_group *sg) 443 { 444 struct session_group *sg2; 445 u_int i; 446 447 i = 0; 448 TAILQ_FOREACH(sg2, &session_groups, entry) { 449 if (sg == sg2) 450 return (i); 451 i++; 452 } 453 454 fatalx("session group not found"); 455 } 456 457 /* 458 * Add a session to the session group containing target, creating it if 459 * necessary. 460 */ 461 void 462 session_group_add(struct session *target, struct session *s) 463 { 464 struct session_group *sg; 465 466 if ((sg = session_group_find(target)) == NULL) { 467 sg = xmalloc(sizeof *sg); 468 TAILQ_INSERT_TAIL(&session_groups, sg, entry); 469 TAILQ_INIT(&sg->sessions); 470 TAILQ_INSERT_TAIL(&sg->sessions, target, gentry); 471 } 472 TAILQ_INSERT_TAIL(&sg->sessions, s, gentry); 473 } 474 475 /* Remove a session from its group and destroy the group if empty. */ 476 void 477 session_group_remove(struct session *s) 478 { 479 struct session_group *sg; 480 481 if ((sg = session_group_find(s)) == NULL) 482 return; 483 TAILQ_REMOVE(&sg->sessions, s, gentry); 484 if (TAILQ_NEXT(TAILQ_FIRST(&sg->sessions), gentry) == NULL) 485 TAILQ_REMOVE(&sg->sessions, TAILQ_FIRST(&sg->sessions), gentry); 486 if (TAILQ_EMPTY(&sg->sessions)) { 487 TAILQ_REMOVE(&session_groups, sg, entry); 488 free(sg); 489 } 490 } 491 492 /* Synchronize a session to its session group. */ 493 void 494 session_group_synchronize_to(struct session *s) 495 { 496 struct session_group *sg; 497 struct session *target; 498 499 if ((sg = session_group_find(s)) == NULL) 500 return; 501 502 target = NULL; 503 TAILQ_FOREACH(target, &sg->sessions, gentry) { 504 if (target != s) 505 break; 506 } 507 session_group_synchronize1(target, s); 508 } 509 510 /* Synchronize a session group to a session. */ 511 void 512 session_group_synchronize_from(struct session *target) 513 { 514 struct session_group *sg; 515 struct session *s; 516 517 if ((sg = session_group_find(target)) == NULL) 518 return; 519 520 TAILQ_FOREACH(s, &sg->sessions, gentry) { 521 if (s != target) 522 session_group_synchronize1(target, s); 523 } 524 } 525 526 /* 527 * Synchronize a session with a target session. This means destroying all 528 * winlinks then recreating them, then updating the current window, last window 529 * stack and alerts. 530 */ 531 void 532 session_group_synchronize1(struct session *target, struct session *s) 533 { 534 struct winlinks old_windows, *ww; 535 struct winlink_stack old_lastw; 536 struct winlink *wl, *wl2; 537 538 /* Don't do anything if the session is empty (it'll be destroyed). */ 539 ww = &target->windows; 540 if (RB_EMPTY(ww)) 541 return; 542 543 /* If the current window has vanished, move to the next now. */ 544 if (s->curw != NULL && 545 winlink_find_by_index(ww, s->curw->idx) == NULL && 546 session_last(s) != 0 && session_previous(s, 0) != 0) 547 session_next(s, 0); 548 549 /* Save the old pointer and reset it. */ 550 memcpy(&old_windows, &s->windows, sizeof old_windows); 551 RB_INIT(&s->windows); 552 553 /* Link all the windows from the target. */ 554 RB_FOREACH(wl, winlinks, ww) { 555 wl2 = winlink_add(&s->windows, wl->idx); 556 winlink_set_window(wl2, wl->window); 557 notify_window_linked(s, wl2->window); 558 wl2->flags |= wl->flags & WINLINK_ALERTFLAGS; 559 } 560 561 /* Fix up the current window. */ 562 if (s->curw != NULL) 563 s->curw = winlink_find_by_index(&s->windows, s->curw->idx); 564 else 565 s->curw = winlink_find_by_index(&s->windows, target->curw->idx); 566 567 /* Fix up the last window stack. */ 568 memcpy(&old_lastw, &s->lastw, sizeof old_lastw); 569 TAILQ_INIT(&s->lastw); 570 TAILQ_FOREACH(wl, &old_lastw, sentry) { 571 wl2 = winlink_find_by_index(&s->windows, wl->idx); 572 if (wl2 != NULL) 573 TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry); 574 } 575 576 /* Then free the old winlinks list. */ 577 while (!RB_EMPTY(&old_windows)) { 578 wl = RB_ROOT(&old_windows); 579 if (winlink_find_by_window_id(&s->windows, wl->window->id) == NULL) 580 notify_window_unlinked(s, wl->window); 581 winlink_remove(&old_windows, wl); 582 } 583 } 584 585 /* Renumber the windows across winlinks attached to a specific session. */ 586 void 587 session_renumber_windows(struct session *s) 588 { 589 struct winlink *wl, *wl1, *wl_new; 590 struct winlinks old_wins; 591 struct winlink_stack old_lastw; 592 int new_idx, new_curw_idx; 593 594 /* Save and replace old window list. */ 595 memcpy(&old_wins, &s->windows, sizeof old_wins); 596 RB_INIT(&s->windows); 597 598 /* Start renumbering from the base-index if it's set. */ 599 new_idx = options_get_number(&s->options, "base-index"); 600 new_curw_idx = 0; 601 602 /* Go through the winlinks and assign new indexes. */ 603 RB_FOREACH(wl, winlinks, &old_wins) { 604 wl_new = winlink_add(&s->windows, new_idx); 605 winlink_set_window(wl_new, wl->window); 606 wl_new->flags |= wl->flags & WINLINK_ALERTFLAGS; 607 608 if (wl == s->curw) 609 new_curw_idx = wl_new->idx; 610 611 new_idx++; 612 } 613 614 /* Fix the stack of last windows now. */ 615 memcpy(&old_lastw, &s->lastw, sizeof old_lastw); 616 TAILQ_INIT(&s->lastw); 617 TAILQ_FOREACH(wl, &old_lastw, sentry) { 618 wl_new = winlink_find_by_window(&s->windows, wl->window); 619 if (wl_new != NULL) 620 TAILQ_INSERT_TAIL(&s->lastw, wl_new, sentry); 621 } 622 623 /* Set the current window. */ 624 s->curw = winlink_find_by_index(&s->windows, new_curw_idx); 625 626 /* Free the old winlinks (reducing window references too). */ 627 RB_FOREACH_SAFE(wl, winlinks, &old_wins, wl1) 628 winlink_remove(&old_wins, wl); 629 } 630