4 * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
30 * Set up the environment and create a new window and pane or a new pane.
32 * We need to set up the following items:
34 * - history limit, comes from the session;
36 * - base index, comes from the session;
38 * - current working directory, may be specified - if it isn't it comes from
39 * either the client or the session;
41 * - PATH variable, comes from the client if any, otherwise from the session
44 * - shell, comes from default-shell;
46 * - termios, comes from the session;
48 * - remaining environment, comes from the session.
52 spawn_log(const char *from
, struct spawn_context
*sc
)
54 struct session
*s
= sc
->s
;
55 struct winlink
*wl
= sc
->wl
;
56 struct window_pane
*wp0
= sc
->wp0
;
57 const char *name
= cmdq_get_name(sc
->item
);
60 log_debug("%s: %s, flags=%#x", from
, name
, sc
->flags
);
62 if (wl
!= NULL
&& wp0
!= NULL
)
63 xsnprintf(tmp
, sizeof tmp
, "wl=%d wp0=%%%u", wl
->idx
, wp0
->id
);
65 xsnprintf(tmp
, sizeof tmp
, "wl=%d wp0=none", wl
->idx
);
67 xsnprintf(tmp
, sizeof tmp
, "wl=none wp0=%%%u", wp0
->id
);
69 xsnprintf(tmp
, sizeof tmp
, "wl=none wp0=none");
70 log_debug("%s: s=$%u %s idx=%d", from
, s
->id
, tmp
, sc
->idx
);
71 log_debug("%s: name=%s", from
, sc
->name
== NULL
? "none" : sc
->name
);
75 spawn_window(struct spawn_context
*sc
, char **cause
)
77 struct cmdq_item
*item
= sc
->item
;
78 struct client
*c
= cmdq_get_client(item
);
79 struct session
*s
= sc
->s
;
81 struct window_pane
*wp
;
84 u_int sx
, sy
, xpixel
, ypixel
;
86 spawn_log(__func__
, sc
);
89 * If the window already exists, we are respawning, so destroy all the
92 if (sc
->flags
& SPAWN_RESPAWN
) {
94 if (~sc
->flags
& SPAWN_KILL
) {
95 TAILQ_FOREACH(wp
, &w
->panes
, entry
) {
100 xasprintf(cause
, "window %s:%d still active",
101 s
->name
, sc
->wl
->idx
);
106 sc
->wp0
= TAILQ_FIRST(&w
->panes
);
107 TAILQ_REMOVE(&w
->panes
, sc
->wp0
, entry
);
110 window_destroy_panes(w
);
112 TAILQ_INSERT_HEAD(&w
->panes
, sc
->wp0
, entry
);
113 window_pane_resize(sc
->wp0
, w
->sx
, w
->sy
);
115 layout_init(w
, sc
->wp0
);
117 window_set_active_pane(w
, sc
->wp0
, 0);
121 * Otherwise we have no window so we will need to create one. First
122 * check if the given index already exists and destroy it if so.
124 if ((~sc
->flags
& SPAWN_RESPAWN
) && idx
!= -1) {
125 wl
= winlink_find_by_index(&s
->windows
, idx
);
126 if (wl
!= NULL
&& (~sc
->flags
& SPAWN_KILL
)) {
127 xasprintf(cause
, "index %d in use", idx
);
132 * Can't use session_detach as it will destroy session
133 * if this makes it empty.
135 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
136 notify_session_window("window-unlinked", s
, wl
->window
);
137 winlink_stack_remove(&s
->lastw
, wl
);
138 winlink_remove(&s
->windows
, wl
);
142 sc
->flags
&= ~SPAWN_DETACHED
;
147 /* Then create a window if needed. */
148 if (~sc
->flags
& SPAWN_RESPAWN
) {
150 idx
= -1 - options_get_number(s
->options
, "base-index");
151 if ((sc
->wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
152 xasprintf(cause
, "couldn't add window %d", idx
);
155 default_window_size(sc
->tc
, s
, NULL
, &sx
, &sy
, &xpixel
, &ypixel
,
157 if ((w
= window_create(sx
, sy
, xpixel
, ypixel
)) == NULL
) {
158 winlink_remove(&s
->windows
, sc
->wl
);
159 xasprintf(cause
, "couldn't create window %d", idx
);
166 winlink_set_window(sc
->wl
, w
);
169 sc
->flags
|= SPAWN_NONOTIFY
;
171 /* Spawn the pane. */
172 wp
= spawn_pane(sc
, cause
);
174 if (~sc
->flags
& SPAWN_RESPAWN
)
175 winlink_remove(&s
->windows
, sc
->wl
);
179 /* Set the name of the new window. */
180 if (~sc
->flags
& SPAWN_RESPAWN
) {
182 if (sc
->name
!= NULL
) {
183 w
->name
= format_single(item
, sc
->name
, c
, s
, NULL
,
185 options_set_number(w
->options
, "automatic-rename", 0);
187 w
->name
= default_window_name(w
);
190 /* Switch to the new window if required. */
191 if (~sc
->flags
& SPAWN_DETACHED
)
192 session_select(s
, sc
->wl
->idx
);
194 /* Fire notification if new window. */
195 if (~sc
->flags
& SPAWN_RESPAWN
)
196 notify_session_window("window-linked", s
, w
);
198 session_group_synchronize_from(s
);
203 spawn_pane(struct spawn_context
*sc
, char **cause
)
205 struct cmdq_item
*item
= sc
->item
;
206 struct cmd_find_state
*target
= cmdq_get_target(item
);
207 struct client
*c
= cmdq_get_client(item
);
208 struct session
*s
= sc
->s
;
209 struct window
*w
= sc
->wl
->window
;
210 struct window_pane
*new_wp
;
211 struct environ
*child
;
212 struct environ_entry
*ee
;
213 char **argv
, *cp
, **argvp
, *argv0
, *cwd
, *new_cwd
;
214 const char *cmd
, *tmp
;
220 sigset_t set
, oldset
;
223 spawn_log(__func__
, sc
);
226 * Work out the current working directory. If respawning, use
227 * the pane's stored one unless specified.
229 if (sc
->cwd
!= NULL
) {
230 cwd
= format_single(item
, sc
->cwd
, c
, target
->s
, NULL
, NULL
);
232 xasprintf(&new_cwd
, "%s/%s", server_client_get_cwd(c
,
237 } else if (~sc
->flags
& SPAWN_RESPAWN
)
238 cwd
= xstrdup(server_client_get_cwd(c
, target
->s
));
243 * If we are respawning then get rid of the old process. Otherwise
244 * either create a new cell or assign to the one we are given.
246 hlimit
= options_get_number(s
->options
, "history-limit");
247 if (sc
->flags
& SPAWN_RESPAWN
) {
248 if (sc
->wp0
->fd
!= -1 && (~sc
->flags
& SPAWN_KILL
)) {
249 window_pane_index(sc
->wp0
, &idx
);
250 xasprintf(cause
, "pane %s:%d.%u still active",
251 s
->name
, sc
->wl
->idx
, idx
);
255 if (sc
->wp0
->fd
!= -1) {
256 bufferevent_free(sc
->wp0
->event
);
259 window_pane_reset_mode_all(sc
->wp0
);
260 screen_reinit(&sc
->wp0
->base
);
261 input_free(sc
->wp0
->ictx
);
262 sc
->wp0
->ictx
= NULL
;
264 new_wp
->flags
&= ~(PANE_STATUSREADY
|PANE_STATUSDRAWN
);
265 } else if (sc
->lc
== NULL
) {
266 new_wp
= window_add_pane(w
, NULL
, hlimit
, sc
->flags
);
267 layout_init(w
, new_wp
);
269 new_wp
= window_add_pane(w
, sc
->wp0
, hlimit
, sc
->flags
);
270 if (sc
->flags
& SPAWN_ZOOM
)
271 layout_assign_pane(sc
->lc
, new_wp
, 1);
273 layout_assign_pane(sc
->lc
, new_wp
, 0);
277 * Now we have a pane with nothing running in it ready for the new
278 * process. Work out the command and arguments and store the working
281 if (sc
->argc
== 0 && (~sc
->flags
& SPAWN_RESPAWN
)) {
282 cmd
= options_get_string(s
->options
, "default-command");
283 if (cmd
!= NULL
&& *cmd
!= '\0') {
285 argv
= (char **)&cmd
;
300 * Replace the stored arguments if there are new ones. If not, the
301 * existing ones will be used (they will only exist for respawn).
304 cmd_free_argv(new_wp
->argc
, new_wp
->argv
);
306 new_wp
->argv
= cmd_copy_argv(argc
, argv
);
309 /* Create an environment for this pane. */
310 child
= environ_for_session(s
, 0);
311 if (sc
->environ
!= NULL
)
312 environ_copy(sc
->environ
, child
);
313 environ_set(child
, "TMUX_PANE", 0, "%%%u", new_wp
->id
);
316 * Then the PATH environment variable. The session one is replaced from
317 * the client if there is one because otherwise running "tmux new
318 * myprogram" wouldn't work if myprogram isn't in the session's path.
320 if (c
!= NULL
&& c
->session
== NULL
) { /* only unattached clients */
321 ee
= environ_find(c
->environ
, "PATH");
323 environ_set(child
, "PATH", 0, "%s", ee
->value
);
325 if (environ_find(child
, "PATH") == NULL
)
326 environ_set(child
, "PATH", 0, "%s", _PATH_DEFPATH
);
328 /* Then the shell. If respawning, use the old one. */
329 if (~sc
->flags
& SPAWN_RESPAWN
) {
330 tmp
= options_get_string(s
->options
, "default-shell");
331 if (!checkshell(tmp
))
334 new_wp
->shell
= xstrdup(tmp
);
336 environ_set(child
, "SHELL", 0, "%s", new_wp
->shell
);
338 /* Log the arguments we are going to use. */
339 log_debug("%s: shell=%s", __func__
, new_wp
->shell
);
340 if (new_wp
->argc
!= 0) {
341 cp
= cmd_stringify_argv(new_wp
->argc
, new_wp
->argv
);
342 log_debug("%s: cmd=%s", __func__
, cp
);
345 log_debug("%s: cwd=%s", __func__
, new_wp
->cwd
);
346 cmd_log_argv(new_wp
->argc
, new_wp
->argv
, "%s", __func__
);
347 environ_log(child
, "%s: environment ", __func__
);
349 /* Initialize the window size. */
350 memset(&ws
, 0, sizeof ws
);
351 ws
.ws_col
= screen_size_x(&new_wp
->base
);
352 ws
.ws_row
= screen_size_y(&new_wp
->base
);
353 ws
.ws_xpixel
= w
->xpixel
* ws
.ws_col
;
354 ws
.ws_ypixel
= w
->ypixel
* ws
.ws_row
;
356 /* Block signals until fork has completed. */
358 sigprocmask(SIG_BLOCK
, &set
, &oldset
);
360 /* If the command is empty, don't fork a child process. */
361 if (sc
->flags
& SPAWN_EMPTY
) {
362 new_wp
->flags
|= PANE_EMPTY
;
363 new_wp
->base
.mode
&= ~MODE_CURSOR
;
364 new_wp
->base
.mode
|= MODE_CRLF
;
368 /* Fork the new process. */
369 new_wp
->pid
= fdforkpty(ptm_fd
, &new_wp
->fd
, new_wp
->tty
, NULL
, &ws
);
370 if (new_wp
->pid
== -1) {
371 xasprintf(cause
, "fork failed: %s", strerror(errno
));
373 if (~sc
->flags
& SPAWN_RESPAWN
) {
374 server_client_remove_pane(new_wp
);
375 layout_close_pane(new_wp
);
376 window_remove_pane(w
, new_wp
);
378 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
383 /* In the parent process, everything is done now. */
384 if (new_wp
->pid
!= 0) {
385 #if defined(HAVE_SYSTEMD) && defined(ENABLE_CGROUPS)
387 * Move the child process into a new cgroup for systemd-oomd
390 if (systemd_move_pid_to_new_cgroup(new_wp
->pid
, cause
) < 0) {
391 log_debug("%s: moving pane to new cgroup failed: %s",
400 * Child process. Change to the working directory or home if that
403 if (chdir(new_wp
->cwd
) == 0)
404 environ_set(child
, "PWD", 0, "%s", new_wp
->cwd
);
405 else if ((tmp
= find_home()) != NULL
&& chdir(tmp
) == 0)
406 environ_set(child
, "PWD", 0, "%s", tmp
);
407 else if (chdir("/") == 0)
408 environ_set(child
, "PWD", 0, "/");
410 fatal("chdir failed");
413 * Update terminal escape characters from the session if available and
414 * force VERASE to tmux's backspace.
416 if (tcgetattr(STDIN_FILENO
, &now
) != 0)
419 memcpy(now
.c_cc
, s
->tio
->c_cc
, sizeof now
.c_cc
);
420 key
= options_get_number(global_options
, "backspace");
422 now
.c_cc
[VERASE
] = '\177';
424 now
.c_cc
[VERASE
] = key
;
426 now
.c_iflag
|= IUTF8
;
428 if (tcsetattr(STDIN_FILENO
, TCSANOW
, &now
) != 0)
431 /* Clean up file descriptors and signals and update the environment. */
432 proc_clear_signals(server_proc
, 1);
433 closefrom(STDERR_FILENO
+ 1);
434 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
439 * If given multiple arguments, use execvp(). Copy the arguments to
440 * ensure they end in a NULL.
442 if (new_wp
->argc
!= 0 && new_wp
->argc
!= 1) {
443 argvp
= cmd_copy_argv(new_wp
->argc
, new_wp
->argv
);
444 execvp(argvp
[0], argvp
);
449 * If one argument, pass it to $SHELL -c. Otherwise create a login
452 cp
= strrchr(new_wp
->shell
, '/');
453 if (new_wp
->argc
== 1) {
454 tmp
= new_wp
->argv
[0];
455 if (cp
!= NULL
&& cp
[1] != '\0')
456 xasprintf(&argv0
, "%s", cp
+ 1);
458 xasprintf(&argv0
, "%s", new_wp
->shell
);
459 execl(new_wp
->shell
, argv0
, "-c", tmp
, (char *)NULL
);
462 if (cp
!= NULL
&& cp
[1] != '\0')
463 xasprintf(&argv0
, "-%s", cp
+ 1);
465 xasprintf(&argv0
, "-%s", new_wp
->shell
);
466 execl(new_wp
->shell
, argv0
, (char *)NULL
);
471 if (~new_wp
->flags
& PANE_EMPTY
) {
472 xasprintf(&cp
, "tmux(%lu).%%%u", (long)getpid(), new_wp
->id
);
473 utempter_add_record(new_wp
->fd
, cp
);
474 kill(getpid(), SIGCHLD
);
479 new_wp
->flags
&= ~PANE_EXITED
;
481 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
482 window_pane_set_event(new_wp
);
486 if (sc
->flags
& SPAWN_RESPAWN
)
488 if ((~sc
->flags
& SPAWN_DETACHED
) || w
->active
== NULL
) {
489 if (sc
->flags
& SPAWN_NONOTIFY
)
490 window_set_active_pane(w
, new_wp
, 0);
492 window_set_active_pane(w
, new_wp
, 1);
494 if (~sc
->flags
& SPAWN_NONOTIFY
)
495 notify_window("window-layout-changed", w
);