Coverage Report

Created: 2026-08-13 06:07

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.50 2026/07/23 09:38:27 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;
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
0
    new_wp = sc->wp0;
335
0
    new_wp->flags &= ~(PANE_STATUSREADY|PANE_STATUSDRAWN);
336
0
  } else {
337
0
    if (sc->lc == NULL) {
338
0
      new_wp = window_add_pane(w, NULL, hlimit, sc->flags);
339
0
      layout_init(w, new_wp);
340
0
    } else {
341
0
      new_wp = window_add_pane(w, sc->wp0, hlimit, sc->flags);
342
0
      if (sc->flags & SPAWN_ZOOM)
343
0
        layout_assign_pane(sc->lc, new_wp, 1);
344
0
      else
345
0
        layout_assign_pane(sc->lc, new_wp, 0);
346
0
    }
347
0
    if (sc->flags & SPAWN_FLOATING)
348
0
      new_wp->layout_cell->flags |= LAYOUT_CELL_FLOATING;
349
350
    /*
351
     * If window currently zoomed, window_set_active_pane calls
352
     * window_unzoom which it copies back the saved_layout_cell.
353
     */
354
0
    if (w->flags & WINDOW_ZOOMED)
355
0
      new_wp->saved_layout_cell = new_wp->layout_cell;
356
0
  }
357
358
  /*
359
   * Now we have a pane with nothing running in it ready for the new
360
   * process. Work out the command and arguments and store the working
361
   * directory.
362
   */
363
0
  if (sc->argc == 0 && (~sc->flags & SPAWN_RESPAWN)) {
364
0
    cmd = options_get_string(s->options, "default-command");
365
0
    if (cmd != NULL && *cmd != '\0') {
366
0
      argc = 1;
367
0
      argv = (char **)&cmd;
368
0
    } else {
369
0
      argc = 0;
370
0
      argv = NULL;
371
0
    }
372
0
  } else {
373
0
    argc = sc->argc;
374
0
    argv = sc->argv;
375
0
  }
376
0
  if (cwd != NULL) {
377
0
    free(new_wp->cwd);
378
0
    new_wp->cwd = cwd;
379
0
  }
380
381
  /*
382
   * Replace the stored arguments if there are new ones. If not, the
383
   * existing ones will be used (they will only exist for respawn).
384
   */
385
0
  if (argc > 0) {
386
0
    cmd_free_argv(new_wp->argc, new_wp->argv);
387
0
    new_wp->argc = argc;
388
0
    new_wp->argv = cmd_copy_argv(argc, argv);
389
0
  }
390
391
  /* Create an environment for this pane. */
392
0
  child = environ_for_session(s, 0);
393
0
  if (sc->environ != NULL)
394
0
    environ_copy(sc->environ, child);
395
0
  environ_set(child, "TMUX_PANE", 0, "%%%u", new_wp->id);
396
397
  /*
398
   * Then the PATH environment variable. The session one is replaced from
399
   * the client if there is one because otherwise running "tmux new
400
   * myprogram" wouldn't work if myprogram isn't in the session's path.
401
   */
402
0
  if (c != NULL && c->session == NULL) { /* only unattached clients */
403
0
    ee = environ_find(c->environ, "PATH");
404
0
    if (ee != NULL)
405
0
      environ_set(child, "PATH", 0, "%s", ee->value);
406
0
  }
407
0
  if (environ_find(child, "PATH") == NULL)
408
0
    environ_set(child, "PATH", 0, "%s", _PATH_DEFPATH);
409
410
  /* Then the shell. If respawning, use the old one. */
411
0
  if (~sc->flags & SPAWN_RESPAWN) {
412
0
    tmp = options_get_string(s->options, "default-shell");
413
0
    if (!checkshell(tmp))
414
0
      tmp = _PATH_BSHELL;
415
0
    free(new_wp->shell);
416
0
    new_wp->shell = xstrdup(tmp);
417
0
  }
418
0
  environ_set(child, "SHELL", 0, "%s", new_wp->shell);
419
420
  /* Log the arguments we are going to use. */
421
0
  log_debug("%s: shell=%s", __func__, new_wp->shell);
422
0
  if (new_wp->argc != 0) {
423
0
    cp = cmd_stringify_argv(new_wp->argc, new_wp->argv);
424
0
    log_debug("%s: cmd=%s", __func__, cp);
425
0
    free(cp);
426
0
  }
427
0
  log_debug("%s: cwd=%s", __func__, new_wp->cwd);
428
0
  cmd_log_argv(new_wp->argc, new_wp->argv, "%s", __func__);
