Coverage Report

Created: 2024-07-27 06:19

/src/tmux/server-fn.c
Line
Count
Source (jump to first uncovered line)
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
6.01k
{
106
6.01k
  struct client *c;
107
108
6.01k
  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
6.01k
}
113
114
void
115
server_status_window(struct window *w)
116
6.01k
{
117
6.01k
  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
6.01k
  RB_FOREACH(s, sessions, &sessions) {
126
0
    if (session_has(s, w))
127
0
      server_status_session(s);
128
0
  }
129
6.01k
}
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 (selectflag)
291
0
    session_select(dst, dstwl->idx);
292
0
  server_redraw_session_group(dst);
293
294
0
  return (0);
295
0
}
296
297
void
298
server_unlink_window(struct session *s, struct winlink *wl)
299
0
{
300
0
  if (session_detach(s, wl))
301
0
    server_destroy_session_group(s);
302
0
  else
303
0
    server_redraw_session_group(s);
304
0
}
305
306
void
307
server_destroy_pane(struct window_pane *wp, int notify)
308
0
{
309
0
  struct window   *w = wp->window;
310
0
  struct screen_write_ctx  ctx;
311
0
  struct grid_cell   gc;
312
0
  int      remain_on_exit;
313
0
  const char    *s;
314
0
  char      *expanded;
315
0
  u_int      sx = screen_size_x(&wp->base);
316
0
  u_int      sy = screen_size_y(&wp->base);
317
318
0
  if (wp->fd != -1) {
319
#ifdef HAVE_UTEMPTER
320
    utempter_remove_record(wp->fd);
321
#endif
322
0
    bufferevent_free(wp->event);
323
0
    wp->event = NULL;
324
0
    close(wp->fd);
325
0
    wp->fd = -1;
326
0
  }
327
328
0
  remain_on_exit = options_get_number(wp->options, "remain-on-exit");
329
0
  if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
330
0
    return;
331
0
  switch (remain_on_exit) {
332
0
  case 0:
333
0
    break;
334
0
  case 2:
335
0
    if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
336
0
      break;
337
    /* FALLTHROUGH */
338
0
  case 1:
339
0
    if (wp->flags & PANE_STATUSDRAWN)
340
0
      return;
341
0
    wp->flags |= PANE_STATUSDRAWN;
342
343
0
    gettimeofday(&wp->dead_time, NULL);
344
0
    if (notify)
345
0
      notify_pane("pane-died", wp);
346
347
0
    s = options_get_string(wp->options, "remain-on-exit-format");
348
0
    if (*s != '\0') {
349
0
      screen_write_start_pane(&ctx, wp, &wp->base);
350
0
      screen_write_scrollregion(&ctx, 0, sy - 1);
351
0
      screen_write_cursormove(&ctx, 0, sy - 1, 0);
352
0
      screen_write_linefeed(&ctx, 1, 8);
353
0
      memcpy(&gc, &grid_default_cell, sizeof gc);
354
355
0
      expanded = format_single(NULL, s, NULL, NULL, NULL, wp);
356
0
      format_draw(&ctx, &gc, sx, expanded, NULL, 0);
357
0
      free(expanded);
358
359
0
      screen_write_stop(&ctx);
360
0
    }
361
0
    wp->base.mode &= ~MODE_CURSOR;
362
363
0
    wp->flags |= PANE_REDRAW;
364
0
    return;
365
0
  }
366
367
0
  if (notify)
368
0
    notify_pane("pane-exited", wp);
369
370
0
  server_unzoom_window(w);
371
0
  server_client_remove_pane(wp);
372
0
  layout_close_pane(wp);
373
0
  window_remove_pane(w, wp);
374
375
0
  if (TAILQ_EMPTY(&w->panes))
376
0
    server_kill_window(w, 1);
377
0
  else
378
0
    server_redraw_window(w);
379
0
}
380
381
static void
382
server_destroy_session_group(struct session *s)
383
0
{
384
0
  struct session_group  *sg;
385
0
  struct session    *s1;
386
387
0
  if ((sg = session_group_contains(s)) == NULL) {
388
0
    server_destroy_session(s);
389
0
    session_destroy(s, 1, __func__);
390
0
  } else {
391
0
    TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
392
0
      server_destroy_session(s);
393
0
      session_destroy(s, 1, __func__);
394
0
    }
395
0
  }
