Coverage Report

Created: 2026-09-01 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/server-fn.c
Line
Count
Source
1
/* $OpenBSD: server-fn.c,v 1.151 2026/08/20 09:19:24 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/wait.h>
21
#include <sys/uio.h>
22
23
#include <signal.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 void server_destroy_session_group(struct session *);
32
static void server_fire_pane_exit(const char *, struct window_pane *);
33
34
static void
35
server_fire_pane_exit(const char *name, struct window_pane *wp)
36
0
{
37
0
  struct event_payload  *ep;
38
0
  struct cmd_find_state  fs;
39
0
  int      status = wp->status;
40
0
  const char    *signame;
41
42
0
  if (WIFSIGNALED(status))
43
0
    signame = sig2name(WTERMSIG(status));
44
45
0
  ep = event_payload_create();
46
0
  cmd_find_from_pane(&fs, wp, 0);
47
0
  event_payload_set_target(ep, &fs);
48
0
  event_payload_set_pane(ep, "pane", wp);
49
0
  event_payload_set_window(ep, "window", wp->window);
50
0
  if (WIFEXITED(status))
51
0
    event_payload_set_int(ep, "exit_status", WEXITSTATUS(status));
52
0
  else if (WIFSIGNALED(status))
53
0
    event_payload_set_string(ep, "exit_signal", "%s", signame);
54
0
  event_payload_set_int(ep, "exit_success", status == 0);
55
0
  events_fire(name, ep);
56
0
}
57
58
void
59
server_redraw_client(struct client *c)
60
0
{
61
0
  c->flags |= CLIENT_ALLREDRAWFLAGS;
62
0
}
63
64
void
65
server_status_client(struct client *c)
66
0
{
67
0
  c->flags |= CLIENT_REDRAWSTATUS;
68
0
}
69
70
void
71
server_redraw_session(struct session *s)
72
0
{
73
0
  struct client *c;
74
75
0
  TAILQ_FOREACH(c, &clients, entry) {
76
0
    if (c->session == s)
77
0
      server_redraw_client(c);
78
0
  }
79
0
}
80
81
void
82
server_redraw_session_group(struct session *s)
83
0
{
84
0
  struct session_group  *sg;
85
86
0
  if ((sg = session_group_contains(s)) == NULL)
87
0
    server_redraw_session(s);
88
0
  else {
89
0
    TAILQ_FOREACH(s, &sg->sessions, gentry)
90
0
      server_redraw_session(s);
91
0
  }
92
0
}
93
94
void
95
server_status_session(struct session *s)
96
0
{
97
0
  struct client *c;
98
99
0
  TAILQ_FOREACH(c, &clients, entry) {
100
0
    if (c->session == s)
101
0
      server_status_client(c);
102
0
  }
103
0
}
104
105
void
106
server_status_session_group(struct session *s)
107
0
{
108
0
  struct session_group  *sg;
109
110
0
  if ((sg = session_group_contains(s)) == NULL)
111
0
    server_status_session(s);
112
0
  else {
113
0
    TAILQ_FOREACH(s, &sg->sessions, gentry)
114
0
      server_status_session(s);
115
0
  }
116
0
}
117
118
void
119
server_redraw_window(struct window *w)
120
0
{
121
0
  struct client *c;
122
123
0
  TAILQ_FOREACH(c, &clients, entry) {
124
0
    if (c->session != NULL &&
125
0
        c->session->curw != NULL &&
126
0
        c->session->curw->window == w)
127
0
      server_redraw_client(c);
128
0
  }
129
0
}
130
131
void
132
server_redraw_window_menu(struct window *w)
133
0
{
134
0
  struct client *c;
135
136
0
  TAILQ_FOREACH(c, &clients, entry) {
137
0
    if (c->session != NULL &&
138
0
        c->session->curw != NULL &&
139
0
        c->session->curw->window == w)
140
0
      c->flags |= CLIENT_REDRAWMENU;
141
0
  }
142
0
}
143
144
void
145
server_redraw_window_borders(struct window *w)
146
0
{
147
0
  struct client *c;
148
149
0
  TAILQ_FOREACH(c, &clients, entry) {
150
0
    if (c->session != NULL &&
151
0
        c->session->curw != NULL &&
152
0
        c->session->curw->window == w)
153
0
      c->flags |= CLIENT_REDRAWBORDERS;
154
0
  }
155
0
}
156
157
void
158
server_status_window(struct window *w)
159
0
{
160
0
  struct session  *s;
161
162
  /*
163
   * This is slightly different. We want to redraw the status line of any
164
   * clients containing this window rather than anywhere it is the
165
   * current window.
166
   */
