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