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>
32 * Set up the environment and create a new window and pane or a new pane.
34 * We need to set up the following items:
36 * - history limit, comes from the session;
38 * - base index, comes from the session;
40 * - current working directory, may be specified - if it isn't it comes from
41 * either the client or the session;
43 * - PATH variable, comes from the client if any, otherwise from the session
46 * - shell, comes from default-shell;
48 * - termios, comes from the session;
50 * - remaining environment, comes from the session.
54 spawn_log(const char *from
, struct spawn_context
*sc
)
56 struct session
*s
= sc
->s
;
57 struct winlink
*wl
= sc
->wl
;
58 struct window_pane
*wp0
= sc
->wp0
;
59 const char *name
= cmdq_get_name(sc
->item
);
62 log_debug("%s: %s, flags=%#x", from
, name
, sc
->flags
);
64 if (wl
!= NULL
&& wp0
!= NULL
)
65 xsnprintf(tmp
, sizeof tmp
, "wl=%d wp0=%%%u", wl
->idx
, wp0
->id
);
67 xsnprintf(tmp
, sizeof tmp
, "wl=%d wp0=none", wl
->idx
);
69 xsnprintf(tmp
, sizeof tmp
, "wl=none wp0=%%%u", wp0
->id
);
71 xsnprintf(tmp
, sizeof tmp
, "wl=none wp0=none");
72 log_debug("%s: s=$%u %s idx=%d", from
, s
->id
, tmp
, sc
->idx
);
73 log_debug("%s: name=%s", from
, sc
->name
== NULL
? "none" : sc
->name
);
77 spawn_window(struct spawn_context
*sc
, char **cause
)
79 struct cmdq_item
*item
= sc
->item
;
80 struct client
*c
= cmdq_get_client(item
);
81 struct session
*s
= sc
->s
;
83 struct window_pane
*wp
;
86 u_int sx
, sy
, xpixel
, ypixel
;
88 spawn_log(__func__
, sc
);
91 * If the window already exists, we are respawning, so destroy all the
94 if (sc
->flags
& SPAWN_RESPAWN
) {
96 if (~sc
->flags
& SPAWN_KILL
) {
97 TAILQ_FOREACH(wp
, &w
->panes
, entry
) {
102 xasprintf(cause
, "window %s:%d still active",
103 s
->name
, sc
->wl
->idx
);
108 sc
->wp0
= TAILQ_FIRST(&w
->panes
);
109 TAILQ_REMOVE(&w
->panes
, sc
->wp0
, entry
);
112 window_destroy_panes(w
);
114 TAILQ_INSERT_HEAD(&w
->panes
, sc
->wp0
, entry
);
115 window_pane_resize(sc
->wp0
, w
->sx
, w
->sy
);
117 layout_init(w
, sc
->wp0
);
118 window_set_active_pane(w
, sc
->wp0
, 0);
122 * Otherwise we have no window so we will need to create one. First
123 * check if the given index already exists and destroy it if so.
125 if ((~sc
->flags
& SPAWN_RESPAWN
) && idx
!= -1) {
126 wl
= winlink_find_by_index(&s
->windows
, idx
);
127 if (wl
!= NULL
&& (~sc
->flags
& SPAWN_KILL
)) {
128 xasprintf(cause
, "index %d in use", idx
);
133 * Can't use session_detach as it will destroy session
134 * if this makes it empty.
136 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
137 notify_session_window("window-unlinked", s
, wl
->window
);
138 winlink_stack_remove(&s
->lastw
, wl
);
139 winlink_remove(&s
->windows
, wl
);
143 sc
->flags
&= ~SPAWN_DETACHED
;
148 /* Then create a window if needed. */
149 if (~sc
->flags
& SPAWN_RESPAWN
) {
151 idx
= -1 - options_get_number(s
->options
, "base-index");
152 if ((sc
->wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
153 xasprintf(cause
, "couldn't add window %d", idx
);
156 default_window_size(sc
->tc
, s
, NULL
, &sx
, &sy
, &xpixel
, &ypixel
,
158 if ((w
= window_create(sx
, sy
, xpixel
, ypixel
)) == NULL
) {
159 winlink_remove(&s
->windows
, sc
->wl
);
160 xasprintf(cause
, "couldn't create window %d", idx
);
167 winlink_set_window(sc
->wl
, w
);
170 sc
->flags
|= SPAWN_NONOTIFY
;
172 /* Spawn the pane. */
173 wp
= spawn_pane(sc
, cause
);
175 if (~sc
->flags
& SPAWN_RESPAWN
)
176 winlink_remove(&s
->windows
, sc
->wl
);
180 /* Set the name of the new window. */
181 if (~sc
->flags
& SPAWN_RESPAWN
) {
183 if (sc
->name
!= NULL
) {
184 w
->name
= format_single(item
, sc
->name
, c
, s
, NULL
,
186 options_set_number(w
->options
, "automatic-rename", 0);
188 w
->name
= default_window_name(w
);
191 /* Switch to the new window if required. */
192 if (~sc
->flags
& SPAWN_DETACHED
)
193 session_select(s
, sc
->wl
->idx
);
195 /* Fire notification if new window. */
196 if (~sc
->flags
& SPAWN_RESPAWN
)
197 notify_session_window("window-linked", s
, w
);
199 session_group_synchronize_from(s
);
204 spawn_pane(struct spawn_context
*sc
, char **cause
)
206 struct cmdq_item
*item
= sc
->item
;
207 struct cmd_find_state
*target
= cmdq_get_target(item
);
208 struct client
*c
= cmdq_get_client(item
);
209 struct session
*s
= sc
->s
;
210 struct window
*w
= sc
->wl
->window
;
211 struct window_pane
*new_wp
;
212 struct environ
*child
;
213 struct environ_entry
*ee
;
214 char **argv
, *cp
, **argvp
, *argv0
, *cwd
, *new_cwd
;
215 const char *cmd
, *tmp
;
221 sigset_t set
, oldset
;
224 spawn_log(__func__
, sc
);
227 * Work out the current working directory. If respawning, use
228 * the pane's stored one unless specified.
230 if (sc
->cwd
!= NULL
) {
231 cwd
= format_single(item
, sc
->cwd
, c
, target
->s
, NULL
, NULL
);
233 xasprintf(&new_cwd
, "%s/%s", server_client_get_cwd(c
,
238 } else if (~sc
->flags
& SPAWN_RESPAWN
)
239 cwd
= xstrdup(server_client_get_cwd(c
, target
->s
));
244 * If we are respawning then get rid of the old process. Otherwise
245 * either create a new cell or assign to the one we are given.
247 hlimit
= options_get_number(s
->options
, "history-limit");
248 if (sc
->flags
& SPAWN_RESPAWN
) {
249 if (sc
->wp0
->fd
!= -1 && (~sc
->flags
& SPAWN_KILL
)) {
250 window_pane_index(sc
->wp0
, &idx
);
251 xasprintf(cause
, "pane %s:%d.%u still active",
252 s
->name
, sc
->wl
->idx
, idx
);
256 if (sc
->wp0
->fd
!= -1) {
257 bufferevent_free(sc
->wp0
->event
);
260 window_pane_reset_mode_all(sc
->wp0
);
261 screen_reinit(&sc
->wp0
->base
);
262 input_free(sc
->wp0
->ictx
);
263 sc
->wp0
->ictx
= NULL
;
265 new_wp
->flags
&= ~(PANE_STATUSREADY
|PANE_STATUSDRAWN
);
266 } else if (sc
->lc
== NULL
) {
267 new_wp
= window_add_pane(w
, NULL
, hlimit
, sc
->flags
);
268 layout_init(w
, new_wp
);
270 new_wp
= window_add_pane(w
, sc
->wp0
, hlimit
, sc
->flags
);
271 if (sc
->flags
& SPAWN_ZOOM
)
272 layout_assign_pane(sc
->lc
, new_wp
, 1);
274 layout_assign_pane(sc
->lc
, new_wp
, 0);
278 * Now we have a pane with nothing running in it ready for the new
279 * process. Work out the command and arguments and store the working
282 if (sc
->argc
== 0 && (~sc
->flags
& SPAWN_RESPAWN
)) {
283 cmd
= options_get_string(s
->options
, "default-command");
284 if (cmd
!= NULL
&& *cmd
!= '\0') {
286 argv
= (char **)&cmd
;
301 * Replace the stored arguments if there are new ones. If not, the
302 * existing ones will be used (they will only exist for respawn).
305 cmd_free_argv(new_wp
->argc
, new_wp
->argv
);
307 new_wp
->argv
= cmd_copy_argv(argc
, argv
);
310 /* Create an environment for this pane. */
311 child
= environ_for_session(s
, 0);
312 if (sc
->environ
!= NULL
)
313 environ_copy(sc
->environ
, child
);
314 environ_set(child
, "TMUX_PANE", 0, "%%%u", new_wp
->id
);
317 * Then the PATH environment variable. The session one is replaced from
318 * the client if there is one because otherwise running "tmux new
319 * myprogram" wouldn't work if myprogram isn't in the session's path.
321 if (c
!= NULL
&& c
->session
== NULL
) { /* only unattached clients */
322 ee
= environ_find(c
->environ
, "PATH");
324 environ_set(child
, "PATH", 0, "%s", ee
->value
);
326 if (environ_find(child
, "PATH") == NULL
)
327 environ_set(child
, "PATH", 0, "%s", _PATH_DEFPATH
);
329 /* Then the shell. If respawning, use the old one. */
330 if (~sc
->flags
& SPAWN_RESPAWN
) {
331 tmp
= options_get_string(s
->options
, "default-shell");
332 if (!checkshell(tmp
))
335 new_wp
->shell
= xstrdup(tmp
);
337 environ_set(child
, "SHELL", 0, "%s", new_wp
->shell
);
339 /* Log the arguments we are going to use. */
340 log_debug("%s: shell=%s", __func__
, new_wp
->shell
);
341 if (new_wp
->argc
!= 0) {
342 cp
= cmd_stringify_argv(new_wp
->argc
, new_wp
->argv
);
343 log_debug("%s: cmd=%s", __func__
, cp
);
346 log_debug("%s: cwd=%s", __func__
, new_wp
->cwd
);
347 cmd_log_argv(new_wp
->argc
, new_wp
->argv
, "%s", __func__
);
348 environ_log(child
, "%s: environment ", __func__
);
350 /* Initialize the window size. */
351 memset(&ws
, 0, sizeof ws
);
352 ws
.ws_col
= screen_size_x(&new_wp
->base
);
353 ws
.ws_row
= screen_size_y(&new_wp
->base
);
354 ws
.ws_xpixel
= w
->xpixel
* ws
.ws_col
;
355 ws
.ws_ypixel
= w
->ypixel
* ws
.ws_row
;
357 /* Block signals until fork has completed. */
359 sigprocmask(SIG_BLOCK
, &set
, &oldset
);
361 /* If the command is empty, don't fork a child process. */
362 if (sc
->flags
& SPAWN_EMPTY
) {
363 new_wp
->flags
|= PANE_EMPTY
;
364 new_wp
->base
.mode
&= ~MODE_CURSOR
;
365 new_wp
->base
.mode
|= MODE_CRLF
;
369 /* Fork the new process. */
370 new_wp
->pid
= fdforkpty(ptm_fd
, &new_wp
->fd
, new_wp
->tty
, NULL
, &ws
);
371 if (new_wp
->pid
== -1) {
372 xasprintf(cause
, "fork failed: %s", strerror(errno
));
374 if (~sc
->flags
& SPAWN_RESPAWN
) {
375 server_client_remove_pane(new_wp
);
376 layout_close_pane(new_wp
);
377 window_remove_pane(w
, new_wp
);
379 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
384 /* In the parent process, everything is done now. */
385 if (new_wp
->pid
!= 0)
389 * Child process. Change to the working directory or home if that
392 if (chdir(new_wp
->cwd
) == 0)
393 environ_set(child
, "PWD", 0, "%s", new_wp
->cwd
);
394 else if ((tmp
= find_home()) != NULL
&& chdir(tmp
) == 0)
395 environ_set(child
, "PWD", 0, "%s", tmp
);
396 else if (chdir("/") == 0)
397 environ_set(child
, "PWD", 0, "/");
399 fatal("chdir failed");
402 * Update terminal escape characters from the session if available and
403 * force VERASE to tmux's backspace.
405 if (tcgetattr(STDIN_FILENO
, &now
) != 0)
408 memcpy(now
.c_cc
, s
->tio
->c_cc
, sizeof now
.c_cc
);
409 key
= options_get_number(global_options
, "backspace");
411 now
.c_cc
[VERASE
] = '\177';
413 now
.c_cc
[VERASE
] = key
;
414 if (tcsetattr(STDIN_FILENO
, TCSANOW
, &now
) != 0)
417 /* Clean up file descriptors and signals and update the environment. */
418 closefrom(STDERR_FILENO
+ 1);
419 proc_clear_signals(server_proc
, 1);
420 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
425 * If given multiple arguments, use execvp(). Copy the arguments to
426 * ensure they end in a NULL.
428 if (new_wp
->argc
!= 0 && new_wp
->argc
!= 1) {
429 argvp
= cmd_copy_argv(new_wp
->argc
, new_wp
->argv
);
430 execvp(argvp
[0], argvp
);
435 * If one argument, pass it to $SHELL -c. Otherwise create a login
438 cp
= strrchr(new_wp
->shell
, '/');
439 if (new_wp
->argc
== 1) {
440 tmp
= new_wp
->argv
[0];
441 if (cp
!= NULL
&& cp
[1] != '\0')
442 xasprintf(&argv0
, "%s", cp
+ 1);
444 xasprintf(&argv0
, "%s", new_wp
->shell
);
445 execl(new_wp
->shell
, argv0
, "-c", tmp
, (char *)NULL
);
448 if (cp
!= NULL
&& cp
[1] != '\0')
449 xasprintf(&argv0
, "-%s", cp
+ 1);
451 xasprintf(&argv0
, "-%s", new_wp
->shell
);
452 execl(new_wp
->shell
, argv0
, (char *)NULL
);
456 new_wp
->flags
&= ~PANE_EXITED
;
458 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
459 window_pane_set_event(new_wp
);
463 if (sc
->flags
& SPAWN_RESPAWN
)
465 if ((~sc
->flags
& SPAWN_DETACHED
) || w
->active
== NULL
) {
466 if (sc
->flags
& SPAWN_NONOTIFY
)
467 window_set_active_pane(w
, new_wp
, 0);
469 window_set_active_pane(w
, new_wp
, 1);
471 if (~sc
->flags
& SPAWN_NONOTIFY
)
472 notify_window("window-layout-changed", w
);