429
0
  environ_log(child, "%s: environment ", __func__);
430
431
  /* Initialize the window size. */
432
0
  memset(&ws, 0, sizeof ws);
433
0
  ws.ws_col = screen_size_x(&new_wp->base);
434
0
  ws.ws_row = screen_size_y(&new_wp->base);
435
0
  ws.ws_xpixel = w->xpixel * ws.ws_col;
436
0
  ws.ws_ypixel = w->ypixel * ws.ws_row;
437
438
  /* Block signals until fork has completed. */
439
0
  sigfillset(&set);
440
0
  sigprocmask(SIG_BLOCK, &set, &oldset);
441
442
  /* If the command is empty, don't fork a child process. */
443
0
  if (sc->flags & SPAWN_EMPTY) {
444
0
    new_wp->flags |= PANE_EMPTY;
445
0
    new_wp->base.mode &= ~MODE_CURSOR;
446
0
    new_wp->base.mode |= MODE_CRLF;
447
0
    goto complete;
448
0
  }
449
0
  new_wp->flags &= ~PANE_EMPTY;
450
451
  /* Store current working directory and change to new one. */
452
0
  if (getcwd(path, sizeof path) != NULL) {
453
0
    if (chdir(new_wp->cwd) == 0)
454
0
      actual_cwd = new_wp->cwd;
455
0
    else if (home != NULL && chdir(home) == 0)
456
0
      actual_cwd = home;
457
0
    else if (chdir("/") == 0)
458
0
      actual_cwd = "/";
459
0
  }
460
461
  /* Fork the new process. */
462
0
  new_wp->pid = fdforkpty(ptm_fd, &new_wp->fd, new_wp->tty, NULL, &ws);
463
0
  if (new_wp->pid == -1) {
464
0
    xasprintf(cause, "fork failed: %s", strerror(errno));
465
0
    new_wp->fd = -1;
466
0
    if (~sc->flags & SPAWN_RESPAWN) {
467
0
      server_client_remove_pane(new_wp);
468
0
      layout_close_pane(new_wp);
469
0
      window_remove_pane(w, new_wp);
470
0
    }
471
0
    sigprocmask(SIG_SETMASK, &oldset, NULL);
472
0
    environ_free(child);
473
0
    return (NULL);
474
0
  }
475
476
  /*
477
   * In the parent process, everything is done now. Change the working
478
   * directory back.
479
   */
480
0
  if (new_wp->pid != 0) {
481
0
    if (actual_cwd != NULL &&
482
0
        chdir(path) != 0 &&
483
0
        (home == NULL || chdir(home) != 0))
484
0
      chdir("/");
485
0
    goto complete;
486
0
  }
487
488
#if defined(HAVE_SYSTEMD) && defined(ENABLE_CGROUPS)
489
  /*
490
   * Move the child process into a new cgroup for systemd-oomd isolation.
491
   */
492
  if (systemd_move_to_new_cgroup(cause) < 0) {
493
    log_debug("%s: moving pane to new cgroup failed: %s",
494
        __func__, *cause);
495
    free (*cause);
496
  }
497
#endif
498
  /*
499
   * Child process. Set PWD to the working directory.
500
   */
501
0
  if (actual_cwd != NULL)
502
0
    environ_set(child, "PWD", 0, "%s", actual_cwd);
503
504
  /*
505
   * Update terminal escape characters from the session if available and
506
   * force VERASE to tmux's backspace.
507
   */
508
0
  if (tcgetattr(STDIN_FILENO, &now) != 0)
509
0
    _exit(1);
510
0
  if (s->tio != NULL)
511
0
    memcpy(now.c_cc, s->tio->c_cc, sizeof now.c_cc);
512
0
  key = options_get_number(global_options, "backspace");
513
0
  if (key >= 0x7f)
514
0
    now.c_cc[VERASE] = '\177';
515
0
  else
516
0
    now.c_cc[VERASE] = key;
517
0
#ifdef IUTF8
518
0
  now.c_iflag |= IUTF8;
519
0
#endif
520
0
  if (tcsetattr(STDIN_FILENO, TCSANOW, &now) != 0)
521
0
    _exit(1);
522
523
  /* Clean up file descriptors and signals and update the environment. */
524
0
  proc_clear_signals(server_proc, 1);
525
0
  closefrom(STDERR_FILENO + 1);
526
0
  sigprocmask(SIG_SETMASK, &oldset, NULL);
527
0
  log_close();
528
0
  environ_push(child);
529
530
  /*
531
   * If given multiple arguments, use execvp(). Copy the arguments to
532
   * ensure they end in a NULL.
533
   */
