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
;
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.
231 cwd
= format_single(item
, sc
->cwd
, c
, target
->s
, NULL
, NULL
);
232 else if (~sc
->flags
& SPAWN_RESPAWN
)
233 cwd
= xstrdup(server_client_get_cwd(c
, target
->s
));
238 * If we are respawning then get rid of the old process. Otherwise
239 * either create a new cell or assign to the one we are given.
241 hlimit
= options_get_number(s
->options
, "history-limit");
242 if (sc
->flags
& SPAWN_RESPAWN
) {
243 if (sc
->wp0
->fd
!= -1 && (~sc
->flags
& SPAWN_KILL
)) {
244 window_pane_index(sc
->wp0
, &idx
);
245 xasprintf(cause
, "pane %s:%d.%u still active",
246 s
->name
, sc
->wl
->idx
, idx
);
250 if (sc
->wp0
->fd
!= -1) {
251 bufferevent_free(sc
->wp0
->event
);
254 window_pane_reset_mode_all(sc
->wp0
);
255 screen_reinit(&sc
->wp0
->base
);
256 input_free(sc
->wp0
->ictx
);
257 sc
->wp0
->ictx
= NULL
;
259 new_wp
->flags
&= ~(PANE_STATUSREADY
|PANE_STATUSDRAWN
);
260 } else if (sc
->lc
== NULL
) {
261 new_wp
= window_add_pane(w
, NULL
, hlimit
, sc
->flags
);
262 layout_init(w
, new_wp
);
264 new_wp
= window_add_pane(w
, sc
->wp0
, hlimit
, sc
->flags
);
265 if (sc
->flags
& SPAWN_ZOOM
)
266 layout_assign_pane(sc
->lc
, new_wp
, 1);
268 layout_assign_pane(sc
->lc
, new_wp
, 0);
272 * Now we have a pane with nothing running in it ready for the new
273 * process. Work out the command and arguments and store the working
276 if (sc
->argc
== 0 && (~sc
->flags
& SPAWN_RESPAWN
)) {
277 cmd
= options_get_string(s
->options
, "default-command");
278 if (cmd
!= NULL
&& *cmd
!= '\0') {
280 argv
= (char **)&cmd
;
295 * Replace the stored arguments if there are new ones. If not, the
296 * existing ones will be used (they will only exist for respawn).
299 cmd_free_argv(new_wp
->argc
, new_wp
->argv
);
301 new_wp
->argv
= cmd_copy_argv(argc
, argv
);
304 /* Create an environment for this pane. */
305 child
= environ_for_session(s
, 0);
306 if (sc
->environ
!= NULL
)
307 environ_copy(sc
->environ
, child
);
308 environ_set(child
, "TMUX_PANE", 0, "%%%u", new_wp
->id
);
311 * Then the PATH environment variable. The session one is replaced from
312 * the client if there is one because otherwise running "tmux new
313 * myprogram" wouldn't work if myprogram isn't in the session's path.
315 if (c
!= NULL
&& c
->session
== NULL
) { /* only unattached clients */
316 ee
= environ_find(c
->environ
, "PATH");
318 environ_set(child
, "PATH", 0, "%s", ee
->value
);
320 if (environ_find(child
, "PATH") == NULL
)
321 environ_set(child
, "PATH", 0, "%s", _PATH_DEFPATH
);
323 /* Then the shell. If respawning, use the old one. */
324 if (~sc
->flags
& SPAWN_RESPAWN
) {
325 tmp
= options_get_string(s
->options
, "default-shell");
326 if (!checkshell(tmp
))
329 new_wp
->shell
= xstrdup(tmp
);
331 environ_set(child
, "SHELL", 0, "%s", new_wp
->shell
);
333 /* Log the arguments we are going to use. */
334 log_debug("%s: shell=%s", __func__
, new_wp
->shell
);
335 if (new_wp
->argc
!= 0) {
336 cp
= cmd_stringify_argv(new_wp
->argc
, new_wp
->argv
);
337 log_debug("%s: cmd=%s", __func__
, cp
);
341 log_debug("%s: cwd=%s", __func__
, cwd
);
342 cmd_log_argv(new_wp
->argc
, new_wp
->argv
, "%s", __func__
);
343 environ_log(child
, "%s: environment ", __func__
);
345 /* Initialize the window size. */
346 memset(&ws
, 0, sizeof ws
);
347 ws
.ws_col
= screen_size_x(&new_wp
->base
);
348 ws
.ws_row
= screen_size_y(&new_wp
->base
);
349 ws
.ws_xpixel
= w
->xpixel
* ws
.ws_col
;
350 ws
.ws_ypixel
= w
->ypixel
* ws
.ws_row
;
352 /* Block signals until fork has completed. */
354 sigprocmask(SIG_BLOCK
, &set
, &oldset
);
356 /* If the command is empty, don't fork a child process. */
357 if (sc
->flags
& SPAWN_EMPTY
) {
358 new_wp
->flags
|= PANE_EMPTY
;
359 new_wp
->base
.mode
&= ~MODE_CURSOR
;
360 new_wp
->base
.mode
|= MODE_CRLF
;
364 /* Fork the new process. */
365 new_wp
->pid
= fdforkpty(ptm_fd
, &new_wp
->fd
, new_wp
->tty
, NULL
, &ws
);
366 if (new_wp
->pid
== -1) {
367 xasprintf(cause
, "fork failed: %s", strerror(errno
));
369 if (~sc
->flags
& SPAWN_RESPAWN
) {
370 server_client_remove_pane(new_wp
);
371 layout_close_pane(new_wp
);
372 window_remove_pane(w
, new_wp
);
374 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
379 /* In the parent process, everything is done now. */
380 if (new_wp
->pid
!= 0)
384 * Child process. Change to the working directory or home if that
387 if (chdir(new_wp
->cwd
) != 0 &&
388 ((tmp
= find_home()) == NULL
|| chdir(tmp
) != 0) &&
390 fatal("chdir failed");
393 * Update terminal escape characters from the session if available and
394 * force VERASE to tmux's backspace.
396 if (tcgetattr(STDIN_FILENO
, &now
) != 0)
399 memcpy(now
.c_cc
, s
->tio
->c_cc
, sizeof now
.c_cc
);
400 key
= options_get_number(global_options
, "backspace");
402 now
.c_cc
[VERASE
] = '\177';
404 now
.c_cc
[VERASE
] = key
;
405 if (tcsetattr(STDIN_FILENO
, TCSANOW
, &now
) != 0)
408 /* Clean up file descriptors and signals and update the environment. */
409 closefrom(STDERR_FILENO
+ 1);
410 proc_clear_signals(server_proc
, 1);
411 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
416 * If given multiple arguments, use execvp(). Copy the arguments to
417 * ensure they end in a NULL.
419 if (new_wp
->argc
!= 0 && new_wp
->argc
!= 1) {
420 argvp
= cmd_copy_argv(new_wp
->argc
, new_wp
->argv
);
421 execvp(argvp
[0], argvp
);
426 * If one argument, pass it to $SHELL -c. Otherwise create a login
429 cp
= strrchr(new_wp
->shell
, '/');
430 if (new_wp
->argc
== 1) {
431 tmp
= new_wp
->argv
[0];
432 if (cp
!= NULL
&& cp
[1] != '\0')
433 xasprintf(&argv0
, "%s", cp
+ 1);
435 xasprintf(&argv0
, "%s", new_wp
->shell
);
436 execl(new_wp
->shell
, argv0
, "-c", tmp
, (char *)NULL
);
439 if (cp
!= NULL
&& cp
[1] != '\0')
440 xasprintf(&argv0
, "-%s", cp
+ 1);
442 xasprintf(&argv0
, "-%s", new_wp
->shell
);
443 execl(new_wp
->shell
, argv0
, (char *)NULL
);
447 new_wp
->flags
&= ~PANE_EXITED
;
449 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
450 window_pane_set_event(new_wp
);
454 if (sc
->flags
& SPAWN_RESPAWN
)
456 if ((~sc
->flags
& SPAWN_DETACHED
) || w
->active
== NULL
) {
457 if (sc
->flags
& SPAWN_NONOTIFY
)
458 window_set_active_pane(w
, new_wp
, 0);
460 window_set_active_pane(w
, new_wp
, 1);
462 if (~sc
->flags
& SPAWN_NONOTIFY
)
463 notify_window("window-layout-changed", w
);