167
168
0
  RB_FOREACH(s, sessions, &sessions) {
169
0
    if (session_has(s, w))
170
0
      server_status_session(s);
171
0
  }
172
0
}
173
174
void
175
server_lock(void)
176
0
{
177
0
  struct client *c;
178
179
0
  TAILQ_FOREACH(c, &clients, entry) {
180
0
    if (c->session != NULL)
181
0
      server_lock_client(c);
182
0
  }
183
0
}
184
185
void
186
server_lock_session(struct session *s)
187
0
{
188
0
  struct client *c;
189
190
0
  TAILQ_FOREACH(c, &clients, entry) {
191
0
    if (c->session == s)
192
0
      server_lock_client(c);
193
0
  }
194
0
}
195
196
void
197
server_lock_client(struct client *c)
198
0
{
199
0
  const char  *cmd;
200
201
0
  if (c->flags & CLIENT_CONTROL)
202
0
    return;
203
204
0
  if (c->flags & CLIENT_SUSPENDED)
205
0
    return;
206
207
0
  cmd = options_get_string(c->session->options, "lock-command");
208
0
  if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
209
0
    return;
210
211
0
  tty_stop_tty(&c->tty);
212
0
  tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP));
213
0
  tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR));
214
0
  tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3));
215
216
0
  c->flags |= CLIENT_SUSPENDED;
217
0
  proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1);
218
0
}
219
220
void
221
server_kill_pane(struct window_pane *wp)
222
0
{
223
0
  struct window *w = wp->window;
224
225
0
  if (window_count_panes(w, 1) == 1) {
226
0
    server_kill_window(w, 1);
227
0
    recalculate_sizes();
228
0
  } else {
229
0
    window_push_zoom(w, 0, wp->flags & PANE_FLOATOVERZOOM);
230
0
    server_client_remove_pane(wp);
231
0
    layout_close_pane(wp);
232
0
    window_remove_pane(w, wp);
233
0
    window_pop_zoom(w);
234
0
    server_redraw_window(w);
235
0
  }
236
0
}
237
238
void
239
server_kill_window(struct window *w, int renumber)
240
0
{
241
0
  struct session  *s, *s1;
242
0
  struct winlink  *wl;
243
244
0
  window_add_ref(w, __func__);
245
0
  RB_FOREACH_SAFE(s, sessions, &sessions, s1) {
246
0
    if (!session_has(s, w))
247
0
      continue;
248
249
0
    server_unzoom_window(w);
250
0
    while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
251
0
      if (session_detach(s, wl)) {
252
0
        server_destroy_session_group(s);
253
0
        break;
254
0
      }
255
0
      server_redraw_session_group(s);
256
0
    }
257
258
0
    if (renumber)
259
0
      server_renumber_session(s);
260
0
  }
261
0
  recalculate_sizes();
262
0
  window_remove_ref(w, __func__);
263
0
}
264
265
void
266
server_renumber_session(struct session *s)
267
0
{
268
0
  struct session_group  *sg;
269
270
0
  if (options_get_number(s->options, "renumber-windows")) {
271
0
    if ((sg = session_group_contains(s)) != NULL) {
272
0
      TAILQ_FOREACH(s, &sg->sessions, gentry)
273
0
          session_renumber_windows(s);
274
0
    } else
275
0
      session_renumber_windows(s);
276
0
  }
277
0
}
278
279
void
280
server_renumber_all(void)
281
0
{
282
0
  struct session  *s;
283
284
0
  RB_FOREACH(s, sessions, &sessions)
285
0
    server_renumber_session(s);
286
0
}
287
288
int
289
server_link_window(struct session *src, struct winlink *srcwl,
290
    struct session *dst, int dstidx, int killflag, int selectflag,
291
    char **cause)
292
0
{
293
0
  struct winlink    *dstwl;
294
0
  struct session_group  *srcsg, *dstsg;
295
296
0
  srcsg = session_group_contains(src);
297
0
  dstsg = session_group_contains(dst);
298
0
  if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
299
0
    xasprintf(cause, "sessions are grouped");
300
0
    return (-1);
301
0
  }
302
303
0
  dstwl = NULL;
304
0
  if (dstidx != -1)
305
0
    dstwl = winlink_find_by_index(&dst->windows, dstidx);
