Coverage Report

Created: 2026-08-31 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/spawn.c
Line
Count
Source
1
/* $OpenBSD: spawn.c,v 1.52 2026/08/20 09:19:24 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2019 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
22
#include <errno.h>
23
#include <signal.h>
24
#include <stdio.h>
25
#include <stdlib.h>
26
#include <string.h>
27
#include <unistd.h>
28
29
#include "tmux.h"
30
31
/*
32
 * Set up the environment and create a new window and pane or a new pane.
33
 *
34
 * We need to set up the following items:
35
 *
36
 * - history limit, comes from the session;
37
 *
38
 * - base index, comes from the session;
39
 *
40
 * - current working directory, may be specified - if it isn't it comes from
41
 *   either the client or the session;
42
 *
43
 * - PATH variable, comes from the client if any, otherwise from the session
44
 *   environment;
45
 *
46
 * - shell, comes from default-shell;
47
 *
48
 * - termios, comes from the session;
49
 *
50
 * - remaining environment, comes from the session.
51
 */
52
53
static void
54
spawn_log(const char *from, struct spawn_context *sc)
55
0
{
56
0
  struct session    *s = sc->s;
57
0
  struct winlink    *wl = sc->wl;
58
0
  struct window_pane  *wp0 = sc->wp0;
59
0
  const char    *name = (sc->name == NULL ? "none" : sc->name);
60
0
  char       tmp[128];
61
62
0
  log_debug("%s: name=%s, flags=%#x", from, name, sc->flags);
63
0
  if (wl != NULL && wp0 != NULL)
64
0
    xsnprintf(tmp, sizeof tmp, "wl=%d wp0=%%%u", wl->idx, wp0->id);
65
0
  else if (wl != NULL)
66
0
    xsnprintf(tmp, sizeof tmp, "wl=%d wp0=none", wl->idx);
67
0
  else if (wp0 != NULL)
68
0
    xsnprintf(tmp, sizeof tmp, "wl=none wp0=%%%u", wp0->id);
69
0
  else
70
0
    xsnprintf(tmp, sizeof tmp, "wl=none wp0=none");
71
0
  log_debug("%s: s=$%u %s idx=%d", from, s->id, tmp, sc->idx);
72
0
}
73
74
static void
75
spawn_fire_pane_created(struct spawn_context *sc, struct window_pane *wp)
76
0
{
77
0
  struct event_payload  *ep;
78
0
  struct cmd_find_state  fs;
79
0
  char      *cmd = NULL;
80
0
  const char    *cwd = wp->cwd;
81
82
0
  ep = event_payload_create();
83
0
  cmd_find_from_winlink_pane(&fs, sc->wl, wp, 0);
84
0
  event_payload_set_target(ep, &fs);
85
0
  event_payload_set_session(ep, "session", sc->s);
86
0
  event_payload_set_window(ep, "window", wp->window);
87
0
  event_payload_set_int(ep, "window_index", sc->wl->idx);
88
0
  event_payload_set_pane(ep, "pane", wp);
89
90
0
  if (wp->argc != 0)
91
0
    cmd = cmd_stringify_argv(wp->argc, wp->argv);
92
0
  if (cmd != NULL && *cmd != '\0')
93
0
    event_payload_set_string(ep, "pane_command", "%s", cmd);
94
0
  else if (wp->shell != NULL)
95
0
    event_payload_set_string(ep, "pane_command", "%s", wp->shell);
96
0
  free(cmd);
97
98
0
  if (cwd != NULL)
99
0
    event_payload_set_string(ep, "pane_current_path", "%s", cwd);
100
101
0
  if (sc->flags & SPAWN_EMPTY)
102
0
    event_payload_set_int(ep, "created_empty", 1);
103
0
  else
104
0
    event_payload_set_int(ep, "created_empty", 0);
105
0
  if (sc->flags & SPAWN_RESPAWN)
106
0
    event_payload_set_int(ep, "created_respawn", 1);
107
0
  else
108
0
    event_payload_set_int(ep, "created_respawn", 0);
109
110
0
  events_fire("pane-created", ep);
111
0
}
112
113
struct winlink *
114
spawn_window(struct spawn_context *sc, char **cause)
115
0
{
116
0
  struct session    *s = sc->s;
117
0
  struct window   *w;
118
0
  struct window_pane  *wp;
119
0
  struct winlink    *wl;
120
0
  int      idx = sc->idx;
121
0
  u_int      sx, sy, xpixel, ypixel;
122
123
0
  spawn_log(__func__, sc);
124
125
  /*
126
   * If the window already exists, we are respawning, so destroy all the
127
   * panes except one.
128
   */
129
0
  if (sc->flags & SPAWN_RESPAWN) {
130
0
    w = sc->wl->window;
131
0
    if (~sc->flags & SPAWN_KILL) {
132
0
      TAILQ_FOREACH(wp, &w->panes, entry) {
133
0
        if (wp->fd != -1)
134
0
          break;
135
0
      }
136
0
      if (wp != NULL) {
137
0
        xasprintf(cause, "window %s:%d still active",
138
0
            s->name, sc->wl->idx);
139
0
        return (NULL);
140
0
      }
141
0
    }
142
143
0
    sc->wp0 = TAILQ_FIRST(&w->panes);
144
0
    TAILQ_REMOVE(&w->panes, sc->wp0, entry);
145
146
0
    layout_free(w, 0);
147
0
    window_destroy_panes(w);
148
149
0
    TAILQ_INSERT_HEAD(&w->panes, sc->wp0, entry);
150
0
    window_pane_resize(sc->wp0, w->sx, w->sy);
151
152
0
    layout_init(w, sc->wp0);
153
0
    w->active = NULL;
154
0
    window_set_active_pane(w, sc->wp0, 0);
155
0
  }
156
157
  /*
158
   * Otherwise we have no window so we will need to create one. First
159
   * check if the given index already exists and destroy it if so.
160
   */
161
0
  if ((~sc->flags & SPAWN_RESPAWN) && idx != -1) {
162
0
    wl = winlink_find_by_index(&s->windows, idx);
163
0
    if (wl != NULL && (~sc->flags & SPAWN_KILL)) {
164
0
      xasprintf(cause, "index %d in use", idx);
165
0
      return (NULL);
166
0
    }
167
0
    if (wl != NULL) {
168
      /*
169
       * Can't use session_detach as it will destroy session
170
       * if this makes it empty.
171
       */
172
0
      wl->flags &= ~WINLINK_ALERTFLAGS;
173
0
      events_fire_winlink("window-unlinked", wl);
174
0
      winlink_stack_remove(&s->lastw, wl);
175
0
      winlink_remove(&s->windows, wl);
176
177
0
      if (s->curw == wl) {
178
0
        s->curw = NULL;
179
0
        sc->flags &= ~SPAWN_DETACHED;
180
0
      }
181
0
    }
182
0
  }
183
184
  /* Then create a window if needed. */
185
0
  if (~sc->flags & SPAWN_RESPAWN) {
186
0
    if (idx == -1)
187
0
      idx = -1 - options_get_number(s->options, "base-index");
188
0
    if ((sc->wl = winlink_add(&s->windows, idx)) == NULL) {
189
0
      xasprintf(cause, "couldn't add window %d", idx);
190
0
      return (NULL);
191
0
    }
192
0
    default_window_size(sc->tc, s, NULL, &sx, &sy, &xpixel, &ypixel,
193
0
        -1);
194
0
    if ((w = window_create(sx, sy, xpixel, ypixel)) == NULL) {
195
0
      winlink_remove(&s->windows, sc->wl);
196
0
      xasprintf(cause, "couldn't create window %d", idx);
197
0
      return (NULL);
198
0
    }
199
0
    if (s->curw == NULL)
200
0
      s->curw = sc->wl;
201
0
    sc->wl->session = s;
202
0
    w->latest = sc->tc;
203
0
    winlink_set_window(sc->wl, w);
204
0
  } else
205
0
    w = NULL;
206
0
  sc->flags |= SPAWN_NONOTIFY;
207
208
  /* Spawn the pane. */
209
0
  wp = spawn_pane(sc, cause);
210
0
  if (wp == NULL) {
211
0
    if (~sc->flags & SPAWN_RESPAWN)
212
0
      winlink_remove(&s->windows, sc->wl);
213
0
    return (NULL);
214
0
  }
215
216
  /* Set the name of the new window. */
217
0
  if (~sc->flags & SPAWN_RESPAWN) {
218
0
    free(w->name);
219
0
    if (sc->name == NULL)
220
0
      w->name = default_window_name(w);
221
0
    else {
222
0
      w->name = xstrdup(sc->name);
223
0
      options_set_number(w->options, "automatic-rename", 0);
224
0
    }
225
0
    window_set_fill_cells(w);
226
0
  }
227
228
  /* Switch to the new window if required. */
229
0
  if (~sc->flags & SPAWN_DETACHED)
230
0
    session_select(s, sc->wl->idx);
231
232
  /* Fire notification if new window. */
233
0
  if (~sc->flags & SPAWN_RESPAWN) {
234
0
    events_fire_window("window-created", w);
235
0
    events_fire_winlink("window-linked", sc->wl);
236
0
  }
237
238
0
  session_group_synchronize_from(s);
239
0
  return (sc->wl);
240
0
}
241
242
struct window_pane *
243
spawn_pane(struct spawn_context *sc, char **cause)
244
0
{
245
0
  struct cmdq_item   *item = sc->item;
246
0
  struct client    *c, *loop;
247
0
  struct session     *s = sc->s;
248
0
  struct session     *ts;
249
0
  struct window    *w = sc->wl->window;
250
0
  struct window_pane   *new_wp;
251
0
  struct environ     *child;
252
0
  struct environ_entry   *ee;
253
0
  char      **argv, *cp, **argvp, *argv0, *cwd, *new_cwd;
254
0
  char        path[PATH_MAX];
255
0
  const char     *cmd, *tmp, *home = find_home();
256
0
  const char     *actual_cwd = NULL;
257
0
  int       argc;
258
0
  u_int       idx;
259
0
  struct termios      now;
260
0
  u_int       hlimit;
261
0
  struct winsize      ws;
262
0
  sigset_t      set, oldset;
263
0
  key_code      key;
264
265
0
  if (item != NULL) {
266
0
    ts = cmdq_get_target(item)->s;
267
0
    c = cmdq_get_client(item);
268
0
  } else {
269
0
    ts = s;
270
0
    c = sc->tc;
271
0
  }
272
273
0
  spawn_log(__func__, sc);
274
275
0
  if (sc->flags & SPAWN_MODAL) {
276
0
    if (~sc->flags & SPAWN_FLOATING) {
277
0
      xasprintf(cause, "modal pane must be floating");
278
0
      return (NULL);
279
0
    }
280
0
    if (w->modal != NULL) {
281
0
      xasprintf(cause, "window already has a modal pane");
282
0
      return (NULL);
283
0
    }
284
0
  }
285
286
  /*
287
   * Work out the current working directory. If respawning, use
288
   * the pane's stored one unless specified.
289
   */
290
0
  if (sc->cwd != NULL) {
291
0
    if (item != NULL)
292
0
      cwd = format_single(item, sc->cwd, c, ts, NULL, NULL);
293
0
    else
294
0
      cwd = xstrdup(sc->cwd);
295
0
    if (*cwd != '/') {
296
0
      xasprintf(&new_cwd, "%s%s%s",
297
0
          server_client_get_cwd(c, ts),
298
0
          *cwd != '\0' ? "/" : "", cwd);
299
0
      free(cwd);
300
0
      cwd = new_cwd;
301
0
    }
302
0
  } else if (~sc->flags & SPAWN_RESPAWN)
303
0
    cwd = xstrdup(server_client_get_cwd(c, ts));
304
0
  else
305
0
    cwd = NULL;
306
307
  /*
308
   * If we are respawning then get rid of the old process. Otherwise
309
   * either create a new cell or assign to the one we are given.
310
   */
311
0
  hlimit = options_get_number(s->options, "history-limit");
312
0
  if (sc->flags & SPAWN_RESPAWN) {
313
0
    if (sc->wp0->fd != -1 && (~sc->flags & SPAWN_KILL)) {
314
0
      window_pane_index(sc->wp0, &idx);
315
0
      xasprintf(cause, "pane %s:%d.%u still active",
316
0
          s->name, sc->wl->idx, idx);
317
0
      free(cwd);
318
0
      return (NULL);
319
0
    }
320
0
    if (sc->wp0->event != NULL) {
321
0
      bufferevent_free(sc->wp0->event);
322
0
      sc->wp0->event = NULL;
323
0
    }
324
0
    if (sc->wp0->fd != -1) {
325
0
      close(sc->wp0->fd);
326
0
      sc->wp0->fd = -1;
327
0
    }
328
0
    window_pane_reset_mode_all(sc->wp0);
329
0
    screen_reinit(&sc->wp0->base, 0);
330
0
    if (sc->wp0->ictx != NULL) {
331
0
      input_free(sc->wp0->ictx);
332
0
      sc->wp0->ictx = NULL;
333
0
    }
334
335
    /*
336
     * The old buffer is gone and the new one starts empty, so
337
     * offsets into the old buffer no longer mean anything. Reset
338
     * them, and drop output control clients still had queued.
339
     */
340
0
    sc->wp0->offset.used = 0;
341
0
    sc->wp0->base_offset = 0;
342
0
    sc->wp0->pipe_offset.used = 0;
343
0
    TAILQ_FOREACH(loop, &clients, entry) {
344
0
      if (loop->flags & CLIENT_CONTROL)
345
0
        control_reset_pane(loop, sc->wp0);
346
0
    }
347
348
0
    new_wp = sc->wp0;
349
0
    new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
350
0
  } else {
351
0
    if (sc->lc == NULL) {
352
0
      new_wp = window_add_pane(w, NULL, hlimit, sc->flags);
353
0
      layout_init(w, new_wp);
354
0
    } else {
355
0
      new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags);
356
0
      if (sc->flags & SPAWN_ZOOM)
357
0
        layout_assign_pane(sc->lc, new_wp, 1);
358
0
      else
359
0
        layout_assign_pane(sc->lc, new_wp, 0);
360
0
    }
361
0
    if (sc->flags & SPAWN_FLOATING)
362
0
      new_wp->layout_cell->flags |= LAYOUT_CELL_FLOATING;
363
0
    if (sc->flags & SPAWN_FLOATOVERZOOM)
364
0
      new_wp->flags |= PANE_FLOATOVERZOOM;
365
366
    /*
367
     * If window currently zoomed, window_set_active_pane calls
368
     * window_unzoom which it copies back the saved_layout_cell.
369
     */
370
0
    if (w->flags & WINDOW_ZOOMED)
371
0
      new_wp->saved_layout_cell = new_wp->layout_cell;
372
0
  }
