Coverage Report

Created: 2026-07-16 06:53

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.148 2026/07/14 19:07:03 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
6.78k
{
147
6.78k
  struct client *c;
148
149
6.78k
  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
6.78k
}
156
157
void
158
server_status_window(struct window *w)
159
6.06k
{
160
6.06k
  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
6.06k
  RB_FOREACH(s, sessions, &sessions) {
169
0
    if (session_has(s, w))
170
0
      server_status_session(s);
171
0
  }
172
6.06k
}
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
    server_unzoom_window(w);
230
0
    server_client_remove_pane(wp);
231
0
    layout_close_pane(wp);
232
0
    window_remove_pane(w, wp);
233
0
    server_redraw_window(w);
234
0
  }
235
0
}
236
237
void
238
server_kill_window(struct window *w, int renumber)
239
0
{
240
0
  struct session  *s, *s1;
241
0
  struct winlink  *wl;
242
243
0
  window_add_ref(w, __func__);
244
0
  RB_FOREACH_SAFE(s, sessions, &sessions, s1) {
245
0
    if (!session_has(s, w))
246
0
      continue;
247
248
0
    server_unzoom_window(w);
249
0
    while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
250
0
      if (session_detach(s, wl)) {
251
0
        server_destroy_session_group(s);
252
0
        break;
253
0
      }
254
0
      server_redraw_session_group(s);
255
0
    }
256
257
0
    if (renumber)
258
0
      server_renumber_session(s);
259
0
  }
260
0
  recalculate_sizes();
261
0
  window_remove_ref(w, __func__);
262
0
}
263
264
void
265
server_renumber_session(struct session *s)
266
0
{
267
0
  struct session_group  *sg;
268
269
0
  if (options_get_number(s->options, "renumber-windows")) {
270
0
    if ((sg = session_group_contains(s)) != NULL) {
271
0
      TAILQ_FOREACH(s, &sg->sessions, gentry)
272
0
          session_renumber_windows(s);
273
0
    } else
274
0
      session_renumber_windows(s);
275
0
  }
276
0
}
277
278
void
279
server_renumber_all(void)
280
0
{
281
0
  struct session  *s;
282
283
0
  RB_FOREACH(s, sessions, &sessions)
284
0
    server_renumber_session(s);
285
0
}
286
287
int
288
server_link_window(struct session *src, struct winlink *srcwl,
289
    struct session *dst, int dstidx, int killflag, int selectflag,
290
    char **cause)
291
0
{
292
0
  struct winlink    *dstwl;
293
0
  struct session_group  *srcsg, *dstsg;
294
295
0
  srcsg = session_group_contains(src);
296
0
  dstsg = session_group_contains(dst);
297
0
  if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
298
0
    xasprintf(cause, "sessions are grouped");
299
0
    return (-1);
300
0
  }
301
302
0
  dstwl = NULL;
303
0
  if (dstidx != -1)
304
0
    dstwl = winlink_find_by_index(&dst->windows, dstidx);
305
0
  if (dstwl != NULL) {
306
0
    if (dstwl->window == srcwl->window) {
307
0
      xasprintf(cause, "same index: %d", dstidx);
308
0
      return (-1);
309
0
    }
310
0
    if (killflag) {
311
      /*
312
       * Can't use session_detach as it will destroy session
313
       * if this makes it empty.
314
       */
315
0
      events_fire_winlink("window-unlinked", dstwl);
316
0
      dstwl->flags &= ~WINLINK_ALERTFLAGS;
317
0
      winlink_stack_remove(&dst->lastw, dstwl);
318
0
      winlink_remove(&dst->windows, dstwl);
319
320
      /* Force select/redraw if current. */
321
0
      if (dstwl == dst->curw) {
322
0
        selectflag = 1;
323
0
        dst->curw = NULL;
324
0
      }
325
0
    }
326
0
  }
327
328
0
  if (dstidx == -1)
329
0
    dstidx = -1 - options_get_number(dst->options, "base-index");
330
0
  dstwl = session_attach(dst, srcwl->window, dstidx, cause);
331
0
  if (dstwl == NULL)