534
0
  if (new_wp->argc != 0 && new_wp->argc != 1) {
535
0
    argvp = cmd_copy_argv(new_wp->argc, new_wp->argv);
536
0
    execvp(argvp[0], argvp);
537
0
    _exit(1);
538
0
  }
539
540
  /*
541
   * If one argument, pass it to $SHELL -c. Otherwise create a login
542
   * shell.
543
   */
544
0
  cp = strrchr(new_wp->shell, '/');
545
0
  if (new_wp->argc == 1) {
546
0
    tmp = new_wp->argv[0];
547
0
    if (cp != NULL && cp[1] != '\0')
548
0
      xasprintf(&argv0, "%s", cp + 1);
549
0
    else
550
0
      xasprintf(&argv0, "%s", new_wp->shell);
551
0
    execl(new_wp->shell, argv0, "-c", tmp, (char *)NULL);
552
0
    _exit(1);
553
0
  }
554
0
  if (cp != NULL && cp[1] != '\0')
555
0
    xasprintf(&argv0, "-%s", cp + 1);
556
0
  else
557
0
    xasprintf(&argv0, "-%s", new_wp->shell);
558
0
  execl(new_wp->shell, argv0, (char *)NULL);
559
0
  _exit(1);
560
561
0
complete:
562
#ifdef HAVE_UTEMPTER
563
  if (~new_wp->flags & PANE_EMPTY) {
564
    xasprintf(&cp, "tmux(%lu).%%%u", (long)getpid(), new_wp->id);
565
    utempter_add_record(new_wp->fd, cp);
566
    kill(getpid(), SIGCHLD);
567
    free(cp);
568
  }
569
#endif
570
571
0
  new_wp->flags &= ~PANE_EXITED;
572
573
0
  sigprocmask(SIG_SETMASK, &oldset, NULL);
574
0
  window_pane_set_event(new_wp);
575
0
  environ_free(child);
576
0
  spawn_fire_pane_created(sc, new_wp);
577
578
0
  if (sc->flags & SPAWN_RESPAWN)
579
0
    return (new_wp);
580
0
  if (sc->flags & SPAWN_MODAL) {
581
0
    w->modal_last = w->active;
582
0
    w->modal = new_wp;
583
0
    window_redraw_active_switch(w, new_wp);
584
0
    if (sc->flags & SPAWN_NONOTIFY)
585
0
      window_set_active_pane(w, new_wp, 0);
586
0
    else
587
0
      window_set_active_pane(w, new_wp, 1);
588
0
  } else if (((~sc->flags & SPAWN_DETACHED) || w->active == NULL) &&
589
0
      w->modal == NULL) {
590
0
    if (sc->flags & SPAWN_NONOTIFY)
591
0
      window_set_active_pane(w, new_wp, 0);
592
0
    else
593
0
      window_set_active_pane(w, new_wp, 1);
594
0
  }
595
0
  if (~sc->flags & SPAWN_NONOTIFY)
596
0
    events_fire_window("window-layout-changed", w);
597
0
  return (new_wp);
598
0
}
599
600
struct spawn_editor_state {
601
  char      *path;
602
  pid_t      pid;
603
  spawn_finish_edit_cb   cb;
604
  void      *arg;
605
};
606
607
static void
608
spawn_editor_free(struct spawn_editor_state *es)
609
0
{
610
0
  unlink(es->path);
611
0
  free(es->path);
612
0
  free(es);
613
0
}
614
615
void
616
spawn_cancel_editor(struct spawn_editor_state *es)
617
0
{
618
0
  if (es == NULL)
619
0
    return;
620
0
  es->cb = NULL;
621
0
  es->arg = NULL;
622
0
}
623
624
pid_t
625
spawn_get_editor_pid(struct spawn_editor_state *es)
626
0
{
627
0
  if (es == NULL)
628
0
    return (-1);
629
0
  return (es->pid);
630
0
}
631
632
void
633
spawn_editor_finish(struct window_pane *wp)
634
0
{
635
0
  struct spawn_editor_state *es = wp->editor;
636
0
  FILE        *f;
637
0
  char        *buf = NULL;
638
0
  off_t        len = 0;
639
0
  int        status = 128 + SIGHUP;
640
641
0
  if (es == NULL)
642
0
    return;
643
0
  wp->editor = NULL;
644
645
0
  if (wp->flags & PANE_STATUSREADY) {
646
0
    if (WIFEXITED(wp->status))
647
0
      status = WEXITSTATUS(wp->status);
648
0
    else if (WIFSIGNALED(wp->status))
649
0
      status = WTERMSIG(wp->status) + 128;
650
0
  }
651
652
0
  if (es->cb == NULL) {
653
0
    spawn_editor_free(es);
654
0
    return;
655
0
  }
656
0
  if (status != 0) {
657
0
    es->cb(NULL, 0, es->arg);
658
0
    spawn_editor_free(es);
659
0
    return;
660
0
  }
661
662
0
  f = fopen(es->path, "r");
663
0
  if (f != NULL) {
664
0
    if (fseeko(f, 0, SEEK_END) == 0) {
665
0
      len = ftello(f);
666
0
      if (len > 0 && (uintmax_t)len <= (uintmax_t)SIZE_MAX) {
667
0
        if (fseeko(f, 0, SEEK_SET) == 0) {
668
0
          buf = malloc(len);
669
0
          if (buf != NULL &&
670
0
              fread(buf, len, 1, f) != 1) {
671
0
            free(buf);
672
0
            buf = NULL;
673
0
            len = 0;
674
0
          }
675
0
        }
676
0
      } else
677
0
        len = 0;
678
0
    }
679
0
    fclose(f);
680
0
  }
681
682
0
  es->cb(buf, len, es->arg);
683
0
  spawn_editor_free(es);
684
0
}
685
686
struct spawn_editor_state *
687
spawn_editor(struct client *c, const char *buf, size_t len,
688
    spawn_finish_edit_cb cb, void *arg)