396
0
}
397
398
static struct session *
399
server_find_session(struct session *s,
400
    int (*f)(struct session *, struct session *))
401
0
{
402
0
  struct session *s_loop, *s_out = NULL;
403
404
0
  RB_FOREACH(s_loop, sessions, &sessions) {
405
0
    if (s_loop != s && (s_out == NULL || f(s_loop, s_out)))
406
0
      s_out = s_loop;
407
0
  }
408
0
  return (s_out);
409
0
}
410
411
static int
412
server_newer_session(struct session *s_loop, struct session *s_out)
413
0
{
414
0
  return (timercmp(&s_loop->activity_time, &s_out->activity_time, >));
415
0
}
416
417
static int
418
server_newer_detached_session(struct session *s_loop, struct session *s_out)
419
0
{
420
0
  if (s_loop->attached)
421
0
    return (0);
422
0
  return (server_newer_session(s_loop, s_out));
423
0
}
424
425
void
426
server_destroy_session(struct session *s)
427
0
{
428
0
  struct client *c;
429
0
  struct session  *s_new = NULL;
430
0
  int    detach_on_destroy;
431
432
0
  detach_on_destroy = options_get_number(s->options, "detach-on-destroy");
433
0
  if (detach_on_destroy == 0)
434
0
    s_new = server_find_session(s, server_newer_session);
435
0
  else if (detach_on_destroy == 2)
436
0
    s_new = server_find_session(s, server_newer_detached_session);
437
0
  else if (detach_on_destroy == 3)
438
0
    s_new = session_previous_session(s);
439
0
  else if (detach_on_destroy == 4)
440
0
    s_new = session_next_session(s);
441
0
  if (s_new == s)
442
0
    s_new = NULL;
443
0
  TAILQ_FOREACH(c, &clients, entry) {
444
0
    if (c->session != s)
445
0
      continue;
446
0
    c->session = NULL;
447
0
    c->last_session = NULL;
448
0
    server_client_set_session(c, s_new);
449
0
    if (s_new == NULL)
450
0
      c->flags |= CLIENT_EXIT;
451
0
  }
452
0
  recalculate_sizes();
453
0
}
454
455
void
456
server_check_unattached(void)
457
0
{
458
0
  struct session    *s;
459
0
  struct session_group  *sg;
460
461
  /*
462
   * If any sessions are no longer attached and have destroy-unattached
463
   * set, collect them.
464
   */
465
0
  RB_FOREACH(s, sessions, &sessions) {
466
0
    if (s->attached != 0)
467
0
      continue;
468
0
    switch (options_get_number(s->options, "destroy-unattached")) {
469
0
    case 0: /* off */
470
0
      continue;
471
0
    case 1: /* on */
472
0
      break;
473
0
    case 2: /* keep-last */
474
0
      sg = session_group_contains(s);
475
0
      if (sg == NULL || session_group_count(sg) <= 1)
476
0
        continue;
477
0
      break;
478
0
    case 3: /* keep-group */
479
0
      sg = session_group_contains(s);
480
0
      if (sg != NULL && session_group_count(sg) == 1)
481
0
        continue;
482
0
      break;
483
0
    }
484
0
    session_destroy(s, 1, __func__);
485
0
  }
486
0
}
487
488
void
489
server_unzoom_window(struct window *w)
490
0
{
491
0
  if (window_unzoom(w, 1) == 0)
492
0
    server_redraw_window(w);
493
0
}