xref: /netbsd-src/external/bsd/tmux/dist/cmd-new-session.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /* $OpenBSD$ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
5  *
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.
9  *
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.
17  */
18 
19 #include <sys/types.h>
20 
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <termios.h>
26 #include <unistd.h>
27 
28 #include "tmux.h"
29 
30 /*
31  * Create a new session and attach to the current terminal unless -d is given.
32  */
33 
34 #define NEW_SESSION_TEMPLATE "#{session_name}:"
35 
36 enum cmd_retval	 cmd_new_session_exec(struct cmd *, struct cmd_q *);
37 
38 const struct cmd_entry cmd_new_session_entry = {
39 	"new-session", "new",
40 	"Ac:dDEF:n:Ps:t:x:y:", 0, -1,
41 	"[-AdDEP] [-c start-directory] [-F format] [-n window-name] "
42 	"[-s session-name] " CMD_TARGET_SESSION_USAGE " [-x width] "
43 	"[-y height] [command]",
44 	CMD_STARTSERVER,
45 	cmd_new_session_exec
46 };
47 
48 const struct cmd_entry cmd_has_session_entry = {
49 	"has-session", "has",
50 	"t:", 0, 0,
51 	CMD_TARGET_SESSION_USAGE,
52 	0,
53 	cmd_new_session_exec
54 };
55 
56 enum cmd_retval
57 cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq)
58 {
59 	struct args		*args = self->args;
60 	struct client		*c = cmdq->client, *c0;
61 	struct session		*s, *groupwith;
62 	struct window		*w;
63 	struct environ		 env;
64 	struct termios		 tio, *tiop;
65 	const char		*newname, *target, *update, *errstr, *template;
66 	const char		*path;
67 	char		       **argv, *cmd, *cause, *cp;
68 	int			 detached, already_attached, idx, cwd, fd = -1;
69 	int			 argc;
70 	u_int			 sx, sy;
71 	struct format_tree	*ft;
72 	struct environ_entry	*envent;
73 
74 	if (self->entry == &cmd_has_session_entry) {
75 		if (cmd_find_session(cmdq, args_get(args, 't'), 0) == NULL)
76 			return (CMD_RETURN_ERROR);
77 		return (CMD_RETURN_NORMAL);
78 	}
79 
80 	if (args_has(args, 't') && (args->argc != 0 || args_has(args, 'n'))) {
81 		cmdq_error(cmdq, "command or window name given with target");
82 		return (CMD_RETURN_ERROR);
83 	}
84 
85 	newname = args_get(args, 's');
86 	if (newname != NULL) {
87 		if (!session_check_name(newname)) {
88 			cmdq_error(cmdq, "bad session name: %s", newname);
89 			return (CMD_RETURN_ERROR);
90 		}
91 		if (session_find(newname) != NULL) {
92 			if (args_has(args, 'A')) {
93 				return (cmd_attach_session(cmdq, newname,
94 				    args_has(args, 'D'), 0, NULL,
95 				    args_has(args, 'E')));
96 			}
97 			cmdq_error(cmdq, "duplicate session: %s", newname);
98 			return (CMD_RETURN_ERROR);
99 		}
100 	}
101 
102 	target = args_get(args, 't');
103 	if (target != NULL) {
104 		groupwith = cmd_find_session(cmdq, target, 0);
105 		if (groupwith == NULL)
106 			return (CMD_RETURN_ERROR);
107 	} else
108 		groupwith = NULL;
109 
110 	/* Set -d if no client. */
111 	detached = args_has(args, 'd');
112 	if (c == NULL)
113 		detached = 1;
114 
115 	/* Is this client already attached? */
116 	already_attached = 0;
117 	if (c != NULL && c->session != NULL)
118 		already_attached = 1;
119 
120 	/* Get the new session working directory. */
121 	if (args_has(args, 'c')) {
122 		ft = format_create();
123 		format_defaults(ft, cmd_find_client(cmdq, NULL, 1), NULL, NULL,
124 		    NULL);
125 		cp = format_expand(ft, args_get(args, 'c'));
126 		format_free(ft);
127 
128 		if (cp != NULL && *cp != '\0') {
129 			fd = open(cp, O_RDONLY|O_DIRECTORY);
130 			free(cp);
131 			if (fd == -1) {
132 				cmdq_error(cmdq, "bad working directory: %s",
133 				    strerror(errno));
134 				return (CMD_RETURN_ERROR);
135 			}
136 		} else if (cp != NULL)
137 			free(cp);
138 		cwd = fd;
139 	} else if (c != NULL && c->session == NULL)
140 		cwd = c->cwd;
141 	else if ((c0 = cmd_find_client(cmdq, NULL, 1)) != NULL)
142 		cwd = c0->session->cwd;
143 	else {
144 		fd = open(".", O_RDONLY);
145 		cwd = fd;
146 	}
147 
148 	/*
149 	 * If this is a new client, check for nesting and save the termios
150 	 * settings (part of which is used for new windows in this session).
151 	 *
152 	 * tcgetattr() is used rather than using tty.tio since if the client is
153 	 * detached, tty_open won't be called. It must be done before opening
154 	 * the terminal as that calls tcsetattr() to prepare for tmux taking
155 	 * over.
156 	 */
157 	if (!detached && !already_attached && c->tty.fd != -1) {
158 		if (server_client_check_nested(cmdq->client)) {
159 			cmdq_error(cmdq, "sessions should be nested with care, "
160 			    "unset $TMUX to force");
161 			return (CMD_RETURN_ERROR);
162 		}
163 		if (tcgetattr(c->tty.fd, &tio) != 0)
164 			fatal("tcgetattr failed");
165 		tiop = &tio;
166 	} else
167 		tiop = NULL;
168 
169 	/* Open the terminal if necessary. */
170 	if (!detached && !already_attached) {
171 		if (server_client_open(c, &cause) != 0) {
172 			cmdq_error(cmdq, "open terminal failed: %s", cause);
173 			free(cause);
174 			goto error;
175 		}
176 	}
177 
178 	/* Find new session size. */
179 	if (c != NULL) {
180 		sx = c->tty.sx;
181 		sy = c->tty.sy;
182 	} else {
183 		sx = 80;
184 		sy = 24;
185 	}
186 	if (detached && args_has(args, 'x')) {
187 		sx = strtonum(args_get(args, 'x'), 1, USHRT_MAX, &errstr);
188 		if (errstr != NULL) {
189 			cmdq_error(cmdq, "width %s", errstr);
190 			goto error;
191 		}
192 	}
193 	if (detached && args_has(args, 'y')) {
194 		sy = strtonum(args_get(args, 'y'), 1, USHRT_MAX, &errstr);
195 		if (errstr != NULL) {
196 			cmdq_error(cmdq, "height %s", errstr);
197 			goto error;
198 		}
199 	}
200 	if (sy > 0 && options_get_number(&global_s_options, "status"))
201 		sy--;
202 	if (sx == 0)
203 		sx = 1;
204 	if (sy == 0)
205 		sy = 1;
206 
207 	/* Figure out the command for the new window. */
208 	argc = -1;
209 	argv = NULL;
210 	if (target == NULL && args->argc != 0) {
211 		argc = args->argc;
212 		argv = args->argv;
213 	} else if (target == NULL) {
214 		cmd = options_get_string(&global_s_options, "default-command");
215 		if (cmd != NULL && *cmd != '\0') {
216 			argc = 1;
217 			argv = &cmd;
218 		} else {
219 			argc = 0;
220 			argv = NULL;
221 		}
222 	}
223 
224 	path = NULL;
225 	if (c != NULL && c->session == NULL)
226 		envent = environ_find(&c->environ, "PATH");
227 	else
228 		envent = environ_find(&global_environ, "PATH");
229 	if (envent != NULL)
230 		path = envent->value;
231 
232 	/* Construct the environment. */
233 	environ_init(&env);
234 	if (c != NULL && !args_has(args, 'E')) {
235 		update = options_get_string(&global_s_options,
236 		    "update-environment");
237 		environ_update(update, &c->environ, &env);
238 	}
239 
240 	/* Create the new session. */
241 	idx = -1 - options_get_number(&global_s_options, "base-index");
242 	s = session_create(newname, argc, argv, path, cwd, &env, tiop, idx, sx,
243 	    sy, &cause);
244 	if (s == NULL) {
245 		cmdq_error(cmdq, "create session failed: %s", cause);
246 		free(cause);
247 		goto error;
248 	}
249 	environ_free(&env);
250 
251 	/* Set the initial window name if one given. */
252 	if (argc >= 0 && args_has(args, 'n')) {
253 		w = s->curw->window;
254 		window_set_name(w, args_get(args, 'n'));
255 		options_set_number(&w->options, "automatic-rename", 0);
256 	}
257 
258 	/*
259 	 * If a target session is given, this is to be part of a session group,
260 	 * so add it to the group and synchronize.
261 	 */
262 	if (groupwith != NULL) {
263 		session_group_add(groupwith, s);
264 		session_group_synchronize_to(s);
265 		session_select(s, RB_MIN(winlinks, &s->windows)->idx);
266 	}
267 
268 	/*
269 	 * Set the client to the new session. If a command client exists, it is
270 	 * taking this session and needs to get MSG_READY and stay around.
271 	 */
272 	if (!detached) {
273 		if (!already_attached)
274 			server_write_ready(c);
275 		else if (c->session != NULL)
276 			c->last_session = c->session;
277 		c->session = s;
278 		status_timer_start(c);
279 		notify_attached_session_changed(c);
280 		session_update_activity(s, NULL);
281 		gettimeofday(&s->last_attached_time, NULL);
282 		server_redraw_client(c);
283 	}
284 	recalculate_sizes();
285 	server_update_socket();
286 
287 	/*
288 	 * If there are still configuration file errors to display, put the new
289 	 * session's current window into more mode and display them now.
290 	 */
291 	if (cfg_finished)
292 		cfg_show_causes(s);
293 
294 	/* Print if requested. */
295 	if (args_has(args, 'P')) {
296 		if ((template = args_get(args, 'F')) == NULL)
297 			template = NEW_SESSION_TEMPLATE;
298 
299 		ft = format_create();
300 		format_defaults(ft, cmd_find_client(cmdq, NULL, 1), s, NULL,
301 		    NULL);
302 
303 		cp = format_expand(ft, template);
304 		cmdq_print(cmdq, "%s", cp);
305 		free(cp);
306 
307 		format_free(ft);
308 	}
309 
310 	if (!detached)
311 		cmdq->client_exit = 0;
312 
313 	if (fd != -1)
314 		close(fd);
315 	return (CMD_RETURN_NORMAL);
316 
317 error:
318 	if (fd != -1)
319 		close(fd);
320 	return (CMD_RETURN_ERROR);
321 }
322