332
0
    return (-1);
333
334
0
  if (marked_pane.wl == srcwl)
335
0
    marked_pane.wl = dstwl;
336
0
  if (selectflag)
337
0
    session_select(dst, dstwl->idx);
338
0
  server_redraw_session_group(dst);
339
340
0
  return (0);
341
0
}
342
343
void
344
server_unlink_window(struct session *s, struct winlink *wl)
345
0
{
346
0
  if (session_detach(s, wl))
347
0
    server_destroy_session_group(s);
348
0
  else
349
0
    server_redraw_session_group(s);
350
0
}
351
352
void
353
server_destroy_pane(struct window_pane *wp, int notify)
354
0
{
355
0
  struct window   *w = wp->window;
356
0
  struct screen_write_ctx  ctx;
357
0
  struct grid_cell   gc;
358
0
  int      remain_on_exit;
359
0
  const char    *s;
360
0
  char      *expanded;
361
0
  u_int      sx = screen_size_x(&wp->base);
362
0
  u_int      sy = screen_size_y(&wp->base);
363
364
0
  if (wp->fd != -1) {
365
#ifdef HAVE_UTEMPTER
366
    utempter_remove_record(wp->fd);
367
    kill(getpid(), SIGCHLD);
368
#endif
369
0
    bufferevent_free(wp->event);
370
0
    wp->event = NULL;
371
0
    close(wp->fd);
372
0
    wp->fd = -1;
373
0
  }
374
375
0
  remain_on_exit = options_get_number(wp->options, "remain-on-exit");
376
0
  if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
377
0
    return;
378
0
  switch (remain_on_exit) {
379
0
  case 0:
380
0
    break;
381
0
  case 2:
382
0
    if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
383
0
      break;
384
    /* FALLTHROUGH */
385
0
  case 1:
386
0
  case 3:
387
0
    if (wp->flags & PANE_STATUSDRAWN)
388
0
      return;
389
0
    wp->flags |= PANE_STATUSDRAWN;
390
391
0
    gettimeofday(&wp->dead_time, NULL);
392
0
    if (notify)
393
0
      server_fire_pane_exit("pane-died", wp);
394
395
0
    s = options_get_string(wp->options, "remain-on-exit-format");
396
0
    if (*s != '\0') {
397
0
      screen_write_start_pane(&ctx, wp, &wp->base);
398
0
      screen_write_scrollregion(&ctx, 0, sy - 1);
399
0
      screen_write_cursormove(&ctx, 0, sy - 1, 0);
400
0
      screen_write_linefeed(&ctx, 1, 8);
401
0
      memcpy(&gc, &grid_default_cell, sizeof gc);
402
403
0
      expanded = format_single(NULL, s, NULL, NULL, NULL, wp);
404
0
      format_draw(&ctx, &gc, sx, expanded, NULL, 0);
405
0
      free(expanded);
406
407
0
      screen_write_stop(&ctx);
408
0
    }
409
0
    wp->base.mode &= ~MODE_CURSOR;
410
411
0
    wp->flags |= PANE_REDRAW;
412
0
    return;
413
0
  }
414
415
0
  if (notify)
416
0
    server_fire_pane_exit("pane-exited", wp);
417
418
0
  server_unzoom_window(w);
419
0
  server_client_remove_pane(wp);
420
0
  layout_close_pane(wp);
421
0
  window_remove_pane(w, wp);
422
423
0
  if (TAILQ_EMPTY(&w->panes))
424
0
    server_kill_window(w, 1);
425
0
  else
426
0
    server_redraw_window(w);
427
0
}
428
429
static void
430
server_destroy_session_group(struct session *s)
431
0
{
432
0
  struct session_group  *sg;
433
0
  struct session    *s1;
434
435
0
  if ((sg = session_group_contains(s)) == NULL) {
436
0
    server_destroy_session(s);
437
0
    session_destroy(s, 1, __func__);
438
0
  } else {
439
0
    TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
440
0
      server_destroy_session(s);
441
0
      session_destroy(s, 1, __func__);
442
0
    }
443
0
  }