373
374
  /*
375
   * Now we have a pane with nothing running in it ready for the new
376
   * process. Work out the command and arguments and store the working
377
   * directory.
378
   */
379
0
  if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) {
380
0
    cmd = options_get_string(s->options, "default-command");
381
0
    if (cmd != NULL && *cmd != '\0') {
382
0
      argc = 1;
383
0
      argv = (char **)&cmd;
384
0
    } else {
385
0
      argc = 0;
386
0
      argv = NULL;
387
0
    }
388
0
  } else {
389
0
    argc = sc->argc;
390
0
    argv = sc->argv;
391
0
  }
392
0
  if (cwd != NULL) {
393
0
    free(new_wp->cwd);
394
0
    new_wp->cwd = cwd;
395
0
  }
396
397
  /*
398
   * Replace the stored arguments if there are new ones. If not, the
399
   * existing ones will be used (they will only exist for respawn).
400
   */
401
0
  if (argc > 0) {
402
0
    cmd_free_argv(new_wp->argc, new_wp->argv);
403
0
    new_wp->argc = argc;
404
0
    new_wp->argv = cmd_copy_argv(argc, argv);
405
0
  }
406
407
  /* Create an environment for this pane. */
408
0
  child = environ_for_session(s, 0);
409
0
  if (sc->environ != NULL)
