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