Coverage Report

Created: 2025-11-09 07:05

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