410
0
    environ_copy(sc->environ, child);
411
0
  environ_set(child, "TMUX_PANE", 0, "%%%u", new_wp->id);
412
413
  /*
414
   * Then the PATH environment variable. The session one is replaced from
415
   * the client if there is one because otherwise running "tmux new
416
   * myprogram" wouldn't work if myprogram isn't in the session's path.
417
   */
418
0
  if (c != NULL && c->session == NULL) { /* only unattached clients */
419
0
    ee = environ_find(c->environ, "PATH");
420
0
    if (ee != NULL)
421
0
      environ_set(child, "PATH", 0, "%s", ee->value);
422
0
  }
423
0
  if (environ_find(child, "PATH") == NULL)
424
0
    environ_set(child, "PATH", 0, "%s", _PATH_DEFPATH);
425
426
  /* Then the shell. If respawning, use the old one. */
427
0
  if (~sc->flags & SPAWN_RESPAWN) {
428
0
    tmp = options_get_string(s->options, "default-shell");
429
0
    if (!checkshell(tmp))
430
0
      tmp = _PATH_BSHELL;
431
0
    free(new_wp->shell);
432
0
    new_wp->shell = xstrdup(tmp);
433
0
  }
434
0
  environ_set(child, "SHELL", 0, "%s", new_wp->shell);