689
0
{
690
0
  struct spawn_editor_state *es;
691
0
  struct spawn_context     sc = { 0 };
692
0
  struct session      *s = c->session;
693
0
  struct winlink      *wl = s->curw;
694
0
  struct window     *w = wl->window;
695
0
  struct window_pane    *wp;
696
0
  struct layout_cell    *lc;
697
0
  struct layout_geometry     lg;
698
0
  struct environ      *env;
699
0
  FILE        *f;
700
0
  char        *cmd, *cause = NULL;
701
0
  char         path[] = _PATH_TMP "tmux.XXXXXXXX";
702
0
  const char      *editor;
703
0
  int        fd;
704
705
0
  if (w->modal != NULL)
706
0
    return (NULL);
707
708
0
  editor = options_get_string(global_options, "editor");
709
0
  fd = mkstemp(path);
710
0
  if (fd == -1)
711
0
    return (NULL);
712
0
  f = fdopen(fd, "w");
713
0
  if (f == NULL) {
714
0
    close(fd);
715
0
    unlink(path);
716
0
    return (NULL);
717
0
  }
718
0
  if (fwrite(buf, len, 1, f) != 1) {
719
0
    fclose(f);
720
0
    unlink(path);
721
0
    return (NULL);
722
0
  }
723
0
  fclose(f);
724
725
0
  es = xcalloc(1, sizeof *es);
726
0
  es->path = xstrdup(path);
727
0
  es->cb = cb;
728
0
  es->arg = arg;
729
730
0
  lg.sx = w->sx * 9 / 10;
731
0
  lg.sy = w->sy * 9 / 10;
732
0
  lg.xoff = w->sx / 2 - lg.sx / 2;
733
0
  lg.yoff = w->sy / 2 - lg.sy / 2;
734
0
  window_push_modal_zoom(w);
735
0
  lc = layout_floating_pane(w, NULL, &lg);
736
0
  if (lc == NULL) {
737
0
    window_pop_modal_zoom(w);
738
0
    spawn_editor_free(es);
739
0
    return (NULL);
740
0
  }
741
742
0
  xasprintf(&cmd, "%s %s", editor, path);
743
0
  env = environ_create();
744
0
  sc.s = s;
745
0
  sc.wl = wl;
746
0
  sc.tc = c;
747
0
  sc.wp0 = w->active;
748
0
  sc.lc = lc;
749
0
  sc.argc = 1;
750
0
  sc.argv = &cmd;
751
0
  sc.environ = env;
752
0
  sc.idx = -1;
753
0
  sc.cwd = _PATH_TMP;
754
0
  sc.flags = SPAWN_FLOATING|SPAWN_MODAL;
755
756
0
  wp = spawn_pane(&sc, &cause);
757
0
  free(cmd);
758
0
  environ_free(env);
759
0
  if (wp == NULL) {
760
0
    free(cause);
761
0
    window_pop_modal_zoom(w);
762
0
    spawn_editor_free(es);
763
0
    return (NULL);
764
0
  }
765
0
  options_set_number(wp->options, "remain-on-exit", 0);
766
0
  es->pid = wp->pid;
767
0
  wp->editor = es;
768
0
  return (es);
769
0
}