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