444
0
}
445
446
static struct session *
447
server_find_session(struct session *s,
448
    int (*f)(struct session *, struct session *))
449
0
{
450
0
  struct session *s_loop, *s_out = NULL;
451
452
0
  RB_FOREACH(s_loop, sessions, &sessions) {
453
0
    if (s_loop != s && f(s_loop, s_out))
454
0
      s_out = s_loop;
455
0
  }
456
0
  return (s_out);
457
0
}
458
459
static int
460
server_newer_session(struct session *s_loop, struct session *s_out)
461
0
{
462
0
  if (s_out == NULL)
463
0
    return (1);
464
0
  return (timercmp(&s_loop->activity_time, &s_out->activity_time, >));
465
0
}
466
467
static int
468
server_newer_detached_session(struct session *s_loop, struct session *s_out)
469
0
{
470
0
  if (s_loop->attached)
471
0
    return (0);
472
0
  return (server_newer_session(s_loop, s_out));
473
0
}
474
475
void
476
server_destroy_session(struct session *s)
477
0
{
478
0
  struct client *c;
479
0
  struct session  *s_new = NULL, *cs_new = NULL, *use_s;
480
0
  struct sort_criteria   sort_crit = { .order = SORT_NAME };
481
0
  int    detach_on_destroy;
482
483
0
  detach_on_destroy = options_get_number(s->options, "detach-on-destroy");
484
0
  if (detach_on_destroy == 0)
485
0
    s_new = server_find_session(s, server_newer_session);
486
0
  else if (detach_on_destroy == 2)
487
0
    s_new = server_find_session(s, server_newer_detached_session);
488
0
  else if (detach_on_destroy == 3)
489
0
    s_new = session_previous_session(s, &sort_crit);
490
0
  else if (detach_on_destroy == 4)
491
0
    s_new = session_next_session(s, &sort_crit);
492
0
  if (s_new == s)
493
0
    s_new = NULL;
494
495
  /*
496
   * If no suitable new session was found above, then look for any
497
   * session as an alternative in case a client needs it.
498
   */
499
0
  if (s_new == NULL &&
500
0
      (detach_on_destroy == 1 || detach_on_destroy == 2))
501
0
    cs_new = server_find_session(s, server_newer_session);
502
503
0
  TAILQ_FOREACH(c, &clients, entry) {
504
0
    if (c->session != s)
505
0
      continue;
506
0
    use_s = s_new;
507
0
    if (use_s == NULL && (c->flags & CLIENT_NO_DETACH_ON_DESTROY))
508
0
      use_s = cs_new;
509
510
0
    c->session = NULL;
511
0
    c->last_session = NULL;
512
0
    server_client_set_session(c, use_s);
513
0
    if (use_s == NULL)
514
0
      c->flags |= CLIENT_EXIT;
515
0
  }
516
0
  recalculate_sizes();
517
0
}
518
519
void
520
server_check_unattached(void)
521
0
{
522
0
  struct session    *s;
523
0
  struct session_group  *sg;
524
525
  /*
526
   * If any sessions are no longer attached and have destroy-unattached
527
   * set, collect them.
528
   */
529
0
  RB_FOREACH(s, sessions, &sessions) {
530
0
    if (s->attached != 0)
531
0
      continue;
532
0
    switch (options_get_number(s->options, "destroy-unattached")) {
533
0
    case 0: /* off */
534
0
      continue;
535
0
    case 1: /* on */
536
0
      break;
537
0
    case 2: /* keep-last */
538
0
      sg = session_group_contains(s);
539
0
      if (sg == NULL || session_group_count(sg) <= 1)
540
0
        continue;
541
0
      break;
542
0
    case 3: /* keep-group */
543
0
      sg = session_group_contains(s);
544
0
      if (sg != NULL && session_group_count(sg) == 1)
545
0
        continue;
546
0
      break;
547
0
    }
548
0
    session_destroy(s, 1, __func__);
549
0
  }
550
0
}
551
552
void
553
server_unzoom_window(struct window *w)
554
0
{
555
0
  if (window_unzoom(w, 1) == 0)
556
0
    server_redraw_window(w);
557
0
}