306
0
  if (dstwl != NULL) {
307
0
    if (dstwl->window == srcwl->window) {
308
0
      xasprintf(cause, "same index: %d", dstidx);
309
0
      return (-1);
310
0
    }
311
0
    if (killflag) {
312
      /*
313
       * Can't use session_detach as it will destroy session
314
       * if this makes it empty.
315
       */
316
0
      events_fire_winlink("window-unlinked", dstwl);
317
0
      dstwl->flags &= ~WINLINK_ALERTFLAGS;
318
0
      winlink_stack_remove(&dst->lastw, dstwl);
319
0
      winlink_remove(&dst->windows, dstwl);
320
321
      /* Force select/redraw if current. */
322
0
      if (dstwl == dst->curw) {
323
0
        selectflag = 1;
324
0
        dst->curw = NULL;
325
0
      }
326
0
    }
327
0
  }
328
329
0
  if (dstidx == -1)
330
0
    dstidx = -1 - options_get_number(dst->options, "base-index");
331
0
  dstwl = session_attach(dst, srcwl->window, dstidx, cause);
332
0
  if (dstwl == NULL)
333
0
    return (-1);
334
335
0
  if (marked_pane.wl == srcwl)
336
0
    marked_pane.wl = dstwl;
337
0
  if (selectflag)
338
0
    session_select(dst, dstwl->idx);
339
0
  server_redraw_session_group(dst);
340
341
0
  return (0);
342
0
}
343
344
void
345
server_unlink_window(struct session *s, struct winlink *wl)
346
0
{
347
0
  if (session_detach(s, wl))
348
0
    server_destroy_session_group(s);
349
0
  else
350
0
    server_redraw_session_group(s);
351
0
}
352
353
void
354
server_destroy_pane(struct window_pane *wp, int notify)
355
0
{
356
0
  struct window   *w = wp->window;
357
0
  struct screen_write_ctx  ctx;
358
0
  struct grid_cell   gc;
359
0
  int      remain_on_exit;
360
0
  const char    *s;
361
0
  char      *expanded;
362
0
  u_int      sx = screen_size_x(&wp->base);
363
0
  u_int      sy = screen_size_y(&wp->base);
364
365
0
  if (wp->fd != -1) {
366
#ifdef HAVE_UTEMPTER
367
    utempter_remove_record(wp->fd);
368
    kill(getpid(), SIGCHLD);
369
#endif
370
0
    bufferevent_free(wp->event);
371
0
    wp->event = NULL;
372
0
    close(wp->fd);
373
0
    wp->fd = -1;
374
0
  }
375
0
  if (wp->pipe_fd != -1) {
376
0
    bufferevent_free(wp->pipe_event);
377
0
    wp->pipe_event = NULL;
378
0
    close(wp->pipe_fd);
379
0
    wp->pipe_fd = -1;
380
0
  }
381
382
0
  remain_on_exit = options_get_number(wp->options, "remain-on-exit");
383
0
  if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
384
0
    return;
385
0
  switch (remain_on_exit) {
386
0
  case 0:
387
0
    break;
388
0
  case 2:
389
0
    if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
390
0
      break;
391
    /* FALLTHROUGH */
392
0
  case 1:
393
0
  case 3:
394
0
    if (wp->flags & PANE_STATUSDRAWN)
395
0
      return;
396
0
    wp->flags |= PANE_STATUSDRAWN;
397
398
0
    gettimeofday(&wp->dead_time, NULL);
399
0
    if (notify)
400
0
      server_fire_pane_exit("pane-died", wp);
401
402
0
    s = options_get_string(wp->options, "remain-on-exit-format");
403
0
    if (*s != '\0') {
404
0
      screen_write_start_pane(&ctx, wp, &wp->base);
405
0
      screen_write_scrollregion(&ctx, 0, sy - 1);
406
0
      screen_write_cursormove(&ctx, 0, sy - 1, 0);
407
0
      screen_write_linefeed(&ctx, 1, 8);
408
0
      memcpy(&gc, &grid_default_cell, sizeof gc);
409
410
0
      expanded = format_single(NULL, s, NULL, NULL, NULL, wp);
411
0
      format_draw(&ctx, &gc, sx, expanded, NULL, 0);
412
0
      free(expanded);
413
414
0
      screen_write_stop(&ctx);
415
0
    }
416
0
    wp->base.mode &= ~MODE_CURSOR;
417
418
0
    wp->flags |= PANE_REDRAW;
419
0
    return;
420
0
  }
421
422
0
  if (notify)
423
0
    server_fire_pane_exit("pane-exited", wp);
424
425
0
  window_push_zoom(w, 0, wp->flags & PANE_FLOATOVERZOOM);
426
0
  server_client_remove_pane(wp);
427
0
  layout_close_pane(wp);
428
0
  window_remove_pane(w, wp);
429
430
0
  if (TAILQ_EMPTY(&w->panes))
431
0
    server_kill_window(w, 1);
432
0
  else {
433
0
    window_pop_zoom(w);
434
0
    server_redraw_window(w);
435
0
  }
436
0
}
437
438
static void
439
server_destroy_session_group(struct session *s)
440
0
{
441
0
  struct session_group  *sg;
442
0
  struct session    *s1;
443
444
0
  if ((sg = session_group_contains(s)) == NULL) {
445
0
    server_destroy_session(s);
446
0
    session_destroy(s, 1, __func__);
447
0
  } else {
448
0
    TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
449
0
      server_destroy_session(s);
450
0
      session_destroy(s, 1, __func__);
451
0
    }
452
0
  }
