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