1 /* $OpenBSD: session.c,v 1.11 2009/10/10 10:02:48 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 struct session_groups session_groups; 34 35 struct winlink *session_next_activity(struct session *, struct winlink *); 36 struct winlink *session_previous_activity(struct session *, struct winlink *); 37 38 void 39 session_alert_cancel(struct session *s, struct winlink *wl) 40 { 41 struct session_alert *sa, *sb; 42 43 sa = SLIST_FIRST(&s->alerts); 44 while (sa != NULL) { 45 sb = sa; 46 sa = SLIST_NEXT(sa, entry); 47 48 if (wl == NULL || sb->wl == wl) { 49 SLIST_REMOVE(&s->alerts, sb, session_alert, entry); 50 xfree(sb); 51 } 52 } 53 } 54 55 void 56 session_alert_add(struct session *s, struct window *w, int type) 57 { 58 struct session_alert *sa; 59 struct winlink *wl; 60 61 RB_FOREACH(wl, winlinks, &s->windows) { 62 if (wl == s->curw) 63 continue; 64 65 if (wl->window == w && 66 !session_alert_has(s, wl, type)) { 67 sa = xmalloc(sizeof *sa); 68 sa->wl = wl; 69 sa->type = type; 70 SLIST_INSERT_HEAD(&s->alerts, sa, entry); 71 } 72 } 73 } 74 75 int 76 session_alert_has(struct session *s, struct winlink *wl, int type) 77 { 78 struct session_alert *sa; 79 80 SLIST_FOREACH(sa, &s->alerts, entry) { 81 if (sa->wl == wl && sa->type == type) 82 return (1); 83 } 84 85 return (0); 86 } 87 88 int 89 session_alert_has_window(struct session *s, struct window *w, int type) 90 { 91 struct session_alert *sa; 92 93 SLIST_FOREACH(sa, &s->alerts, entry) { 94 if (sa->wl->window == w && sa->type == type) 95 return (1); 96 } 97 98 return (0); 99 } 100 101 /* Find session by name. */ 102 struct session * 103 session_find(const char *name) 104 { 105 struct session *s; 106 u_int i; 107 108 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { 109 s = ARRAY_ITEM(&sessions, i); 110 if (s != NULL && strcmp(s->name, name) == 0) 111 return (s); 112 } 113 114 return (NULL); 115 } 116 117 /* Create a new session. */ 118 struct session * 119 session_create(const char *name, const char *cmd, const char *cwd, 120 struct environ *env, struct termios *tio, int idx, u_int sx, u_int sy, 121 char **cause) 122 { 123 struct session *s; 124 u_int i; 125 126 s = xmalloc(sizeof *s); 127 s->references = 0; 128 s->flags = 0; 129 s->activity = time(NULL); 130 131 if (gettimeofday(&s->tv, NULL) != 0) 132 fatal("gettimeofday failed"); 133 134 s->curw = NULL; 135 TAILQ_INIT(&s->lastw); 136 RB_INIT(&s->windows); 137 SLIST_INIT(&s->alerts); 138 139 paste_init_stack(&s->buffers); 140 141 options_init(&s->options, &global_s_options); 142 environ_init(&s->environ); 143 if (env != NULL) 144 environ_copy(env, &s->environ); 145 146 s->tio = NULL; 147 if (tio != NULL) { 148 s->tio = xmalloc(sizeof *s->tio); 149 memcpy(s->tio, tio, sizeof *s->tio); 150 } 151 152 s->sx = sx; 153 s->sy = sy; 154 155 for (i = 0; i < ARRAY_LENGTH(&sessions); i++) { 156 if (ARRAY_ITEM(&sessions, i) == NULL) { 157 ARRAY_SET(&sessions, i, s); 158 break; 159 } 160 } 161 if (i == ARRAY_LENGTH(&sessions)) 162 ARRAY_ADD(&sessions, s); 163 164 if (name != NULL) 165 s->name = xstrdup(name); 166 else 167 xasprintf(&s->name, "%u", i); 168 169 if (cmd != NULL) { 170 if (session_new(s, NULL, cmd, cwd, idx, cause) == NULL) { 171 session_destroy(s); 172 return (NULL); 173 } 174 session_select(s, RB_ROOT(&s->windows)->idx); 175 } 176 177 log_debug("session %s created", s->name); 178 179 return (s); 180 } 181 182 /* Destroy a session. */ 183 void 184 session_destroy(struct session *s) 185 { 186 u_int i; 187 188 log_debug("session %s destroyed", s->name); 189 190 if (session_index(s, &i) != 0) 191 fatalx("session not found"); 192 ARRAY_SET(&sessions, i, NULL); 193 while (!ARRAY_EMPTY(&sessions) && ARRAY_LAST(&sessions) == NULL) 194 ARRAY_TRUNC(&sessions, 1); 195 196 if (s->tio != NULL) 197 xfree(s->tio); 198 199 session_group_remove(s); 200 session_alert_cancel(s, NULL); 201 environ_free(&s->environ); 202 options_free(&s->options); 203 paste_free_stack(&s->buffers); 204 205 while (!TAILQ_EMPTY(&s->lastw)) 206 winlink_stack_remove(&s->lastw, TAILQ_FIRST(&s->lastw)); 207 while (!RB_EMPTY(&s->windows)) 208 winlink_remove(&s->windows, RB_ROOT(&s->windows)); 209 210 xfree(s->name); 211 212 for (i = 0; i < ARRAY_LENGTH(&dead_sessions); i++) { 213 if (ARRAY_ITEM(&dead_sessions, i) == NULL) { 214 ARRAY_SET(&dead_sessions, i, s); 215 break; 216 } 217 } 218 if (i == ARRAY_LENGTH(&dead_sessions)) 219 ARRAY_ADD(&dead_sessions, s); 220 s->flags |= SESSION_DEAD; 221 } 222 223 /* Find session index. */ 224 int 225 session_index(struct session *s, u_int *i) 226 { 227 for (*i = 0; *i < ARRAY_LENGTH(&sessions); (*i)++) { 228 if (s == ARRAY_ITEM(&sessions, *i)) 229 return (0); 230 } 231 return (-1); 232 } 233 234 /* Create a new window on a session. */ 235 struct winlink * 236 session_new(struct session *s, 237 const char *name, const char *cmd, const char *cwd, int idx, char **cause) 238 { 239 struct window *w; 240 struct environ env; 241 const char *shell; 242 u_int hlimit; 243 244 environ_init(&env); 245 environ_copy(&global_environ, &env); 246 environ_copy(&s->environ, &env); 247 server_fill_environ(s, &env); 248 249 shell = options_get_string(&s->options, "default-shell"); 250 if (*shell == '\0' || areshell(shell)) 251 shell = _PATH_BSHELL; 252 253 hlimit = options_get_number(&s->options, "history-limit"); 254 w = window_create( 255 name, cmd, shell, cwd, &env, s->tio, s->sx, s->sy, hlimit, cause); 256 if (w == NULL) { 257 environ_free(&env); 258 return (NULL); 259 } 260 environ_free(&env); 261 262 if (options_get_number(&s->options, "set-remain-on-exit")) 263 options_set_number(&w->options, "remain-on-exit", 1); 264 265 return (session_attach(s, w, idx, cause)); 266 } 267 268 /* Attach a window to a session. */ 269 struct winlink * 270 session_attach(struct session *s, struct window *w, int idx, char **cause) 271 { 272 struct winlink *wl; 273 274 if ((wl = winlink_add(&s->windows, w, idx)) == NULL) 275 xasprintf(cause, "index in use: %d", idx); 276 session_group_synchronize_from(s); 277 return (wl); 278 } 279 280 /* Detach a window from a session. */ 281 int 282 session_detach(struct session *s, struct winlink *wl) 283 { 284 if (s->curw == wl && 285 session_last(s) != 0 && session_previous(s, 0) != 0) 286 session_next(s, 0); 287 288 session_alert_cancel(s, wl); 289 winlink_stack_remove(&s->lastw, wl); 290 winlink_remove(&s->windows, wl); 291 session_group_synchronize_from(s); 292 if (RB_EMPTY(&s->windows)) { 293 session_destroy(s); 294 return (1); 295 } 296 return (0); 297 } 298 299 /* Return if session has window. */ 300 int 301 session_has(struct session *s, struct window *w) 302 { 303 struct winlink *wl; 304 305 RB_FOREACH(wl, winlinks, &s->windows) { 306 if (wl->window == w) 307 return (1); 308 } 309 return (0); 310 } 311 312 struct winlink * 313 session_next_activity(struct session *s, struct winlink *wl) 314 { 315 while (wl != NULL) { 316 if (session_alert_has(s, wl, WINDOW_BELL)) 317 break; 318 if (session_alert_has(s, wl, WINDOW_ACTIVITY)) 319 break; 320 if (session_alert_has(s, wl, WINDOW_CONTENT)) 321 break; 322 wl = winlink_next(&s->windows, wl); 323 } 324 return (wl); 325 } 326 327 /* Move session to next window. */ 328 int 329 session_next(struct session *s, int activity) 330 { 331 struct winlink *wl; 332 333 if (s->curw == NULL) 334 return (-1); 335 336 wl = winlink_next(&s->windows, s->curw); 337 if (activity) 338 wl = session_next_activity(s, wl); 339 if (wl == NULL) { 340 wl = RB_MIN(winlinks, &s->windows); 341 if (activity && ((wl = session_next_activity(s, wl)) == NULL)) 342 return (-1); 343 } 344 if (wl == s->curw) 345 return (1); 346 winlink_stack_remove(&s->lastw, wl); 347 winlink_stack_push(&s->lastw, s->curw); 348 s->curw = wl; 349 session_alert_cancel(s, wl); 350 return (0); 351 } 352 353 struct winlink * 354 session_previous_activity(struct session *s, struct winlink *wl) 355 { 356 while (wl != NULL) { 357 if (session_alert_has(s, wl, WINDOW_BELL)) 358 break; 359 if (session_alert_has(s, wl, WINDOW_ACTIVITY)) 360 break; 361 if (session_alert_has(s, wl, WINDOW_CONTENT)) 362 break; 363 wl = winlink_previous(&s->windows, wl); 364 } 365 return (wl); 366 } 367 368 /* Move session to previous window. */ 369 int 370 session_previous(struct session *s, int activity) 371 { 372 struct winlink *wl; 373 374 if (s->curw == NULL) 375 return (-1); 376 377 wl = winlink_previous(&s->windows, s->curw); 378 if (activity) 379 wl = session_previous_activity(s, wl); 380 if (wl == NULL) { 381 wl = RB_MAX(winlinks, &s->windows); 382 if (activity && (wl = session_previous_activity(s, wl)) == NULL) 383 return (-1); 384 } 385 if (wl == s->curw) 386 return (1); 387 winlink_stack_remove(&s->lastw, wl); 388 winlink_stack_push(&s->lastw, s->curw); 389 s->curw = wl; 390 session_alert_cancel(s, wl); 391 return (0); 392 } 393 394 /* Move session to specific window. */ 395 int 396 session_select(struct session *s, int idx) 397 { 398 struct winlink *wl; 399 400 wl = winlink_find_by_index(&s->windows, idx); 401 if (wl == NULL) 402 return (-1); 403 if (wl == s->curw) 404 return (1); 405 winlink_stack_remove(&s->lastw, wl); 406 winlink_stack_push(&s->lastw, s->curw); 407 s->curw = wl; 408 session_alert_cancel(s, wl); 409 return (0); 410 } 411 412 /* Move session to last used window. */ 413 int 414 session_last(struct session *s) 415 { 416 struct winlink *wl; 417 418 wl = TAILQ_FIRST(&s->lastw); 419 if (wl == NULL) 420 return (-1); 421 if (wl == s->curw) 422 return (1); 423 424 winlink_stack_remove(&s->lastw, wl); 425 winlink_stack_push(&s->lastw, s->curw); 426 s->curw = wl; 427 session_alert_cancel(s, wl); 428 return (0); 429 } 430 431 /* Find the session group containing a session. */ 432 struct session_group * 433 session_group_find(struct session *target) 434 { 435 struct session_group *sg; 436 struct session *s; 437 438 TAILQ_FOREACH(sg, &session_groups, entry) { 439 TAILQ_FOREACH(s, &sg->sessions, gentry) { 440 if (s == target) 441 return (sg); 442 } 443 } 444 return (NULL); 445 } 446 447 /* Find session group index. */ 448 u_int 449 session_group_index(struct session_group *sg) 450 { 451 struct session_group *sg2; 452 u_int i; 453 454 i = 0; 455 TAILQ_FOREACH(sg2, &session_groups, entry) { 456 if (sg == sg2) 457 return (i); 458 i++; 459 } 460 461 fatalx("session group not found"); 462 } 463 464 /* 465 * Add a session to the session group containing target, creating it if 466 * necessary. 467 */ 468 void 469 session_group_add(struct session *target, struct session *s) 470 { 471 struct session_group *sg; 472 473 if ((sg = session_group_find(target)) == NULL) { 474 sg = xmalloc(sizeof *sg); 475 TAILQ_INSERT_TAIL(&session_groups, sg, entry); 476 TAILQ_INIT(&sg->sessions); 477 TAILQ_INSERT_TAIL(&sg->sessions, target, gentry); 478 } 479 TAILQ_INSERT_TAIL(&sg->sessions, s, gentry); 480 } 481 482 /* Remove a session from its group and destroy the group if empty. */ 483 void 484 session_group_remove(struct session *s) 485 { 486 struct session_group *sg; 487 488 if ((sg = session_group_find(s)) == NULL) 489 return; 490 TAILQ_REMOVE(&sg->sessions, s, gentry); 491 if (TAILQ_NEXT(TAILQ_FIRST(&sg->sessions), gentry) == NULL) 492 TAILQ_REMOVE(&sg->sessions, TAILQ_FIRST(&sg->sessions), gentry); 493 if (TAILQ_EMPTY(&sg->sessions)) { 494 TAILQ_REMOVE(&session_groups, sg, entry); 495 xfree(sg); 496 } 497 } 498 499 /* Synchronize a session to its session group. */ 500 void 501 session_group_synchronize_to(struct session *s) 502 { 503 struct session_group *sg; 504 struct session *target; 505 506 if ((sg = session_group_find(s)) == NULL) 507 return; 508 509 target = NULL; 510 TAILQ_FOREACH(target, &sg->sessions, gentry) { 511 if (target != s) 512 break; 513 } 514 session_group_synchronize1(target, s); 515 } 516 517 /* Synchronize a session group to a session. */ 518 void 519 session_group_synchronize_from(struct session *target) 520 { 521 struct session_group *sg; 522 struct session *s; 523 524 if ((sg = session_group_find(target)) == NULL) 525 return; 526 527 TAILQ_FOREACH(s, &sg->sessions, gentry) { 528 if (s != target) 529 session_group_synchronize1(target, s); 530 } 531 } 532 533 /* 534 * Synchronize a session with a target session. This means destroying all 535 * winlinks then recreating them, then updating the current window, last window 536 * stack and alerts. 537 */ 538 void 539 session_group_synchronize1(struct session *target, struct session *s) 540 { 541 struct winlinks old_windows, *ww; 542 struct winlink_stack old_lastw; 543 struct winlink *wl, *wl2; 544 struct session_alert *sa; 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 while (winlink_find_by_index(ww, s->curw->idx) == NULL) 554 session_next(s, 0); 555 } 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 winlink_add(&s->windows, wl->window, wl->idx); 564 565 /* Fix up the current window. */ 566 if (s->curw != NULL) 567 s->curw = winlink_find_by_index(&s->windows, s->curw->idx); 568 else 569 s->curw = winlink_find_by_index(&s->windows, target->curw->idx); 570 571 /* Fix up the last window stack. */ 572 memcpy(&old_lastw, &s->lastw, sizeof old_lastw); 573 TAILQ_INIT(&s->lastw); 574 TAILQ_FOREACH(wl, &old_lastw, sentry) { 575 wl2 = winlink_find_by_index(&s->windows, wl->idx); 576 if (wl2 != NULL) 577 TAILQ_INSERT_TAIL(&s->lastw, wl2, sentry); 578 } 579 580 /* And update the alerts list. */ 581 SLIST_FOREACH(sa, &s->alerts, entry) { 582 wl = winlink_find_by_index(&s->windows, sa->wl->idx); 583 if (wl == NULL) 584 session_alert_cancel(s, sa->wl); 585 else 586 sa->wl = wl; 587 } 588 589 /* Then free the old winlinks list. */ 590 while (!RB_EMPTY(&old_windows)) { 591 wl = RB_ROOT(&old_windows); 592 RB_REMOVE(winlinks, &old_windows, wl); 593 xfree(wl); 594 } 595 } 596