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
);
116 window_set_active_pane(w
, sc
->wp0
, 0);
120 * Otherwise we have no window so we will need to create one. First
121 * check if the given index already exists and destroy it if so.
123 if ((~sc
->flags
& SPAWN_RESPAWN
) && idx
!= -1) {
124 wl
= winlink_find_by_index(&s
->windows
, idx
);
125 if (wl
!= NULL
&& (~sc
->flags
& SPAWN_KILL
)) {
126 xasprintf(cause
, "index %d in use", idx
);
131 * Can't use session_detach as it will destroy session
132 * if this makes it empty.
134 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
135 notify_session_window("window-unlinked", s
, wl
->window
);
136 winlink_stack_remove(&s
->lastw
, wl
);
137 winlink_remove(&s
->windows
, wl
);
141 sc
->flags
&= ~SPAWN_DETACHED
;
146 /* Then create a window if needed. */
147 if (~sc
->flags
& SPAWN_RESPAWN
) {
149 idx
= -1 - options_get_number(s
->options
, "base-index");
150 if ((sc
->wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
151 xasprintf(cause
, "couldn't add window %d", idx
);
154 default_window_size(sc
->tc
, s
, NULL
, &sx
, &sy
, &xpixel
, &ypixel
,
156 if ((w
= window_create(sx
, sy
, xpixel
, ypixel
)) == NULL
) {
157 winlink_remove(&s
->windows
, sc
->wl
);
158 xasprintf(cause
, "couldn't create window %d", idx
);
165 winlink_set_window(sc
->wl
, w
);
168 sc
->flags
|= SPAWN_NONOTIFY
;
170 /* Spawn the pane. */
171 wp
= spawn_pane(sc
, cause
);
173 if (~sc
->flags
& SPAWN_RESPAWN
)
174 winlink_remove(&s
->windows
, sc
->wl
);
178 /* Set the name of the new window. */
179 if (~sc
->flags
& SPAWN_RESPAWN
) {
181 if (sc
->name
!= NULL
) {
182 w
->name
= format_single(item
, sc
->name
, c
, s
, NULL
,
184 options_set_number(w
->options
, "automatic-rename", 0);
186 w
->name
= default_window_name(w
);
189 /* Switch to the new window if required. */
190 if (~sc
->flags
& SPAWN_DETACHED
)
191 session_select(s
, sc
->wl
->idx
);
193 /* Fire notification if new window. */
194 if (~sc
->flags
& SPAWN_RESPAWN
)
195 notify_session_window("window-linked", s
, w
);
197 session_group_synchronize_from(s
);
202 spawn_pane(struct spawn_context
*sc
, char **cause
)
204 struct cmdq_item
*item
= sc
->item
;
205 struct cmd_find_state
*target
= cmdq_get_target(item
);
206 struct client
*c
= cmdq_get_client(item
);
207 struct session
*s
= sc
->s
;
208 struct window
*w
= sc
->wl
->window
;
209 struct window_pane
*new_wp
;
210 struct environ
*child
;
211 struct environ_entry
*ee
;
212 char **argv
, *cp
, **argvp
, *argv0
, *cwd
;
213 const char *cmd
, *tmp
;
219 sigset_t set
, oldset
;
222 spawn_log(__func__
, sc
);
225 * Work out the current working directory. If respawning, use
226 * the pane's stored one unless specified.
229 cwd
= format_single(item
, sc
->cwd
, c
, target
->s
, NULL
, NULL
);
230 else if (~sc
->flags
& SPAWN_RESPAWN
)
231 cwd
= xstrdup(server_client_get_cwd(c
, target
->s
));
236 * If we are respawning then get rid of the old process. Otherwise
237 * either create a new cell or assign to the one we are given.
239 hlimit
= options_get_number(s
->options
, "history-limit");
240 if (sc
->flags
& SPAWN_RESPAWN
) {
241 if (sc
->wp0
->fd
!= -1 && (~sc
->flags
& SPAWN_KILL
)) {
242 window_pane_index(sc
->wp0
, &idx
);
243 xasprintf(cause
, "pane %s:%d.%u still active",
244 s
->name
, sc
->wl
->idx
, idx
);
248 if (sc
->wp0
->fd
!= -1) {
249 bufferevent_free(sc
->wp0
->event
);
252 window_pane_reset_mode_all(sc
->wp0
);
253 screen_reinit(&sc
->wp0
->base
);
254 input_free(sc
->wp0
->ictx
);
255 sc
->wp0
->ictx
= NULL
;
257 new_wp
->flags
&= ~(PANE_STATUSREADY
|PANE_STATUSDRAWN
);
258 } else if (sc
->lc
== NULL
) {
259 new_wp
= window_add_pane(w
, NULL
, hlimit
, sc
->flags
);
260 layout_init(w
, new_wp
);
262 new_wp
= window_add_pane(w
, sc
->wp0
, hlimit
, sc
->flags
);
263 if (sc
->flags
& SPAWN_ZOOM
)
264 layout_assign_pane(sc
->lc
, new_wp
, 1);
266 layout_assign_pane(sc
->lc
, new_wp
, 0);
270 * Now we have a pane with nothing running in it ready for the new
271 * process. Work out the command and arguments and store the working
274 if (sc
->argc
== 0 && (~sc
->flags
& SPAWN_RESPAWN
)) {
275 cmd
= options_get_string(s
->options
, "default-command");
276 if (cmd
!= NULL
&& *cmd
!= '\0') {
278 argv
= (char **)&cmd
;
293 * Replace the stored arguments if there are new ones. If not, the
294 * existing ones will be used (they will only exist for respawn).
297 cmd_free_argv(new_wp
->argc
, new_wp
->argv
);
299 new_wp
->argv
= cmd_copy_argv(argc
, argv
);
302 /* Create an environment for this pane. */
303 child
= environ_for_session(s
, 0);
304 if (sc
->environ
!= NULL
)
305 environ_copy(sc
->environ
, child
);
306 environ_set(child
, "TMUX_PANE", 0, "%%%u", new_wp
->id
);
309 * Then the PATH environment variable. The session one is replaced from
310 * the client if there is one because otherwise running "tmux new
311 * myprogram" wouldn't work if myprogram isn't in the session's path.
313 if (c
!= NULL
&& c
->session
== NULL
) { /* only unattached clients */
314 ee
= environ_find(c
->environ
, "PATH");
316 environ_set(child
, "PATH", 0, "%s", ee
->value
);
318 if (environ_find(child
, "PATH") == NULL
)
319 environ_set(child
, "PATH", 0, "%s", _PATH_DEFPATH
);
321 /* Then the shell. If respawning, use the old one. */
322 if (~sc
->flags
& SPAWN_RESPAWN
) {
323 tmp
= options_get_string(s
->options
, "default-shell");
324 if (!checkshell(tmp
))
327 new_wp
->shell
= xstrdup(tmp
);
329 environ_set(child
, "SHELL", 0, "%s", new_wp
->shell
);
331 /* Log the arguments we are going to use. */
332 log_debug("%s: shell=%s", __func__
, new_wp
->shell
);
333 if (new_wp
->argc
!= 0) {
334 cp
= cmd_stringify_argv(new_wp
->argc
, new_wp
->argv
);
335 log_debug("%s: cmd=%s", __func__
, cp
);
339 log_debug("%s: cwd=%s", __func__
, cwd
);
340 cmd_log_argv(new_wp
->argc
, new_wp
->argv
, "%s", __func__
);
341 environ_log(child
, "%s: environment ", __func__
);
343 /* Initialize the window size. */
344 memset(&ws
, 0, sizeof ws
);
345 ws
.ws_col
= screen_size_x(&new_wp
->base
);
346 ws
.ws_row
= screen_size_y(&new_wp
->base
);
347 ws
.ws_xpixel
= w
->xpixel
* ws
.ws_col
;
348 ws
.ws_ypixel
= w
->ypixel
* ws
.ws_row
;
350 /* Block signals until fork has completed. */
352 sigprocmask(SIG_BLOCK
, &set
, &oldset
);
354 /* If the command is empty, don't fork a child process. */
355 if (sc
->flags
& SPAWN_EMPTY
) {
356 new_wp
->flags
|= PANE_EMPTY
;
357 new_wp
->base
.mode
&= ~MODE_CURSOR
;
358 new_wp
->base
.mode
|= MODE_CRLF
;
362 /* Fork the new process. */
363 new_wp
->pid
= fdforkpty(ptm_fd
, &new_wp
->fd
, new_wp
->tty
, NULL
, &ws
);
364 if (new_wp
->pid
== -1) {
365 xasprintf(cause
, "fork failed: %s", strerror(errno
));
367 if (~sc
->flags
& SPAWN_RESPAWN
) {
368 server_client_remove_pane(new_wp
);
369 layout_close_pane(new_wp
);
370 window_remove_pane(w
, new_wp
);
372 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
377 /* In the parent process, everything is done now. */
378 if (new_wp
->pid
!= 0)
382 * Child process. Change to the working directory or home if that
385 if (chdir(new_wp
->cwd
) != 0 &&
386 ((tmp
= find_home()) == NULL
|| chdir(tmp
) != 0) &&
388 fatal("chdir failed");
391 * Update terminal escape characters from the session if available and
392 * force VERASE to tmux's backspace.
394 if (tcgetattr(STDIN_FILENO
, &now
) != 0)
397 memcpy(now
.c_cc
, s
->tio
->c_cc
, sizeof now
.c_cc
);
398 key
= options_get_number(global_options
, "backspace");
400 now
.c_cc
[VERASE
] = '\177';
402 now
.c_cc
[VERASE
] = key
;
404 now
.c_iflag
|= IUTF8
;
406 if (tcsetattr(STDIN_FILENO
, TCSANOW
, &now
) != 0)
409 /* Clean up file descriptors and signals and update the environment. */
410 closefrom(STDERR_FILENO
+ 1);
411 proc_clear_signals(server_proc
, 1);
412 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
417 * If given multiple arguments, use execvp(). Copy the arguments to
418 * ensure they end in a NULL.
420 if (new_wp
->argc
!= 0 && new_wp
->argc
!= 1) {
421 argvp
= cmd_copy_argv(new_wp
->argc
, new_wp
->argv
);
422 execvp(argvp
[0], argvp
);
427 * If one argument, pass it to $SHELL -c. Otherwise create a login
430 cp
= strrchr(new_wp
->shell
, '/');
431 if (new_wp
->argc
== 1) {
432 tmp
= new_wp
->argv
[0];
433 if (cp
!= NULL
&& cp
[1] != '\0')
434 xasprintf(&argv0
, "%s", cp
+ 1);
436 xasprintf(&argv0
, "%s", new_wp
->shell
);
437 execl(new_wp
->shell
, argv0
, "-c", tmp
, (char *)NULL
);
440 if (cp
!= NULL
&& cp
[1] != '\0')
441 xasprintf(&argv0
, "-%s", cp
+ 1);
443 xasprintf(&argv0
, "-%s", new_wp
->shell
);
444 execl(new_wp
->shell
, argv0
, (char *)NULL
);
449 if (~new_wp
->flags
& PANE_EMPTY
) {
450 xasprintf(&cp
, "tmux(%lu).%%%u", (long)getpid(), new_wp
->id
);
451 utempter_add_record(new_wp
->fd
, cp
);
452 kill(getpid(), SIGCHLD
);
457 new_wp
->flags
&= ~PANE_EXITED
;
459 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
460 window_pane_set_event(new_wp
);
464 if (sc
->flags
& SPAWN_RESPAWN
)
466 if ((~sc
->flags
& SPAWN_DETACHED
) || w
->active
== NULL
) {
467 if (sc
->flags
& SPAWN_NONOTIFY
)
468 window_set_active_pane(w
, new_wp
, 0);
470 window_set_active_pane(w
, new_wp
, 1);
472 if (~sc
->flags
& SPAWN_NONOTIFY
)
473 notify_window("window-layout-changed", w
);