435
436
  /* Log the arguments we are going to use. */
437
0
  log_debug("%s: shell=%s", __func__, new_wp->shell);
438
0
  if (new_wp->argc != 0) {
439
0
    cp = cmd_stringify_argv(new_wp->argc, new_wp->argv);
440
0
    log_debug("%s: cmd=%s", __func__, cp);
441
0
    free(cp);
442
0
  }
443
0
  log_debug("%s: cwd=%s", __func__, new_wp->cwd);
444
0
  cmd_log_argv(new_wp->argc, new_wp->argv, "%s", __func__);
445
0
  environ_log(child, "%s: environment ", __func__);
446
447
  /* Initialize the window size. */
448
0
  memset(&ws, 0, sizeof ws);
449
0
  ws.ws_col = screen_size_x(&new_wp->base);
450
0
  ws.ws_row = screen_size_y(&new_wp->base);
451
0
  ws.ws_xpixel = w->xpixel * ws.ws_col;
452
0
  ws.ws_ypixel = w->ypixel * ws.ws_row;
453
454
  /* Block signals until fork has completed. */
455
0
  sigfillset(&set);
456
0
  sigprocmask(SIG_BLOCK, &set, &oldset);
457
458
  /* If the command is empty, don't fork a child process. */
459
0
  if (sc->flags & SPAWN_EMPTY) {
460
0
    new_wp->flags |= PANE_EMPTY;
461
0
    new_wp->base.mode &= ~MODE_CURSOR;
462
0
    new_wp->base.mode |= MODE_CRLF;
463
0
    goto complete;
464
0
  }