453
0
}
454
455
static struct session *
456
server_find_session(struct session *s,
457
    int (*f)(struct session *, struct session *))
458
0
{
459
0
  struct session *s_loop, *s_out = NULL;
460
461
0
  RB_FOREACH(s_loop, sessions, &sessions) {
462
0
    if (s_loop != s && f(s_loop, s_out))
463
0
      s_out = s_loop;
464
0
  }
465
0
  return (s_out);
466
0
}
467
468
static int
469
server_newer_session(struct session *s_loop, struct session *s_out)
470
0
{
471
0
  if (s_out == NULL)
472
0
    return (1);
473
0
  return (timercmp(&s_loop->activity_time, &s_out->activity_time, >));
474
0
}
475
476
static int
477
server_newer_detached_session(struct session *s_loop, struct session *s_out)
478
0
{
479
0
  if (s_loop->attached)
480
0
    return (0);
481
0
  return (server_newer_session(s_loop, s_out));
482
0
}
483
484
void
485
server_destroy_session(struct session *s)
486
0
{
487
0
  struct client *c;
488
0
  struct session  *s_new = NULL, *cs_new = NULL, *use_s;
489
0
  struct sort_criteria   sort_crit = { .order = SORT_NAME };
490
0
  int    detach_on_destroy;
491
492
0
  detach_on_destroy = options_get_number(s->options, "detach-on-destroy");
493
0
  if (detach_on_destroy == 0)
494
0
    s_new = server_find_session(s, server_newer_session);
495
0
  else if (detach_on_destroy == 2)
496
0
    s_new = server_find_session(s, server_newer_detached_session);
497
0
  else if (detach_on_destroy == 3)
498
0
    s_new = session_previous_session(s, &sort_crit);
499
0
  else if (detach_on_destroy == 4)
500
0
    s_new = session_next_session(s, &sort_crit);
501
0
  if (s_new == s)
502
0
    s_new = NULL;
503
504
  /*
505
   * If no suitable new session was found above, then look for any
506
   * session as an alternative in case a client needs it.
507
   */
508
0
  if (s_new == NULL &&
509
0
      (detach_on_destroy == 1 || detach_on_destroy == 2))
510
0
    cs_new = server_find_session(s, server_newer_session);
511
512
0
  TAILQ_FOREACH(c, &clients, entry) {
513
0
    if (c->session != s)
514
0
      continue;
515
0
    use_s = s_new;
516
0
    if (use_s == NULL && (c->flags & CLIENT_NO_DETACH_ON_DESTROY))
517
0
      use_s = cs_new;
518
519
0
    c->session = NULL;
520
0
    c->last_session = NULL;
521
0
    server_client_set_session(c, use_s);
522
0
    if (use_s == NULL)
523
0
      c->flags |= CLIENT_EXIT;
524
0
  }
525
0
  recalculate_sizes();
526
0
}
527
528
void
529
server_check_unattached(void)
530
0
{
531
0
  struct session    *s;
532
0
  struct session_group  *sg;
533
534
  /*
535
   * If any sessions are no longer attached and have destroy-unattached
536
   * set, collect them.
537
   */
538
0
  RB_FOREACH(s, sessions, &sessions) {
539
0
    if (s->attached != 0)
540
0
      continue;
541
0
    switch (options_get_number(s->options, "destroy-unattached")) {
542
0
    case 0: /* off */
543
0
      continue;
544
0
    case 1: /* on */
545
0
      break;
546
0
    case 2: /* keep-last */
547
0
      sg = session_group_contains(s);
548
0
      if (sg == NULL || session_group_count(sg) <= 1)
549
0
        continue;
550
0
      break;
551
0
    case 3: /* keep-group */
552
0
      sg = session_group_contains(s);
553
0
      if (sg != NULL && session_group_count(sg) == 1)
554
0
        continue;
555
0
      break;
556
0
    }
557
0
    server_destroy_session(s);
558
0
    session_destroy(s, 1, __func__);
559
0
  }
560
0
}
561
562
void
563
server_unzoom_window(struct window *w)
564
0
{
565
0
  if (window_unzoom(w, 1) == 0)
566
0
    server_redraw_window(w);
567
0
}