xref: /openbsd-src/usr.bin/tmux/cmd-new-session.c (revision 8e309d18bf90f4983b96a7af2a17b66380c6597c)
1*8e309d18Snicm /* $OpenBSD: cmd-new-session.c,v 1.146 2022/07/06 08:40:52 nicm Exp $ */
2311827fbSnicm 
3311827fbSnicm /*
498ca8272Snicm  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5311827fbSnicm  *
6311827fbSnicm  * Permission to use, copy, modify, and distribute this software for any
7311827fbSnicm  * purpose with or without fee is hereby granted, provided that the above
8311827fbSnicm  * copyright notice and this permission notice appear in all copies.
9311827fbSnicm  *
10311827fbSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11311827fbSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12311827fbSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13311827fbSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14311827fbSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15311827fbSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16311827fbSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17311827fbSnicm  */
18311827fbSnicm 
19311827fbSnicm #include <sys/types.h>
20311827fbSnicm 
21e1803d63Snicm #include <errno.h>
22e1803d63Snicm #include <fcntl.h>
23e3cefb22Snicm #include <stdlib.h>
24c1f39627Snicm #include <string.h>
25c1f39627Snicm #include <termios.h>
26b511d510Snicm #include <unistd.h>
27c1f39627Snicm 
28311827fbSnicm #include "tmux.h"
29311827fbSnicm 
30311827fbSnicm /*
31311827fbSnicm  * Create a new session and attach to the current terminal unless -d is given.
32311827fbSnicm  */
33311827fbSnicm 
341905ff33Snicm #define NEW_SESSION_TEMPLATE "#{session_name}:"
351905ff33Snicm 
3668e0a7f2Snicm static enum cmd_retval	cmd_new_session_exec(struct cmd *, struct cmdq_item *);
37311827fbSnicm 
38311827fbSnicm const struct cmd_entry cmd_new_session_entry = {
39c057646bSnicm 	.name = "new-session",
40c057646bSnicm 	.alias = "new",
41c057646bSnicm 
42a51dead1Snicm 	.args = { "Ac:dDe:EF:f:n:Ps:t:x:Xy:", 0, -1, NULL },
43b4606cf7Snicm 	.usage = "[-AdDEPX] [-c start-directory] [-e environment] [-F format] "
44cfef6bbbSnicm 		 "[-f flags] [-n window-name] [-s session-name] "
45d8b32369Snicm 		 CMD_TARGET_SESSION_USAGE " [-x width] [-y height] "
46d8b32369Snicm 		 "[shell-command]",
47c057646bSnicm 
48bf0d297eSnicm 	.target = { 't', CMD_FIND_SESSION, CMD_FIND_CANFAIL },
498d471e80Snicm 
508d471e80Snicm 	.flags = CMD_STARTSERVER,
51c057646bSnicm 	.exec = cmd_new_session_exec
52311827fbSnicm };
53311827fbSnicm 
5410d7b797Snicm const struct cmd_entry cmd_has_session_entry = {
55c057646bSnicm 	.name = "has-session",
56c057646bSnicm 	.alias = "has",
57c057646bSnicm 
58a51dead1Snicm 	.args = { "t:", 0, 0, NULL },
59c057646bSnicm 	.usage = CMD_TARGET_SESSION_USAGE,
60c057646bSnicm 
61bf0d297eSnicm 	.target = { 't', CMD_FIND_SESSION, 0 },
628d471e80Snicm 
638d471e80Snicm 	.flags = 0,
64c057646bSnicm 	.exec = cmd_new_session_exec
6510d7b797Snicm };
6610d7b797Snicm 
67dc1f0f5fSnicm static enum cmd_retval
cmd_new_session_exec(struct cmd * self,struct cmdq_item * item)6868e0a7f2Snicm cmd_new_session_exec(struct cmd *self, struct cmdq_item *item)
69311827fbSnicm {
7090d7ba38Snicm 	struct args		*args = cmd_get_args(self);
71823b6d6dSnicm 	struct cmd_find_state	*current = cmdq_get_current(item);
72040343aeSnicm 	struct cmd_find_state	*target = cmdq_get_target(item);
73040343aeSnicm 	struct client		*c = cmdq_get_client(item);
74c27246d4Snicm 	struct session		*s, *as, *groupwith = NULL;
75fb46cb3dSnicm 	struct environ		*env;
767b470e93Snicm 	struct options		*oo;
77f58d7262Snicm 	struct termios		 tio, *tiop;
78c27246d4Snicm 	struct session_group	*sg = NULL;
7905b80794Snicm 	const char		*errstr, *template, *group, *tmp;
80c26c4f79Snicm 	char			*cause, *cwd = NULL, *cp, *newname = NULL;
81c27246d4Snicm 	char			*name, *prefix = NULL;
82c26c4f79Snicm 	int			 detached, already_attached, is_control = 0;
831693b10bSnicm 	u_int			 sx, sy, dsx, dsy, count = args_count(args);
841693b10bSnicm 	struct spawn_context	 sc = { 0 };
8589b49179Snicm 	enum cmd_retval		 retval;
86c26c4f79Snicm 	struct cmd_find_state    fs;
8705b80794Snicm 	struct args_value	*av;
88311827fbSnicm 
8990d7ba38Snicm 	if (cmd_get_entry(self) == &cmd_has_session_entry) {
903447b427Snicm 		/*
913cfca51bSnicm 		 * cmd_find_target() will fail if the session cannot be found,
923cfca51bSnicm 		 * so always return success here.
933447b427Snicm 		 */
9410d7b797Snicm 		return (CMD_RETURN_NORMAL);
9510d7b797Snicm 	}
9610d7b797Snicm 
971693b10bSnicm 	if (args_has(args, 't') && (count != 0 || args_has(args, 'n'))) {
9868e0a7f2Snicm 		cmdq_error(item, "command or window name given with target");
991fe07f53Snicm 		return (CMD_RETURN_ERROR);
1001fe07f53Snicm 	}
1011fe07f53Snicm 
10253fa2326Snicm 	tmp = args_get(args, 's');
10353fa2326Snicm 	if (tmp != NULL) {
104c27246d4Snicm 		name = format_single(item, tmp, c, NULL, NULL, NULL);
105c27246d4Snicm 		newname = session_check_name(name);
106998b9ee1Snicm 		if (newname == NULL) {
107998b9ee1Snicm 			cmdq_error(item, "invalid session: %s", name);
108998b9ee1Snicm 			free(name);
109998b9ee1Snicm 			return (CMD_RETURN_ERROR);
110998b9ee1Snicm 		}
111c27246d4Snicm 		free(name);
11253fa2326Snicm 	}
113eb088ac5Snicm 	if (args_has(args, 'A')) {
11453fa2326Snicm 		if (newname != NULL)
11553fa2326Snicm 			as = session_find(newname);
11653fa2326Snicm 		else
117040343aeSnicm 			as = target->s;
11853fa2326Snicm 		if (as != NULL) {
11953fa2326Snicm 			retval = cmd_attach_session(item, as->name,
12053fa2326Snicm 			    args_has(args, 'D'), args_has(args, 'X'), 0, NULL,
121cfef6bbbSnicm 			    args_has(args, 'E'), args_get(args, 'f'));
12289b49179Snicm 			free(newname);
12389b49179Snicm 			return (retval);
124eb088ac5Snicm 		}
12553fa2326Snicm 	}
12653fa2326Snicm 	if (newname != NULL && session_find(newname) != NULL) {
12768e0a7f2Snicm 		cmdq_error(item, "duplicate session: %s", newname);
128c26c4f79Snicm 		goto fail;
129311827fbSnicm 	}
130311827fbSnicm 
13106440b28Snicm 	/* Is this going to be part of a session group? */
13206440b28Snicm 	group = args_get(args, 't');
13306440b28Snicm 	if (group != NULL) {
134040343aeSnicm 		groupwith = target->s;
135c27246d4Snicm 		if (groupwith == NULL)
13606440b28Snicm 			sg = session_group_find(group);
137c27246d4Snicm 		else
13806440b28Snicm 			sg = session_group_contains(groupwith);
13906440b28Snicm 		if (sg != NULL)
140c27246d4Snicm 			prefix = xstrdup(sg->name);
14106440b28Snicm 		else if (groupwith != NULL)
142c27246d4Snicm 			prefix = xstrdup(groupwith->name);
143998b9ee1Snicm 		else {
144c27246d4Snicm 			prefix = session_check_name(group);
145998b9ee1Snicm 			if (prefix == NULL) {
146998b9ee1Snicm 				cmdq_error(item, "invalid session group: %s",
147998b9ee1Snicm 				    group);
148998b9ee1Snicm 				goto fail;
149998b9ee1Snicm 			}
150998b9ee1Snicm 		}
15106440b28Snicm 	}
15201b2421eSnicm 
153ca6e6a1fSnicm 	/* Set -d if no client. */
154ca7befccSnicm 	detached = args_has(args, 'd');
155175d36ccSnicm 	if (c == NULL)
156ca6e6a1fSnicm 		detached = 1;
157c9d1f66aSnicm 	else if (c->flags & CLIENT_CONTROL)
158c9d1f66aSnicm 		is_control = 1;
159ca6e6a1fSnicm 
160175d36ccSnicm 	/* Is this client already attached? */
161175d36ccSnicm 	already_attached = 0;
162175d36ccSnicm 	if (c != NULL && c->session != NULL)
163175d36ccSnicm 		already_attached = 1;
164175d36ccSnicm 
165e1803d63Snicm 	/* Get the new session working directory. */
16689b49179Snicm 	if ((tmp = args_get(args, 'c')) != NULL)
16789b49179Snicm 		cwd = format_single(item, tmp, c, NULL, NULL, NULL);
1683baa4a0cSnicm 	else
1698a00c84cSnicm 		cwd = xstrdup(server_client_get_cwd(c, NULL));
170e1803d63Snicm 
1710d4c683bSnicm 	/*
172fde81079Snicm 	 * If this is a new client, check for nesting and save the termios
173fde81079Snicm 	 * settings (part of which is used for new windows in this session).
1740d4c683bSnicm 	 *
175fde81079Snicm 	 * tcgetattr() is used rather than using tty.tio since if the client is
176fde81079Snicm 	 * detached, tty_open won't be called. It must be done before opening
177fde81079Snicm 	 * the terminal as that calls tcsetattr() to prepare for tmux taking
178fde81079Snicm 	 * over.
1790d4c683bSnicm 	 */
180a34cf9c8Snicm 	if (!detached &&
181a34cf9c8Snicm 	    !already_attached &&
182a34cf9c8Snicm 	    c->fd != -1 &&
183a34cf9c8Snicm 	    (~c->flags & CLIENT_CONTROL)) {
184040343aeSnicm 		if (server_client_check_nested(cmdq_get_client(item))) {
18568e0a7f2Snicm 			cmdq_error(item, "sessions should be nested with care, "
186fde81079Snicm 			    "unset $TMUX to force");
187c26c4f79Snicm 			goto fail;
188fde81079Snicm 		}
189a4da624bSnicm 		if (tcgetattr(c->fd, &tio) != 0)
1900d4c683bSnicm 			fatal("tcgetattr failed");
191f58d7262Snicm 		tiop = &tio;
1920d4c683bSnicm 	} else
193f58d7262Snicm 		tiop = NULL;
1940d4c683bSnicm 
195dcdf1788Snicm 	/* Open the terminal if necessary. */
196175d36ccSnicm 	if (!detached && !already_attached) {
197807352cfSnicm 		if (server_client_open(c, &cause) != 0) {
19868e0a7f2Snicm 			cmdq_error(item, "open terminal failed: %s", cause);
1997d053cf9Snicm 			free(cause);
200c26c4f79Snicm 			goto fail;
201311827fbSnicm 		}
202dcdf1788Snicm 	}
203311827fbSnicm 
2047b470e93Snicm 	/* Get default session size. */
2057b470e93Snicm 	if (args_has(args, 'x')) {
2067b470e93Snicm 		tmp = args_get(args, 'x');
2077b470e93Snicm 		if (strcmp(tmp, "-") == 0) {
2087b470e93Snicm 			if (c != NULL)
2097b470e93Snicm 				dsx = c->tty.sx;
210adabc796Snicm 			else
211adabc796Snicm 				dsx = 80;
2127b470e93Snicm 		} else {
2137b470e93Snicm 			dsx = strtonum(tmp, 1, USHRT_MAX, &errstr);
2147b470e93Snicm 			if (errstr != NULL) {
2157b470e93Snicm 				cmdq_error(item, "width %s", errstr);
216c26c4f79Snicm 				goto fail;
2177b470e93Snicm 			}
2187b470e93Snicm 		}
2198f26b93fSnicm 	} else
2208f26b93fSnicm 		dsx = 80;
2217b470e93Snicm 	if (args_has(args, 'y')) {
2227b470e93Snicm 		tmp = args_get(args, 'y');
2237b470e93Snicm 		if (strcmp(tmp, "-") == 0) {
2247b470e93Snicm 			if (c != NULL)
2257b470e93Snicm 				dsy = c->tty.sy;
226adabc796Snicm 			else
227adabc796Snicm 				dsy = 24;
2287b470e93Snicm 		} else {
2297b470e93Snicm 			dsy = strtonum(tmp, 1, USHRT_MAX, &errstr);
2307b470e93Snicm 			if (errstr != NULL) {
2317b470e93Snicm 				cmdq_error(item, "height %s", errstr);
232c26c4f79Snicm 				goto fail;
2337b470e93Snicm 			}
2347b470e93Snicm 		}
2358f26b93fSnicm 	} else
2368f26b93fSnicm 		dsy = 24;
2377b470e93Snicm 
238832f07f9Snicm 	/* Find new session size. */
2397b470e93Snicm 	if (!detached && !is_control) {
240175d36ccSnicm 		sx = c->tty.sx;
241175d36ccSnicm 		sy = c->tty.sy;
242db993454Snicm 		if (sy > 0 && options_get_number(global_s_options, "status"))
243c9d1f66aSnicm 			sy--;
24460b67408Snicm 	} else {
245c26c4f79Snicm 		tmp = options_get_string(global_s_options, "default-size");
246c26c4f79Snicm 		if (sscanf(tmp, "%ux%u", &sx, &sy) != 2) {
2478f26b93fSnicm 			sx = dsx;
2488f26b93fSnicm 			sy = dsy;
2498f26b93fSnicm 		} else {
2507b470e93Snicm 			if (args_has(args, 'x'))
2517b470e93Snicm 				sx = dsx;
2527b470e93Snicm 			if (args_has(args, 'y'))
2537b470e93Snicm 				sy = dsy;
254e2d5d61cSnicm 		}
2558f26b93fSnicm 	}
256dcdf1788Snicm 	if (sx == 0)
257dcdf1788Snicm 		sx = 1;
258dcdf1788Snicm 	if (sy == 0)
259dcdf1788Snicm 		sy = 1;
260832f07f9Snicm 
261c26c4f79Snicm 	/* Create the new session. */
2627b470e93Snicm 	oo = options_create(global_s_options);
263db8f8d01Snicm 	if (args_has(args, 'x') || args_has(args, 'y')) {
264db8f8d01Snicm 		if (!args_has(args, 'x'))
265db8f8d01Snicm 			dsx = sx;
266db8f8d01Snicm 		if (!args_has(args, 'y'))
267db8f8d01Snicm 			dsy = sy;
2687b470e93Snicm 		options_set_string(oo, "default-size", 0, "%ux%u", dsx, dsy);
269db8f8d01Snicm 	}
270c26c4f79Snicm 	env = environ_create();
271c26c4f79Snicm 	if (c != NULL && !args_has(args, 'E'))
272c26c4f79Snicm 		environ_update(global_s_options, c->environ, env);
27305b80794Snicm 	av = args_first_value(args, 'e');
27405b80794Snicm 	while (av != NULL) {
275825f884aSnicm 		environ_put(env, av->string, 0);
27605b80794Snicm 		av = args_next_value(av);
277b4606cf7Snicm 	}
278c26c4f79Snicm 	s = session_create(prefix, newname, cwd, env, oo, tiop);
2797b470e93Snicm 
280c26c4f79Snicm 	/* Spawn the initial window. */
281c26c4f79Snicm 	sc.item = item;
282c26c4f79Snicm 	sc.s = s;
2836f732176Snicm 	if (!detached)
2842077c06bSnicm 		sc.tc = c;
285c26c4f79Snicm 
286c26c4f79Snicm 	sc.name = args_get(args, 'n');
287d8b32369Snicm 	args_to_vector(args, &sc.argc, &sc.argv);
288c26c4f79Snicm 
289c26c4f79Snicm 	sc.idx = -1;
290c26c4f79Snicm 	sc.cwd = args_get(args, 'c');
291c26c4f79Snicm 
292c26c4f79Snicm 	sc.flags = 0;
293c26c4f79Snicm 
294c26c4f79Snicm 	if (spawn_window(&sc, &cause) == NULL) {
295c26c4f79Snicm 		session_destroy(s, 0, __func__);
296c26c4f79Snicm 		cmdq_error(item, "create window failed: %s", cause);
2977d053cf9Snicm 		free(cause);
298c26c4f79Snicm 		goto fail;
299311827fbSnicm 	}
300311827fbSnicm 
301dcdf1788Snicm 	/*
30201b2421eSnicm 	 * If a target session is given, this is to be part of a session group,
30301b2421eSnicm 	 * so add it to the group and synchronize.
30401b2421eSnicm 	 */
30506440b28Snicm 	if (group != NULL) {
30606440b28Snicm 		if (sg == NULL) {
30783ee10d2Snicm 			if (groupwith != NULL) {
30806440b28Snicm 				sg = session_group_new(groupwith->name);
30906440b28Snicm 				session_group_add(sg, groupwith);
31006440b28Snicm 			} else
31106440b28Snicm 				sg = session_group_new(group);
31206440b28Snicm 		}
31306440b28Snicm 		session_group_add(sg, s);
31401b2421eSnicm 		session_group_synchronize_to(s);
31558c1759dSnicm 		session_select(s, RB_MIN(winlinks, &s->windows)->idx);
31601b2421eSnicm 	}
317f7f49c14Snicm 	notify_session("session-created", s);
31801b2421eSnicm 
31901b2421eSnicm 	/*
320832f07f9Snicm 	 * Set the client to the new session. If a command client exists, it is
321832f07f9Snicm 	 * taking this session and needs to get MSG_READY and stay around.
322dcdf1788Snicm 	 */
323ca6e6a1fSnicm 	if (!detached) {
324cfef6bbbSnicm 		if (args_has(args, 'f'))
325cfef6bbbSnicm 			server_client_set_flags(c, args_get(args, 'f'));
3265b8ac713Snicm 		if (!already_attached) {
3275b8ac713Snicm 			if (~c->flags & CLIENT_CONTROL)
3285b8ac713Snicm 				proc_send(c->peer, MSG_READY, -1, NULL, 0);
3295b8ac713Snicm 		} else if (c->session != NULL)
330175d36ccSnicm 			c->last_session = c->session;
3311a773291Snicm 		server_client_set_session(c, s);
332823b6d6dSnicm 		if (~cmdq_get_flags(item) & CMDQ_STATE_REPEAT)
3330036f409Snicm 			server_client_set_key_table(c, NULL);
334311827fbSnicm 	}
335311827fbSnicm 
33615037bfcSnicm 	/* Print if requested. */
33715037bfcSnicm 	if (args_has(args, 'P')) {
33815037bfcSnicm 		if ((template = args_get(args, 'F')) == NULL)
33915037bfcSnicm 			template = NEW_SESSION_TEMPLATE;
340c8713798Snicm 		cp = format_single(item, template, c, s, s->curw, NULL);
34168e0a7f2Snicm 		cmdq_print(item, "%s", cp);
34215037bfcSnicm 		free(cp);
34315037bfcSnicm 	}
34415037bfcSnicm 
345c1e0bdabSnicm 	if (!detached)
346765b9a58Snicm 		c->flags |= CLIENT_ATTACHED;
347c1e0bdabSnicm 	if (!args_has(args, 'd'))
348040343aeSnicm 		cmd_find_from_session(current, s, 0);
349e1803d63Snicm 
3500772530eSnicm 	cmd_find_from_session(&fs, s, 0);
351844b9093Snicm 	cmdq_insert_hook(s, item, &fs, "after-new-session");
352765b9a58Snicm 
353*8e309d18Snicm 	if (cfg_finished)
354*8e309d18Snicm 		cfg_show_causes(s);
355*8e309d18Snicm 
3561693b10bSnicm 	if (sc.argv != NULL)
3571693b10bSnicm 		cmd_free_argv(sc.argc, sc.argv);
35889b49179Snicm 	free(cwd);
35989b49179Snicm 	free(newname);
360c27246d4Snicm 	free(prefix);
361175d36ccSnicm 	return (CMD_RETURN_NORMAL);
362e1803d63Snicm 
363c26c4f79Snicm fail:
3641693b10bSnicm 	if (sc.argv != NULL)
3651693b10bSnicm 		cmd_free_argv(sc.argc, sc.argv);
36689b49179Snicm 	free(cwd);
36789b49179Snicm 	free(newname);
368c27246d4Snicm 	free(prefix);
369e1803d63Snicm 	return (CMD_RETURN_ERROR);
370311827fbSnicm }
371