465
0
  new_wp->flags &= ~PANE_EMPTY;
466
467
  /* Store current working directory and change to new one. */
468
0
  if (getcwd(path, sizeof path) != NULL) {
469
0
    if (chdir(new_wp->cwd) == 0)
470
0
      actual_cwd = new_wp->cwd;
471
0
    else if (home != NULL && chdir(home) == 0)
472
0
      actual_cwd = home;
473
0
    else if (chdir("/") == 0)
474
0
      actual_cwd = "/";
475
0
  }
476
477
  /* Fork the new process. */
478
0
  new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws);
479
0
  if (new_wp->pid == -1) {
480
0
    xasprintf(cause, "fork failed: %s", strerror(errno));
481
0
    new_wp->fd = -1;
482
0
    if (~sc->flags & SPAWN_RESPAWN) {
483
0
      server_client_remove_pane(new_wp);
484
0
      layout_close_pane(new_wp);
485
0
      window_remove_pane(w, new_wp);
486
0
    }
487
0
    sigprocmask(SIG_SETMASK, &oldset, NULL);
488
0
    environ_free(child);
489
0
    return (NULL);
490
0
  }
491
492
  /*
493
   * In the parent process, everything is done now. Change the working
494
   * directory back.
495
   */
496
0
  if (new_wp->pid != 0) {
497
0
    if (actual_cwd != NULL &&
498
0
        chdir(path) != 0 &&
499
0
        (home == NULL || chdir(home) != 0))
500
0
      chdir("/");
501
0
    goto complete;
502
0
  }
