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