Coverage Report

Created: 2026-01-17 06:24

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