503
504
#if defined(HAVE_SYSTEMD) && defined(ENABLE_CGROUPS)
505
  /*
506
   * Move the child process into a new cgroup for systemd-oomd isolation.
507
   */
508
  if (systemd_move_to_new_cgroup(cause) < 0) {
509
    log_debug("%s: moving pane to new cgroup failed: %s",
510
        __func__, *cause);
511
    free (*cause);
512
  }
513
#endif
514
  /*
515
   * Child process. Set PWD to the working directory.
516
   */
517
0
  if (actual_cwd != NULL)
518
0
    environ_set(child, "PWD", 0, "%s", actual_cwd);
519
520
  /*
521
   * Update terminal escape characters from the session if available and
522
   * force VERASE to tmux's backspace.
523
   */
524
0
  if (tcgetattr(STDIN_FILENO, &now) != 0)
525
0
    _exit(1);
526
0
  if (s->tio != NULL)
527
0
    memcpy(now.c_cc, s->tio->c_cc, sizeof now.c_cc);
528
0
  key = options_get_number(global_options, "backspace");
529
0
  if (key >= 0x7f)
530
0
    now.c_cc[VERASE] = '\177';
531
0
  else
532
0
    now.c_cc[VERASE] = key;
533
0
#ifdef IUTF8
534
0
  now.c_iflag |= IUTF8;
535
0
#endif
536
0
  if (tcsetattr(STDIN_FILENO, TCSANOW, &now) != 0)
537
0
    _exit(1);
538
539
  /* Clean up file descriptors and signals and update the environment. */
540
0
  proc_clear_signals(server_proc, 1);
541
0
  closefrom(STDERR_FILENO + 1);
542
0
  sigprocmask(SIG_SETMASK, &oldset, NULL);
543
0
  log_close();
544
0
  environ_push(child);
545
546
  /*
547
   * If given multiple arguments, use execvp(). Copy the arguments to
548
   * ensure they end in a NULL.
549
   */
550
0
  if (new_wp->argc != 0 && new_wp->argc != 1) {
551
0
    argvp = cmd_copy_argv(new_wp->argc, new_wp->argv);
552
0
    execvp(argvp[0], argvp);
553
0
    _exit(1);
554
0
  }
555
556
  /*
557
   * If one argument, pass it to $SHELL -c. Otherwise create a login
558
   * shell.
559
   */
560
0
  cp = strrchr(new_wp->shell, '/');
561
0
  if (new_wp->argc == 1) {
562
0
    tmp = new_wp->argv[0];
563
0
    if (cp != NULL && cp[1] != '\0')
564
0
      xasprintf(&argv0, "%s", cp + 1);
565
0
    else
566
0
      xasprintf(&argv0, "%s", new_wp->shell);
567
0
    execl(new_wp->shell, argv0, "-c", tmp, (char *)NULL);
568
0
    _exit(1);
569
0
  }
570
0
  if (cp != NULL && cp[1] != '\0')
571
0
    xasprintf(&argv0, "-%s", cp + 1);
572
0
  else
573
0
    xasprintf(&argv0, "-%s", new_wp->shell);
574
0
  execl(new_wp->shell, argv0, (char *)NULL);
575
0
  _exit(1);
