1 /* $OpenBSD: server-fn.c,v 1.113 2018/02/28 08:55:44 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 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/queue.h> 21 #include <sys/wait.h> 22 #include <sys/uio.h> 23 24 #include <imsg.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <time.h> 28 #include <unistd.h> 29 30 #include "tmux.h" 31 32 static struct session *server_next_session(struct session *); 33 static void server_destroy_session_group(struct session *); 34 35 void 36 server_redraw_client(struct client *c) 37 { 38 c->flags |= CLIENT_REDRAW; 39 } 40 41 void 42 server_status_client(struct client *c) 43 { 44 c->flags |= CLIENT_STATUS; 45 } 46 47 void 48 server_redraw_session(struct session *s) 49 { 50 struct client *c; 51 52 TAILQ_FOREACH(c, &clients, entry) { 53 if (c->session == s) 54 server_redraw_client(c); 55 } 56 } 57 58 void 59 server_redraw_session_group(struct session *s) 60 { 61 struct session_group *sg; 62 63 if ((sg = session_group_contains(s)) == NULL) 64 server_redraw_session(s); 65 else { 66 TAILQ_FOREACH(s, &sg->sessions, gentry) 67 server_redraw_session(s); 68 } 69 } 70 71 void 72 server_status_session(struct session *s) 73 { 74 struct client *c; 75 76 TAILQ_FOREACH(c, &clients, entry) { 77 if (c->session == s) 78 server_status_client(c); 79 } 80 } 81 82 void 83 server_status_session_group(struct session *s) 84 { 85 struct session_group *sg; 86 87 if ((sg = session_group_contains(s)) == NULL) 88 server_status_session(s); 89 else { 90 TAILQ_FOREACH(s, &sg->sessions, gentry) 91 server_status_session(s); 92 } 93 } 94 95 void 96 server_redraw_window(struct window *w) 97 { 98 struct client *c; 99 100 TAILQ_FOREACH(c, &clients, entry) { 101 if (c->session != NULL && c->session->curw->window == w) 102 server_redraw_client(c); 103 } 104 } 105 106 void 107 server_redraw_window_borders(struct window *w) 108 { 109 struct client *c; 110 111 TAILQ_FOREACH(c, &clients, entry) { 112 if (c->session != NULL && c->session->curw->window == w) 113 c->flags |= CLIENT_BORDERS; 114 } 115 } 116 117 void 118 server_status_window(struct window *w) 119 { 120 struct session *s; 121 122 /* 123 * This is slightly different. We want to redraw the status line of any 124 * clients containing this window rather than anywhere it is the 125 * current window. 126 */ 127 128 RB_FOREACH(s, sessions, &sessions) { 129 if (session_has(s, w)) 130 server_status_session(s); 131 } 132 } 133 134 void 135 server_lock(void) 136 { 137 struct client *c; 138 139 TAILQ_FOREACH(c, &clients, entry) { 140 if (c->session != NULL) 141 server_lock_client(c); 142 } 143 } 144 145 void 146 server_lock_session(struct session *s) 147 { 148 struct client *c; 149 150 TAILQ_FOREACH(c, &clients, entry) { 151 if (c->session == s) 152 server_lock_client(c); 153 } 154 } 155 156 void 157 server_lock_client(struct client *c) 158 { 159 const char *cmd; 160 161 if (c->flags & CLIENT_CONTROL) 162 return; 163 164 if (c->flags & CLIENT_SUSPENDED) 165 return; 166 167 cmd = options_get_string(c->session->options, "lock-command"); 168 if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE) 169 return; 170 171 tty_stop_tty(&c->tty); 172 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP)); 173 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR)); 174 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3)); 175 176 c->flags |= CLIENT_SUSPENDED; 177 proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1); 178 } 179 180 void 181 server_kill_window(struct window *w) 182 { 183 struct session *s, *next_s, *target_s; 184 struct session_group *sg; 185 struct winlink *wl; 186 187 next_s = RB_MIN(sessions, &sessions); 188 while (next_s != NULL) { 189 s = next_s; 190 next_s = RB_NEXT(sessions, &sessions, s); 191 192 if (!session_has(s, w)) 193 continue; 194 server_unzoom_window(w); 195 while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) { 196 if (session_detach(s, wl)) { 197 server_destroy_session_group(s); 198 break; 199 } else 200 server_redraw_session_group(s); 201 } 202 203 if (options_get_number(s->options, "renumber-windows")) { 204 if ((sg = session_group_contains(s)) != NULL) { 205 TAILQ_FOREACH(target_s, &sg->sessions, gentry) 206 session_renumber_windows(target_s); 207 } else 208 session_renumber_windows(s); 209 } 210 } 211 recalculate_sizes(); 212 } 213 214 int 215 server_link_window(struct session *src, struct winlink *srcwl, 216 struct session *dst, int dstidx, int killflag, int selectflag, 217 char **cause) 218 { 219 struct winlink *dstwl; 220 struct session_group *srcsg, *dstsg; 221 222 srcsg = session_group_contains(src); 223 dstsg = session_group_contains(dst); 224 if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) { 225 xasprintf(cause, "sessions are grouped"); 226 return (-1); 227 } 228 229 dstwl = NULL; 230 if (dstidx != -1) 231 dstwl = winlink_find_by_index(&dst->windows, dstidx); 232 if (dstwl != NULL) { 233 if (dstwl->window == srcwl->window) { 234 xasprintf(cause, "same index: %d", dstidx); 235 return (-1); 236 } 237 if (killflag) { 238 /* 239 * Can't use session_detach as it will destroy session 240 * if this makes it empty. 241 */ 242 notify_session_window("window-unlinked", dst, 243 dstwl->window); 244 dstwl->flags &= ~WINLINK_ALERTFLAGS; 245 winlink_stack_remove(&dst->lastw, dstwl); 246 winlink_remove(&dst->windows, dstwl); 247 248 /* Force select/redraw if current. */ 249 if (dstwl == dst->curw) { 250 selectflag = 1; 251 dst->curw = NULL; 252 } 253 } 254 } 255 256 if (dstidx == -1) 257 dstidx = -1 - options_get_number(dst->options, "base-index"); 258 dstwl = session_attach(dst, srcwl->window, dstidx, cause); 259 if (dstwl == NULL) 260 return (-1); 261 262 if (selectflag) 263 session_select(dst, dstwl->idx); 264 server_redraw_session_group(dst); 265 266 return (0); 267 } 268 269 void 270 server_unlink_window(struct session *s, struct winlink *wl) 271 { 272 if (session_detach(s, wl)) 273 server_destroy_session_group(s); 274 else 275 server_redraw_session_group(s); 276 } 277 278 void 279 server_destroy_pane(struct window_pane *wp, int notify) 280 { 281 struct window *w = wp->window; 282 struct screen_write_ctx ctx; 283 struct grid_cell gc; 284 time_t t; 285 char tim[26]; 286 287 if (wp->fd != -1) { 288 bufferevent_free(wp->event); 289 close(wp->fd); 290 wp->fd = -1; 291 } 292 293 if (options_get_number(w->options, "remain-on-exit")) { 294 if (~wp->flags & PANE_STATUSREADY) 295 return; 296 297 if (wp->flags & PANE_STATUSDRAWN) 298 return; 299 wp->flags |= PANE_STATUSDRAWN; 300 301 if (notify) 302 notify_pane("pane-died", wp); 303 304 screen_write_start(&ctx, wp, &wp->base); 305 screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1); 306 screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1); 307 screen_write_linefeed(&ctx, 1, 8); 308 memcpy(&gc, &grid_default_cell, sizeof gc); 309 310 time(&t); 311 ctime_r(&t, tim); 312 313 if (WIFEXITED(wp->status)) { 314 screen_write_nputs(&ctx, -1, &gc, 315 "Pane is dead (status %d, %s)", 316 WEXITSTATUS(wp->status), 317 tim); 318 } else if (WIFSIGNALED(wp->status)) { 319 screen_write_nputs(&ctx, -1, &gc, 320 "Pane is dead (signal %d, %s)", 321 WTERMSIG(wp->status), 322 tim); 323 } 324 325 screen_write_stop(&ctx); 326 wp->flags |= PANE_REDRAW; 327 return; 328 } 329 330 if (notify) 331 notify_pane("pane-exited", wp); 332 333 server_unzoom_window(w); 334 layout_close_pane(wp); 335 window_remove_pane(w, wp); 336 337 if (TAILQ_EMPTY(&w->panes)) 338 server_kill_window(w); 339 else 340 server_redraw_window(w); 341 } 342 343 static void 344 server_destroy_session_group(struct session *s) 345 { 346 struct session_group *sg; 347 struct session *s1; 348 349 if ((sg = session_group_contains(s)) == NULL) 350 server_destroy_session(s); 351 else { 352 TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) { 353 server_destroy_session(s); 354 session_destroy(s, __func__); 355 } 356 } 357 } 358 359 static struct session * 360 server_next_session(struct session *s) 361 { 362 struct session *s_loop, *s_out; 363 364 s_out = NULL; 365 RB_FOREACH(s_loop, sessions, &sessions) { 366 if (s_loop == s) 367 continue; 368 if (s_out == NULL || 369 timercmp(&s_loop->activity_time, &s_out->activity_time, <)) 370 s_out = s_loop; 371 } 372 return (s_out); 373 } 374 375 void 376 server_destroy_session(struct session *s) 377 { 378 struct client *c; 379 struct session *s_new; 380 381 if (!options_get_number(s->options, "detach-on-destroy")) 382 s_new = server_next_session(s); 383 else 384 s_new = NULL; 385 386 TAILQ_FOREACH(c, &clients, entry) { 387 if (c->session != s) 388 continue; 389 if (s_new == NULL) { 390 c->session = NULL; 391 c->flags |= CLIENT_EXIT; 392 } else { 393 c->last_session = NULL; 394 c->session = s_new; 395 server_client_set_key_table(c, NULL); 396 status_timer_start(c); 397 notify_client("client-session-changed", c); 398 session_update_activity(s_new, NULL); 399 gettimeofday(&s_new->last_attached_time, NULL); 400 server_redraw_client(c); 401 alerts_check_session(s_new); 402 } 403 } 404 recalculate_sizes(); 405 } 406 407 void 408 server_check_unattached(void) 409 { 410 struct session *s; 411 412 /* 413 * If any sessions are no longer attached and have destroy-unattached 414 * set, collect them. 415 */ 416 RB_FOREACH(s, sessions, &sessions) { 417 if (!(s->flags & SESSION_UNATTACHED)) 418 continue; 419 if (options_get_number (s->options, "destroy-unattached")) 420 session_destroy(s, __func__); 421 } 422 } 423 424 /* Set stdin callback. */ 425 int 426 server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int, 427 void *), void *cb_data, char **cause) 428 { 429 if (c == NULL || c->session != NULL) { 430 *cause = xstrdup("no client with stdin"); 431 return (-1); 432 } 433 if (c->flags & CLIENT_TERMINAL) { 434 *cause = xstrdup("stdin is a tty"); 435 return (-1); 436 } 437 if (c->stdin_callback != NULL) { 438 *cause = xstrdup("stdin in use"); 439 return (-1); 440 } 441 442 c->stdin_callback_data = cb_data; 443 c->stdin_callback = cb; 444 445 c->references++; 446 447 if (c->stdin_closed) 448 c->stdin_callback(c, 1, c->stdin_callback_data); 449 450 proc_send(c->peer, MSG_STDIN, -1, NULL, 0); 451 452 return (0); 453 } 454 455 void 456 server_unzoom_window(struct window *w) 457 { 458 if (window_unzoom(w) == 0) 459 server_redraw_window(w); 460 } 461