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
);
119 window_set_active_pane(w
, sc
->wp0
, 0);
123 * Otherwise we have no window so we will need to create one. First
124 * check if the given index already exists and destroy it if so.
126 if ((~sc
->flags
& SPAWN_RESPAWN
) && idx
!= -1) {
127 wl
= winlink_find_by_index(&s
->windows
, idx
);
128 if (wl
!= NULL
&& (~sc
->flags
& SPAWN_KILL
)) {
129 xasprintf(cause
, "index %d in use", idx
);
134 * Can't use session_detach as it will destroy session
135 * if this makes it empty.
137 wl
->flags
&= ~WINLINK_ALERTFLAGS
;
138 notify_session_window("window-unlinked", s
, wl
->window
);
139 winlink_stack_remove(&s
->lastw
, wl
);
140 winlink_remove(&s
->windows
, wl
);
144 sc
->flags
&= ~SPAWN_DETACHED
;
149 /* Then create a window if needed. */
150 if (~sc
->flags
& SPAWN_RESPAWN
) {
152 idx
= -1 - options_get_number(s
->options
, "base-index");
153 if ((sc
->wl
= winlink_add(&s
->windows
, idx
)) == NULL
) {
154 xasprintf(cause
, "couldn't add window %d", idx
);
157 default_window_size(sc
->tc
, s
, NULL
, &sx
, &sy
, &xpixel
, &ypixel
,
159 if ((w
= window_create(sx
, sy
, xpixel
, ypixel
)) == NULL
) {
160 winlink_remove(&s
->windows
, sc
->wl
);
161 xasprintf(cause
, "couldn't create window %d", idx
);
168 winlink_set_window(sc
->wl
, w
);
171 sc
->flags
|= SPAWN_NONOTIFY
;
173 /* Spawn the pane. */
174 wp
= spawn_pane(sc
, cause
);
176 if (~sc
->flags
& SPAWN_RESPAWN
)
177 winlink_remove(&s
->windows
, sc
->wl
);
181 /* Set the name of the new window. */
182 if (~sc
->flags
& SPAWN_RESPAWN
) {
184 if (sc
->name
!= NULL
) {
185 w
->name
= format_single(item
, sc
->name
, c
, s
, NULL
,
187 options_set_number(w
->options
, "automatic-rename", 0);
189 w
->name
= default_window_name(w
);
192 /* Switch to the new window if required. */
193 if (~sc
->flags
& SPAWN_DETACHED
)
194 session_select(s
, sc
->wl
->idx
);
196 /* Fire notification if new window. */
197 if (~sc
->flags
& SPAWN_RESPAWN
)
198 notify_session_window("window-linked", s
, w
);
200 session_group_synchronize_from(s
);
205 spawn_pane(struct spawn_context
*sc
, char **cause
)
207 struct cmdq_item
*item
= sc
->item
;
208 struct cmd_find_state
*target
= cmdq_get_target(item
);
209 struct client
*c
= cmdq_get_client(item
);
210 struct session
*s
= sc
->s
;
211 struct window
*w
= sc
->wl
->window
;
212 struct window_pane
*new_wp
;
213 struct environ
*child
;
214 struct environ_entry
*ee
;
215 char **argv
, *cp
, **argvp
, *argv0
, *cwd
, *new_cwd
;
216 const char *cmd
, *tmp
;
222 sigset_t set
, oldset
;
225 spawn_log(__func__
, sc
);
228 * Work out the current working directory. If respawning, use
229 * the pane's stored one unless specified.
231 if (sc
->cwd
!= NULL
) {
232 cwd
= format_single(item
, sc
->cwd
, c
, target
->s
, NULL
, NULL
);
234 xasprintf(&new_cwd
, "%s/%s", server_client_get_cwd(c
,
239 } else if (~sc
->flags
& SPAWN_RESPAWN
)
240 cwd
= xstrdup(server_client_get_cwd(c
, target
->s
));
245 * If we are respawning then get rid of the old process. Otherwise
246 * either create a new cell or assign to the one we are given.
248 hlimit
= options_get_number(s
->options
, "history-limit");
249 if (sc
->flags
& SPAWN_RESPAWN
) {
250 if (sc
->wp0
->fd
!= -1 && (~sc
->flags
& SPAWN_KILL
)) {
251 window_pane_index(sc
->wp0
, &idx
);
252 xasprintf(cause
, "pane %s:%d.%u still active",
253 s
->name
, sc
->wl
->idx
, idx
);
257 if (sc
->wp0
->fd
!= -1) {
258 bufferevent_free(sc
->wp0
->event
);
261 window_pane_reset_mode_all(sc
->wp0
);
262 screen_reinit(&sc
->wp0
->base
);
263 input_free(sc
->wp0
->ictx
);
264 sc
->wp0
->ictx
= NULL
;
266 new_wp
->flags
&= ~(PANE_STATUSREADY
|PANE_STATUSDRAWN
);
267 } else if (sc
->lc
== NULL
) {
268 new_wp
= window_add_pane(w
, NULL
, hlimit
, sc
->flags
);
269 layout_init(w
, new_wp
);
271 new_wp
= window_add_pane(w
, sc
->wp0
, hlimit
, sc
->flags
);
272 if (sc
->flags
& SPAWN_ZOOM
)
273 layout_assign_pane(sc
->lc
, new_wp
, 1);
275 layout_assign_pane(sc
->lc
, new_wp
, 0);
279 * Now we have a pane with nothing running in it ready for the new
280 * process. Work out the command and arguments and store the working
283 if (sc
->argc
== 0 && (~sc
->flags
& SPAWN_RESPAWN
)) {
284 cmd
= options_get_string(s
->options
, "default-command");
285 if (cmd
!= NULL
&& *cmd
!= '\0') {
287 argv
= (char **)&cmd
;
302 * Replace the stored arguments if there are new ones. If not, the
303 * existing ones will be used (they will only exist for respawn).
306 cmd_free_argv(new_wp
->argc
, new_wp
->argv
);
308 new_wp
->argv
= cmd_copy_argv(argc
, argv
);
311 /* Create an environment for this pane. */
312 child
= environ_for_session(s
, 0);
313 if (sc
->environ
!= NULL
)
314 environ_copy(sc
->environ
, child
);
315 environ_set(child
, "TMUX_PANE", 0, "%%%u", new_wp
->id
);
318 * Then the PATH environment variable. The session one is replaced from
319 * the client if there is one because otherwise running "tmux new
320 * myprogram" wouldn't work if myprogram isn't in the session's path.
322 if (c
!= NULL
&& c
->session
== NULL
) { /* only unattached clients */
323 ee
= environ_find(c
->environ
, "PATH");
325 environ_set(child
, "PATH", 0, "%s", ee
->value
);
327 if (environ_find(child
, "PATH") == NULL
)
328 environ_set(child
, "PATH", 0, "%s", _PATH_DEFPATH
);
330 /* Then the shell. If respawning, use the old one. */
331 if (~sc
->flags
& SPAWN_RESPAWN
) {
332 tmp
= options_get_string(s
->options
, "default-shell");
333 if (!checkshell(tmp
))
336 new_wp
->shell
= xstrdup(tmp
);
338 environ_set(child
, "SHELL", 0, "%s", new_wp
->shell
);
340 /* Log the arguments we are going to use. */
341 log_debug("%s: shell=%s", __func__
, new_wp
->shell
);
342 if (new_wp
->argc
!= 0) {
343 cp
= cmd_stringify_argv(new_wp
->argc
, new_wp
->argv
);
344 log_debug("%s: cmd=%s", __func__
, cp
);
347 log_debug("%s: cwd=%s", __func__
, new_wp
->cwd
);
348 cmd_log_argv(new_wp
->argc
, new_wp
->argv
, "%s", __func__
);
349 environ_log(child
, "%s: environment ", __func__
);
351 /* Initialize the window size. */
352 memset(&ws
, 0, sizeof ws
);
353 ws
.ws_col
= screen_size_x(&new_wp
->base
);
354 ws
.ws_row
= screen_size_y(&new_wp
->base
);
355 ws
.ws_xpixel
= w
->xpixel
* ws
.ws_col
;
356 ws
.ws_ypixel
= w
->ypixel
* ws
.ws_row
;
358 /* Block signals until fork has completed. */
360 sigprocmask(SIG_BLOCK
, &set
, &oldset
);
362 /* If the command is empty, don't fork a child process. */
363 if (sc
->flags
& SPAWN_EMPTY
) {
364 new_wp
->flags
|= PANE_EMPTY
;
365 new_wp
->base
.mode
&= ~MODE_CURSOR
;
366 new_wp
->base
.mode
|= MODE_CRLF
;
370 /* Fork the new process. */
371 new_wp
->pid
= fdforkpty(ptm_fd
, &new_wp
->fd
, new_wp
->tty
, NULL
, &ws
);
372 if (new_wp
->pid
== -1) {
373 xasprintf(cause
, "fork failed: %s", strerror(errno
));
375 if (~sc
->flags
& SPAWN_RESPAWN
) {
376 server_client_remove_pane(new_wp
);
377 layout_close_pane(new_wp
);
378 window_remove_pane(w
, new_wp
);
380 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
385 /* In the parent process, everything is done now. */
386 if (new_wp
->pid
!= 0)
390 * Child process. Change to the working directory or home if that
393 if (chdir(new_wp
->cwd
) == 0)
394 environ_set(child
, "PWD", 0, "%s", new_wp
->cwd
);
395 else if ((tmp
= find_home()) != NULL
&& chdir(tmp
) == 0)
396 environ_set(child
, "PWD", 0, "%s", tmp
);
397 else if (chdir("/") == 0)
398 environ_set(child
, "PWD", 0, "/");
400 fatal("chdir failed");
403 * Update terminal escape characters from the session if available and
404 * force VERASE to tmux's backspace.
406 if (tcgetattr(STDIN_FILENO
, &now
) != 0)
409 memcpy(now
.c_cc
, s
->tio
->c_cc
, sizeof now
.c_cc
);
410 key
= options_get_number(global_options
, "backspace");
412 now
.c_cc
[VERASE
] = '\177';
414 now
.c_cc
[VERASE
] = key
;
415 if (tcsetattr(STDIN_FILENO
, TCSANOW
, &now
) != 0)
418 /* Clean up file descriptors and signals and update the environment. */
419 proc_clear_signals(server_proc
, 1);
420 closefrom(STDERR_FILENO
+ 1);
421 sigprocmask(SIG_SETMASK
, &oldset
, NULL
);
426 * If given multiple arguments, use execvp(). Copy the arguments to
427 * ensure they end in a NULL.
429 if (new_wp
->argc
!= 0 && new_wp
->argc
!= 1) {
430 argvp
= cmd_copy_argv(new_wp
->argc
, new_wp
->argv
);
431 execvp(argvp
[0], argvp
);
436 * If one argument, pass it to $SHELL -c. Otherwise create a login
439 cp
= strrchr(new_wp
->shell
, '/');
440 if (new_wp
->argc
== 1) {
441 tmp
= new_wp
->argv
[0];
442 if (cp
!= NULL
&& cp
[1] != '\0')
443 xasprintf(&argv0
, "%s", cp
+ 1);
445 xasprintf(&argv0
, "%s", new_wp
->shell
);
446 execl(new_wp
->shell
, argv0
, "-c", tmp
, (char *)NULL
);
449 if (cp
!= NULL
&& cp
[1] != '\0')
450 xasprintf(&argv0
, "-%s", cp
+ 1);
452 xasprintf(&argv0
, "-%s", new_wp
->shell
);
453 execl(new_wp
->shell
, argv0
, (char *)NULL
);
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
);