576
577
0
complete:
578
#ifdef HAVE_UTEMPTER
579
  if (~new_wp->flags & PANE_EMPTY) {
580
    xasprintf(&cp, "tmux(%lu).%%%u", (long)getpid(), new_wp->id);
581
    utempter_add_record(new_wp->fd, cp);
582
    kill(getpid(), SIGCHLD);
583
    free(cp);
584
  }
585
#endif
586
587
0
  new_wp->flags &= ~PANE_EXITED;
588
589
0
  sigprocmask(SIG_SETMASK, &oldset, NULL);
590
0
  window_pane_set_event(new_wp);
591
0
  environ_free(child);
592
0
  spawn_fire_pane_created(sc, new_wp);
593
594
0
  if (sc->flags & SPAWN_RESPAWN)
595
0
    return (new_wp);
596
0
  if (sc->flags & SPAWN_MODAL) {
597
0
    w->modal_last = w->active;
598
0
    w->modal = new_wp;
599
0
    window_redraw_active_switch(w, new_wp);
600
0
    if (sc->flags & SPAWN_NONOTIFY)
601
0
      window_set_active_pane(w, new_wp, 0);
602
0
    else
603
0
      window_set_active_pane(w, new_wp, 1);
604
0
  } else if (((~sc->flags & SPAWN_DETACHED) || w->active == NULL) &&
605
0
      w->modal == NULL) {
606
0
    if (sc->flags & SPAWN_NONOTIFY)
607
0
      window_set_active_pane(w, new_wp, 0);
608
0
    else
609
0
      window_set_active_pane(w, new_wp, 1);
610
0
  }
611
0
  if (~sc->flags & SPAWN_NONOTIFY)
612
0
    events_fire_window("window-layout-changed", w);
613
0
  return (new_wp);
614
0
}
615
616
struct spawn_editor_state {
617
  char      *path;
618
  pid_t      pid;
619
  spawn_finish_edit_cb   cb;
620
  void      *arg;
621
};
622
623
static void
624
spawn_editor_free(struct spawn_editor_state *es)
625
0
{
626
0
  unlink(es->path);
627
0
  free(es->path);
628
0
  free(es);
629
0
}
630
631
void
632
spawn_cancel_editor(struct spawn_editor_state *es)
633
0
{
634
0
  if (es == NULL)
635
0
    return;
636
0
  es->cb = NULL;
637
0
  es->arg = NULL;
638
0
}
639
640
pid_t
641
spawn_get_editor_pid(struct spawn_editor_state *es)
642
0
{
643
0
  if (es == NULL)
644
0
    return (-1);
645
0
  return (es->pid);
646
0
}
647
648
void
649
spawn_editor_finish(struct window_pane *wp)
650
12.4k
{
651
12.4k
  struct spawn_editor_state *es = wp->editor;
652
12.4k
  FILE        *f;
653
12.4k
  char        *buf = NULL;
654
12.4k
  off_t        len = 0;
655
12.4k
  int        status = 128 + SIGHUP;
656
657
12.4k
  if (es == NULL)
658
12.4k
    return;
659
0
  wp->editor = NULL;
660
661
0
  if (wp->flags & PANE_STATUSREADY) {
662
0
    if (WIFEXITED(wp->status))
663
0
      status = WEXITSTATUS(wp->status);
664
0
    else if (WIFSIGNALED(wp->status))
665
0
      status = WTERMSIG(wp->status) + 128;
666
0
  }
667
668
0
  if (es->cb == NULL) {
669
0
    spawn_editor_free(es);
670
0
    return;
671
0
  }
672
0
  if (status != 0) {
673
0
    es->cb(NULL, 0, es->arg);
674
0
    spawn_editor_free(es);
675
0
    return;
676
0
  }
677
678
0
  f = fopen(es->path, "r");
679
0
  if (f != NULL) {
680
0
    if (fseeko(f, 0, SEEK_END) == 0) {
681
0
      len = ftello(f);
682
0
      if (len > 0 && (uintmax_t)len <= (uintmax_t)SIZE_MAX) {
683
0
        if (fseeko(f, 0, SEEK_SET) == 0) {
684
0
          buf = malloc(len);
685
0
          if (buf != NULL &&
686
0
              fread(buf, len, 1, f) != 1) {
687
0
            free(buf);
688
0
            buf = NULL;
689
0
            len = 0;
690
0
          }
691
0
        }
692
0
      } else
693
0
        len = 0;
694
0
    }
695
0
    fclose(f);
696
0
  }
697
698
0
  es->cb(buf, len, es->arg);
699
0
  spawn_editor_free(es);
700
0
}
701
702
struct spawn_editor_state *
703
spawn_editor(struct client *c, const char *buf, size_t len,
704
    spawn_finish_edit_cb cb, void *arg)
