1 |
|
|
/* $OpenBSD: cmd-new-session.c,v 1.87 2016/03/05 07:47:52 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 |
|
|
enum cmd_retval cmd_new_session_exec(struct cmd *, struct cmd_q *); |
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 |
|
|
.tflag = CMD_SESSION_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 |
|
|
.tflag = CMD_SESSION, |
61 |
|
|
|
62 |
|
|
.flags = 0, |
63 |
|
|
.exec = cmd_new_session_exec |
64 |
|
|
}; |
65 |
|
|
|
66 |
|
|
enum cmd_retval |
67 |
|
|
cmd_new_session_exec(struct cmd *self, struct cmd_q *cmdq) |
68 |
|
|
{ |
69 |
|
|
struct args *args = self->args; |
70 |
|
|
struct client *c = cmdq->client; |
71 |
|
|
struct session *s, *as; |
72 |
|
|
struct session *groupwith = cmdq->state.tflag.s; |
73 |
|
|
struct window *w; |
74 |
|
|
struct environ *env; |
75 |
|
|
struct termios tio, *tiop; |
76 |
|
|
const char *newname, *target, *update, *errstr, *template; |
77 |
|
|
const char *path, *cwd, *to_free = NULL; |
78 |
|
|
char **argv, *cmd, *cause, *cp; |
79 |
|
|
int detached, already_attached, idx, argc; |
80 |
|
|
u_int sx, sy; |
81 |
|
|
struct format_tree *ft; |
82 |
|
|
struct environ_entry *envent; |
83 |
|
|
|
84 |
|
|
if (self->entry == &cmd_has_session_entry) { |
85 |
|
|
/* |
86 |
|
|
* cmd_prepare() will fail if the session cannot be found, |
87 |
|
|
* hence always return success here. |
88 |
|
|
*/ |
89 |
|
|
return (CMD_RETURN_NORMAL); |
90 |
|
|
} |
91 |
|
|
|
92 |
|
|
if (args_has(args, 't') && (args->argc != 0 || args_has(args, 'n'))) { |
93 |
|
|
cmdq_error(cmdq, "command or window name given with target"); |
94 |
|
|
return (CMD_RETURN_ERROR); |
95 |
|
|
} |
96 |
|
|
|
97 |
|
|
newname = args_get(args, 's'); |
98 |
|
|
if (newname != NULL) { |
99 |
|
|
if (!session_check_name(newname)) { |
100 |
|
|
cmdq_error(cmdq, "bad session name: %s", newname); |
101 |
|
|
return (CMD_RETURN_ERROR); |
102 |
|
|
} |
103 |
|
|
if ((as = session_find(newname)) != NULL) { |
104 |
|
|
if (args_has(args, 'A')) { |
105 |
|
|
/* |
106 |
|
|
* This cmdq is now destined for |
107 |
|
|
* attach-session. Because attach-session |
108 |
|
|
* will have already been prepared, copy this |
109 |
|
|
* session into its tflag so it can be used. |
110 |
|
|
*/ |
111 |
|
|
cmd_find_from_session(&cmdq->state.tflag, as); |
112 |
|
|
return (cmd_attach_session(cmdq, |
113 |
|
|
args_has(args, 'D'), 0, NULL, |
114 |
|
|
args_has(args, 'E'))); |
115 |
|
|
} |
116 |
|
|
cmdq_error(cmdq, "duplicate session: %s", newname); |
117 |
|
|
return (CMD_RETURN_ERROR); |
118 |
|
|
} |
119 |
|
|
} |
120 |
|
|
|
121 |
|
|
if ((target = args_get(args, 't')) != NULL) { |
122 |
|
|
if (groupwith == NULL) { |
123 |
|
|
cmdq_error(cmdq, "no such session: %s", target); |
124 |
|
|
goto error; |
125 |
|
|
} |
126 |
|
|
} else |
127 |
|
|
groupwith = NULL; |
128 |
|
|
|
129 |
|
|
/* Set -d if no client. */ |
130 |
|
|
detached = args_has(args, 'd'); |
131 |
|
|
if (c == NULL) |
132 |
|
|
detached = 1; |
133 |
|
|
|
134 |
|
|
/* Is this client already attached? */ |
135 |
|
|
already_attached = 0; |
136 |
|
|
if (c != NULL && c->session != NULL) |
137 |
|
|
already_attached = 1; |
138 |
|
|
|
139 |
|
|
/* Get the new session working directory. */ |
140 |
|
|
if (args_has(args, 'c')) { |
141 |
|
|
ft = format_create(cmdq, 0); |
142 |
|
|
format_defaults(ft, c, NULL, NULL, NULL); |
143 |
|
|
to_free = cwd = format_expand(ft, args_get(args, 'c')); |
144 |
|
|
format_free(ft); |
145 |
|
|
} else if (c != NULL && c->session == NULL && c->cwd != NULL) |
146 |
|
|
cwd = c->cwd; |
147 |
|
|
else |
148 |
|
|
cwd = "."; |
149 |
|
|
|
150 |
|
|
/* |
151 |
|
|
* If this is a new client, check for nesting and save the termios |
152 |
|
|
* settings (part of which is used for new windows in this session). |
153 |
|
|
* |
154 |
|
|
* tcgetattr() is used rather than using tty.tio since if the client is |
155 |
|
|
* detached, tty_open won't be called. It must be done before opening |
156 |
|
|
* the terminal as that calls tcsetattr() to prepare for tmux taking |
157 |
|
|
* over. |
158 |
|
|
*/ |
159 |
|
|
if (!detached && !already_attached && c->tty.fd != -1) { |
160 |
|
|
if (server_client_check_nested(cmdq->client)) { |
161 |
|
|
cmdq_error(cmdq, "sessions should be nested with care, " |
162 |
|
|
"unset $TMUX to force"); |
163 |
|
|
return (CMD_RETURN_ERROR); |
164 |
|
|
} |
165 |
|
|
if (tcgetattr(c->tty.fd, &tio) != 0) |
166 |
|
|
fatal("tcgetattr failed"); |
167 |
|
|
tiop = &tio; |
168 |
|
|
} else |
169 |
|
|
tiop = NULL; |
170 |
|
|
|
171 |
|
|
/* Open the terminal if necessary. */ |
172 |
|
|
if (!detached && !already_attached) { |
173 |
|
|
if (server_client_open(c, &cause) != 0) { |
174 |
|
|
cmdq_error(cmdq, "open terminal failed: %s", cause); |
175 |
|
|
free(cause); |
176 |
|
|
goto error; |
177 |
|
|
} |
178 |
|
|
} |
179 |
|
|
|
180 |
|
|
/* Find new session size. */ |
181 |
|
|
if (c != NULL) { |
182 |
|
|
sx = c->tty.sx; |
183 |
|
|
sy = c->tty.sy; |
184 |
|
|
} else { |
185 |
|
|
sx = 80; |
186 |
|
|
sy = 24; |
187 |
|
|
} |
188 |
|
|
if (detached && args_has(args, 'x')) { |
189 |
|
|
sx = strtonum(args_get(args, 'x'), 1, USHRT_MAX, &errstr); |
190 |
|
|
if (errstr != NULL) { |
191 |
|
|
cmdq_error(cmdq, "width %s", errstr); |
192 |
|
|
goto error; |
193 |
|
|
} |
194 |
|
|
} |
195 |
|
|
if (detached && args_has(args, 'y')) { |
196 |
|
|
sy = strtonum(args_get(args, 'y'), 1, USHRT_MAX, &errstr); |
197 |
|
|
if (errstr != NULL) { |
198 |
|
|
cmdq_error(cmdq, "height %s", errstr); |
199 |
|
|
goto error; |
200 |
|
|
} |
201 |
|
|
} |
202 |
|
|
if (sy > 0 && options_get_number(global_s_options, "status")) |
203 |
|
|
sy--; |
204 |
|
|
if (sx == 0) |
205 |
|
|
sx = 1; |
206 |
|
|
if (sy == 0) |
207 |
|
|
sy = 1; |
208 |
|
|
|
209 |
|
|
/* Figure out the command for the new window. */ |
210 |
|
|
argc = -1; |
211 |
|
|
argv = NULL; |
212 |
|
|
if (!args_has(args, 't') && args->argc != 0) { |
213 |
|
|
argc = args->argc; |
214 |
|
|
argv = args->argv; |
215 |
|
|
} else if (groupwith == NULL) { |
216 |
|
|
cmd = options_get_string(global_s_options, "default-command"); |
217 |
|
|
if (cmd != NULL && *cmd != '\0') { |
218 |
|
|
argc = 1; |
219 |
|
|
argv = &cmd; |
220 |
|
|
} else { |
221 |
|
|
argc = 0; |
222 |
|
|
argv = NULL; |
223 |
|
|
} |
224 |
|
|
} |
225 |
|
|
|
226 |
|
|
path = NULL; |
227 |
|
|
if (c != NULL && c->session == NULL) |
228 |
|
|
envent = environ_find(c->environ, "PATH"); |
229 |
|
|
else |
230 |
|
|
envent = environ_find(global_environ, "PATH"); |
231 |
|
|
if (envent != NULL) |
232 |
|
|
path = envent->value; |
233 |
|
|
|
234 |
|
|
/* Construct the environment. */ |
235 |
|
|
env = environ_create(); |
236 |
|
|
if (c != NULL && !args_has(args, 'E')) { |
237 |
|
|
update = options_get_string(global_s_options, |
238 |
|
|
"update-environment"); |
239 |
|
|
environ_update(update, c->environ, env); |
240 |
|
|
} |
241 |
|
|
|
242 |
|
|
/* Create the new session. */ |
243 |
|
|
idx = -1 - options_get_number(global_s_options, "base-index"); |
244 |
|
|
s = session_create(newname, argc, argv, path, cwd, env, tiop, idx, sx, |
245 |
|
|
sy, &cause); |
246 |
|
|
environ_free(env); |
247 |
|
|
if (s == NULL) { |
248 |
|
|
cmdq_error(cmdq, "create session failed: %s", cause); |
249 |
|
|
free(cause); |
250 |
|
|
goto error; |
251 |
|
|
} |
252 |
|
|
|
253 |
|
|
/* Set the initial window name if one given. */ |
254 |
|
|
if (argc >= 0 && args_has(args, 'n')) { |
255 |
|
|
w = s->curw->window; |
256 |
|
|
window_set_name(w, args_get(args, 'n')); |
257 |
|
|
options_set_number(w->options, "automatic-rename", 0); |
258 |
|
|
} |
259 |
|
|
|
260 |
|
|
/* |
261 |
|
|
* If a target session is given, this is to be part of a session group, |
262 |
|
|
* so add it to the group and synchronize. |
263 |
|
|
*/ |
264 |
|
|
if (groupwith != NULL) { |
265 |
|
|
session_group_add(groupwith, s); |
266 |
|
|
session_group_synchronize_to(s); |
267 |
|
|
session_select(s, RB_MIN(winlinks, &s->windows)->idx); |
268 |
|
|
} |
269 |
|
|
|
270 |
|
|
/* |
271 |
|
|
* Set the client to the new session. If a command client exists, it is |
272 |
|
|
* taking this session and needs to get MSG_READY and stay around. |
273 |
|
|
*/ |
274 |
|
|
if (!detached) { |
275 |
|
|
if (!already_attached) { |
276 |
|
|
if (~c->flags & CLIENT_CONTROL) |
277 |
|
|
proc_send(c->peer, MSG_READY, -1, NULL, 0); |
278 |
|
|
} else if (c->session != NULL) |
279 |
|
|
c->last_session = c->session; |
280 |
|
|
c->session = s; |
281 |
|
|
server_client_set_key_table(c, NULL); |
282 |
|
|
status_timer_start(c); |
283 |
|
|
notify_attached_session_changed(c); |
284 |
|
|
session_update_activity(s, NULL); |
285 |
|
|
gettimeofday(&s->last_attached_time, NULL); |
286 |
|
|
server_redraw_client(c); |
287 |
|
|
} |
288 |
|
|
recalculate_sizes(); |
289 |
|
|
server_update_socket(); |
290 |
|
|
|
291 |
|
|
/* |
292 |
|
|
* If there are still configuration file errors to display, put the new |
293 |
|
|
* session's current window into more mode and display them now. |
294 |
|
|
*/ |
295 |
|
|
if (cfg_finished) |
296 |
|
|
cfg_show_causes(s); |
297 |
|
|
|
298 |
|
|
/* Print if requested. */ |
299 |
|
|
if (args_has(args, 'P')) { |
300 |
|
|
if ((template = args_get(args, 'F')) == NULL) |
301 |
|
|
template = NEW_SESSION_TEMPLATE; |
302 |
|
|
|
303 |
|
|
ft = format_create(cmdq, 0); |
304 |
|
|
format_defaults(ft, c, s, NULL, NULL); |
305 |
|
|
|
306 |
|
|
cp = format_expand(ft, template); |
307 |
|
|
cmdq_print(cmdq, "%s", cp); |
308 |
|
|
free(cp); |
309 |
|
|
|
310 |
|
|
format_free(ft); |
311 |
|
|
} |
312 |
|
|
|
313 |
|
|
if (!detached) |
314 |
|
|
cmdq->client_exit = 0; |
315 |
|
|
|
316 |
|
|
if (to_free != NULL) |
317 |
|
|
free((void *)to_free); |
318 |
|
|
return (CMD_RETURN_NORMAL); |
319 |
|
|
|
320 |
|
|
error: |
321 |
|
|
if (to_free != NULL) |
322 |
|
|
free((void *)to_free); |
323 |
|
|
return (CMD_RETURN_ERROR); |
324 |
|
|
} |