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 |
|
|
} |