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