705
0
{
706
0
  struct spawn_editor_state *es;
707
0
  struct spawn_context     sc = { 0 };
708
0
  struct session      *s = c->session;
709
0
  struct winlink      *wl = s->curw;
710
0
  struct window     *w = wl->window;
711
0
  struct window_pane    *wp;
712
0
  struct layout_cell    *lc;
713
0
  struct layout_geometry     lg;
714
0
  struct environ      *env;
715
0
  FILE        *f;
716
0
  char        *cmd, *cause = NULL;
717
0
  char         path[] = _PATH_TMP "tmux.XXXXXXXX";
718
0
  const char      *editor;
719
0
  int        fd;
720
721
0
  if (w->modal != NULL)
722
0
    return (NULL);
723
724
0
  editor = options_get_string(global_options, "editor");
725
0
  fd = mkstemp(path);
726
0
  if (fd == -1)
727
0
    return (NULL);
728
0
  f = fdopen(fd, "w");
729
0
  if (f == NULL) {
730
0
    close(fd);
731
0
    unlink(path);
732
0
    return (NULL);
733
0
  }
734
0
  if (fwrite(buf, len, 1, f) != 1) {
735
0
    fclose(f);
736
0
    unlink(path);
737
0
    return (NULL);
738
0
  }
739
0
  fclose(f);
740
741
0
  es = xcalloc(1, sizeof *es);
742
0
  es->path = xstrdup(path);
743
0
  es->cb = cb;
744
0
  es->arg = arg;
745
746
0
  lg.sx = w->sx * 9 / 10;
747
0
  lg.sy = w->sy * 9 / 10;
748
0
  lg.xoff = w->sx / 2 - lg.sx / 2;
749
0
  lg.yoff = w->sy / 2 - lg.sy / 2;
750
0
  window_push_zoom(w, 0, 1);
751
0
  lc = layout_floating_pane(w, NULL, &lg);
752
0
  if (lc == NULL) {
753
0
    window_pop_zoom(w);
754
0
    spawn_editor_free(es);
755
0
    return (NULL);
756
0
  }
757
758
0
  xasprintf(&cmd, "%s %s", editor, path);
759
0
  env = environ_create();
760
0
  sc.s = s;
761
0
  sc.wl = wl;
762
0
  sc.tc = c;
763
0
  sc.wp0 = w->active;
764
0
  sc.lc = lc;
765
0
  sc.argc = 1;
766
0
  sc.argv = &cmd;
767
0
  sc.environ = env;
768
0
  sc.idx = -1;
769
0
  sc.cwd = _PATH_TMP;
770
0
  sc.flags = SPAWN_FLOATING|SPAWN_MODAL|SPAWN_FLOATOVERZOOM;
771
772
0
  wp = spawn_pane(&sc, &cause);
773
0
  free(cmd);
774
0
  environ_free(env);
775
0
  if (wp == NULL) {
776
0
    free(cause);
777
0
    window_pop_zoom(w);
778
0
    spawn_editor_free(es);
779
0
    return (NULL);
780
0
  }
781
0
  window_pop_zoom(w);
782
0
  options_set_number(wp->options, "remain-on-exit", 0);
783
0
  es->pid = wp->pid;
784
0
  wp->editor = es;
785
0
  return (es);
786
0
}