Line | Count | Source |
1 | | /* $OpenBSD: tmux.c,v 1.225 2026/09/25 13:44:49 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 | | #include <sys/stat.h> |
21 | | #include <sys/utsname.h> |
22 | | |
23 | | #include <errno.h> |
24 | | #include <fcntl.h> |
25 | | #include <langinfo.h> |
26 | | #include <locale.h> |
27 | | #include <pwd.h> |
28 | | #include <signal.h> |
29 | | #include <stdlib.h> |
30 | | #include <string.h> |
31 | | #include <time.h> |
32 | | #include <unistd.h> |
33 | | |
34 | | #include "tmux.h" |
35 | | |
36 | | struct options *global_options; /* server options */ |
37 | | struct options *global_s_options; /* session options */ |
38 | | struct options *global_w_options; /* window options */ |
39 | | struct environ *global_environ; |
40 | | |
41 | | struct timeval start_time; |
42 | | const char *socket_path; |
43 | | int ptm_fd = -1; |
44 | | const char *shell_command; |
45 | | |
46 | | static __dead void usage(int); |
47 | | static char *make_label(const char *, char **); |
48 | | |
49 | | static int areshell(const char *); |
50 | | static const char *getshell(void); |
51 | | |
52 | | #ifdef ASAN |
53 | | __attribute__((used)) const char *__asan_default_options(void); |
54 | | __attribute__((used)) const char * |
55 | | __asan_default_options(void) |
56 | | { |
57 | | return ( |
58 | | "abort_on_error=1:" |
59 | | "halt_on_error=1:" |
60 | | "detect_leaks=0:" |
61 | | "detect_stack_use_after_return=1:" |
62 | | "strict_string_checks=1:" |
63 | | "check_initialization_order=1:" |
64 | | "log_path=/tmp/tmux-asan:" |
65 | | "log_exe_name=1" |
66 | | ); |
67 | | } |
68 | | #endif |
69 | | |
70 | | static __dead void |
71 | | usage(int status) |
72 | 0 | { |
73 | 0 | fprintf(status ? stderr : stdout, |
74 | 0 | "usage: %s [-2CDhlNuVv] [-c shell-command] [-f file] [-L socket-name]\n" |
75 | 0 | " [-S socket-path] [-T features] [command [flags]]\n", |
76 | 0 | getprogname()); |
77 | 0 | exit(status); |
78 | 0 | } |
79 | | |
80 | | static const char * |
81 | | getshell(void) |
82 | 0 | { |
83 | 0 | struct passwd *pw; |
84 | 0 | const char *shell; |
85 | |
|
86 | 0 | shell = getenv("SHELL"); |
87 | 0 | if (checkshell(shell)) |
88 | 0 | return (shell); |
89 | | |
90 | 0 | pw = getpwuid(getuid()); |
91 | 0 | if (pw != NULL && checkshell(pw->pw_shell)) |
92 | 0 | return (pw->pw_shell); |
93 | | |
94 | 0 | return (_PATH_BSHELL); |
95 | 0 | } |
96 | | |
97 | | int |
98 | | checkshell(const char *shell) |
99 | 0 | { |
100 | 0 | if (shell == NULL || *shell != '/') |
101 | 0 | return (0); |
102 | 0 | if (areshell(shell)) |
103 | 0 | return (0); |
104 | 0 | if (access(shell, X_OK) != 0) |
105 | 0 | return (0); |
106 | 0 | return (1); |
107 | 0 | } |
108 | | |
109 | | static int |
110 | | areshell(const char *shell) |
111 | 0 | { |
112 | 0 | const char *progname, *ptr; |
113 | |
|
114 | 0 | if ((ptr = strrchr(shell, '/')) != NULL) |
115 | 0 | ptr++; |
116 | 0 | else |
117 | 0 | ptr = shell; |
118 | 0 | progname = getprogname(); |
119 | 0 | if (*progname == '-') |
120 | 0 | progname++; |
121 | 0 | if (strcmp(ptr, progname) == 0) |
122 | 0 | return (1); |
123 | 0 | return (0); |
124 | 0 | } |
125 | | |
126 | | static char * |
127 | | expand_path(const char *path, const char *home) |
128 | 0 | { |
129 | 0 | char *expanded, *name; |
130 | 0 | const char *end; |
131 | 0 | struct environ_entry *value; |
132 | |
|
133 | 0 | if (strncmp(path, "~/", 2) == 0) { |
134 | 0 | if (home == NULL) |
135 | 0 | return (NULL); |
136 | 0 | xasprintf(&expanded, "%s%s", home, path + 1); |
137 | 0 | return (expanded); |
138 | 0 | } |
139 | | |
140 | 0 | if (*path == '$') { |
141 | 0 | end = strchr(path, '/'); |
142 | 0 | if (end == NULL) |
143 | 0 | name = xstrdup(path + 1); |
144 | 0 | else |
145 | 0 | name = xstrndup(path + 1, end - path - 1); |
146 | 0 | value = environ_find(global_environ, name); |
147 | 0 | free(name); |
148 | 0 | if (value == NULL) |
149 | 0 | return (NULL); |
150 | 0 | if (end == NULL) |
151 | 0 | end = ""; |
152 | 0 | xasprintf(&expanded, "%s%s", value->value, end); |
153 | 0 | return (expanded); |
154 | 0 | } |
155 | | |
156 | 0 | return (xstrdup(path)); |
157 | 0 | } |
158 | | |
159 | | static void |
160 | | expand_paths(const char *s, char ***paths, u_int *n, int no_realpath) |
161 | 0 | { |
162 | 0 | const char *home = find_home(); |
163 | 0 | char *copy, *next, *tmp, resolved[PATH_MAX], *expanded; |
164 | 0 | char *path; |
165 | 0 | u_int i; |
166 | |
|
167 | 0 | *paths = NULL; |
168 | 0 | *n = 0; |
169 | |
|
170 | 0 | copy = tmp = xstrdup(s); |
171 | 0 | while ((next = strsep(&tmp, ":")) != NULL) { |
172 | 0 | expanded = expand_path(next, home); |
173 | 0 | if (expanded == NULL) { |
174 | 0 | log_debug("%s: invalid path: %s", __func__, next); |
175 | 0 | continue; |
176 | 0 | } |
177 | 0 | if (no_realpath) |
178 | 0 | path = expanded; |
179 | 0 | else { |
180 | 0 | if (realpath(expanded, resolved) == NULL) { |
181 | 0 | log_debug("%s: realpath(\"%s\") failed: %s", |
182 | 0 | __func__, expanded, strerror(errno)); |
183 | 0 | free(expanded); |
184 | 0 | continue; |
185 | 0 | } |
186 | 0 | path = xstrdup(resolved); |
187 | 0 | free(expanded); |
188 | 0 | } |
189 | 0 | for (i = 0; i < *n; i++) { |
190 | 0 | if (strcmp(path, (*paths)[i]) == 0) |
191 | 0 | break; |
192 | 0 | } |
193 | 0 | if (i != *n) { |
194 | 0 | log_debug("%s: duplicate path: %s", __func__, path); |
195 | 0 | free(path); |
196 | 0 | continue; |
197 | 0 | } |
198 | 0 | *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths); |
199 | 0 | (*paths)[(*n)++] = path; |
200 | 0 | } |
201 | 0 | free(copy); |
202 | 0 | } |
203 | | |
204 | | static int |
205 | | check_socket_path(const char *path, char **cause) |
206 | 0 | { |
207 | 0 | char *copy, *next, *tmp; |
208 | |
|
209 | 0 | if (*path != '/') { |
210 | 0 | xasprintf(cause, "socket directory %s is not an absolute path", |
211 | 0 | path); |
212 | 0 | return (0); |
213 | 0 | } |
214 | 0 | copy = tmp = xstrdup(path); |
215 | 0 | while ((next = strsep(&tmp, "/")) != NULL) { |
216 | 0 | if (strcmp(next, "..") == 0) { |
217 | 0 | xasprintf(cause, "socket directory %s contains ..", |
218 | 0 | path); |
219 | 0 | free(copy); |
220 | 0 | return (0); |
221 | 0 | } |
222 | 0 | } |
223 | 0 | free(copy); |
224 | 0 | return (1); |
225 | 0 | } |
226 | | |
227 | | static char * |
228 | | make_label(const char *label, char **cause) |
229 | 0 | { |
230 | 0 | char **paths, *path = NULL, *base, resolved[PATH_MAX]; |
231 | 0 | u_int i, n; |
232 | 0 | struct stat sb; |
233 | 0 | uid_t uid; |
234 | |
|
235 | 0 | *cause = NULL; |
236 | 0 | if (label == NULL) |
237 | 0 | label = "default"; |
238 | 0 | uid = getuid(); |
239 | | |
240 | | /* |
241 | | * An unset variable has already been dropped and an empty one is |
242 | | * skipped, but anything else must be an existing absolute path with no |
243 | | * ".." or it is an error. |
244 | | */ |
245 | 0 | expand_paths(TMUX_SOCK, &paths, &n, 1); |
246 | 0 | for (i = 0; i < n; i++) { |
247 | 0 | if (*paths[i] == '\0') |
248 | 0 | continue; |
249 | 0 | if (!check_socket_path(paths[i], cause)) |
250 | 0 | break; |
251 | 0 | if (realpath(paths[i], resolved) == NULL) { |
252 | 0 | xasprintf(cause, |
253 | 0 | "couldn't resolve socket directory %s (%s)", |
254 | 0 | paths[i], strerror(errno)); |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | path = xstrdup(resolved); |
258 | 0 | break; |
259 | 0 | } |
260 | 0 | for (i = 0; i < n; i++) |
261 | 0 | free(paths[i]); |
262 | 0 | free(paths); |
263 | 0 | if (path == NULL) { |
264 | 0 | if (*cause == NULL) |
265 | 0 | xasprintf(cause, "no suitable socket path"); |
266 | 0 | return (NULL); |
267 | 0 | } |
268 | | |
269 | 0 | xasprintf(&base, "%s/tmux-%ld", path, (long)uid); |
270 | 0 | free(path); |
271 | 0 | if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) { |
272 | 0 | xasprintf(cause, "couldn't create directory %s (%s)", base, |
273 | 0 | strerror(errno)); |
274 | 0 | goto fail; |
275 | 0 | } |
276 | 0 | if (lstat(base, &sb) != 0) { |
277 | 0 | xasprintf(cause, "couldn't read directory %s (%s)", base, |
278 | 0 | strerror(errno)); |
279 | 0 | goto fail; |
280 | 0 | } |
281 | 0 | if (!S_ISDIR(sb.st_mode)) { |
282 | 0 | xasprintf(cause, "%s is not a directory", base); |
283 | 0 | goto fail; |
284 | 0 | } |
285 | 0 | if (sb.st_uid != uid || (sb.st_mode & TMUX_SOCK_PERM) != 0) { |
286 | 0 | xasprintf(cause, "directory %s has unsafe permissions", base); |
287 | 0 | goto fail; |
288 | 0 | } |
289 | 0 | xasprintf(&path, "%s/%s", base, label); |
290 | 0 | free(base); |
291 | 0 | return (path); |
292 | | |
293 | 0 | fail: |
294 | 0 | free(base); |
295 | 0 | return (NULL); |
296 | 0 | } |
297 | | |
298 | | char * |
299 | | shell_argv0(const char *shell, int is_login) |
300 | 0 | { |
301 | 0 | const char *slash, *name; |
302 | 0 | char *argv0; |
303 | |
|
304 | 0 | slash = strrchr(shell, '/'); |
305 | 0 | if (slash != NULL && slash[1] != '\0') |
306 | 0 | name = slash + 1; |
307 | 0 | else |
308 | 0 | name = shell; |
309 | 0 | if (is_login) |
310 | 0 | xasprintf(&argv0, "-%s", name); |
311 | 0 | else |
312 | 0 | xasprintf(&argv0, "%s", name); |
313 | 0 | return (argv0); |
314 | 0 | } |
315 | | |
316 | | void |
317 | | setblocking(int fd, int state) |
318 | 0 | { |
319 | 0 | int mode; |
320 | |
|
321 | 0 | if ((mode = fcntl(fd, F_GETFL)) != -1) { |
322 | 0 | if (!state) |
323 | 0 | mode |= O_NONBLOCK; |
324 | 0 | else |
325 | 0 | mode &= ~O_NONBLOCK; |
326 | 0 | fcntl(fd, F_SETFL, mode); |
327 | 0 | } |
328 | 0 | } |
329 | | |
330 | | uint64_t |
331 | | get_timer(void) |
332 | 48.5k | { |
333 | 48.5k | struct timespec ts; |
334 | | |
335 | | /* |
336 | | * We want a timestamp in milliseconds suitable for time measurement, |
337 | | * so prefer the monotonic clock. |
338 | | */ |
339 | 48.5k | if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) |
340 | 0 | clock_gettime(CLOCK_REALTIME, &ts); |
341 | 48.5k | return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL)); |
342 | 48.5k | } |
343 | | |
344 | | char * |
345 | | clean_name(const char *name, int untrusted) |
346 | 14.8k | { |
347 | 14.8k | char *copy, *cp, *new_name; |
348 | | |
349 | 14.8k | if (!utf8_isvalid(name)) |
350 | 404 | return (NULL); |
351 | 14.4k | copy = xstrdup(name); |
352 | 169k | for (cp = copy; *cp != '\0'; cp++) { |
353 | 154k | if (untrusted && cp[0] == '#' && cp[1] == '(') |
354 | 197 | *cp = '_'; |
355 | 154k | } |
356 | 14.4k | utf8_stravis(&new_name, copy, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); |
357 | 14.4k | free(copy); |
358 | 14.4k | return (new_name); |
359 | 14.8k | } |
360 | | |
361 | | int |
362 | | check_name(const char *name) |
363 | 0 | { |
364 | 0 | if (!utf8_isvalid(name)) |
365 | 0 | return (0); |
366 | 0 | return (1); |
367 | 0 | } |
368 | | |
369 | | const char * |
370 | | sig2name(int signo) |
371 | 0 | { |
372 | 0 | static char s[11]; |
373 | |
|
374 | | #ifdef HAVE_SYS_SIGNAME |
375 | | if (signo > 0 && signo < NSIG) |
376 | | return (sys_signame[signo]); |
377 | | #endif |
378 | 0 | xsnprintf(s, sizeof s, "%d", signo); |
379 | 0 | return (s); |
380 | 0 | } |
381 | | |
382 | | const char * |
383 | | find_cwd(void) |
384 | 0 | { |
385 | 0 | char resolved1[PATH_MAX], resolved2[PATH_MAX]; |
386 | 0 | static char cwd[PATH_MAX]; |
387 | 0 | const char *pwd; |
388 | |
|
389 | 0 | if (getcwd(cwd, sizeof cwd) == NULL) |
390 | 0 | return (NULL); |
391 | 0 | if ((pwd = getenv("PWD")) == NULL || *pwd == '\0') |
392 | 0 | return (cwd); |
393 | | |
394 | | /* |
395 | | * We want to use PWD so that symbolic links are maintained, |
396 | | * but only if it matches the actual working directory. |
397 | | */ |
398 | 0 | if (realpath(pwd, resolved1) == NULL) |
399 | 0 | return (cwd); |
400 | 0 | if (realpath(cwd, resolved2) == NULL) |
401 | 0 | return (cwd); |
402 | 0 | if (strcmp(resolved1, resolved2) != 0) |
403 | 0 | return (cwd); |
404 | 0 | return (pwd); |
405 | 0 | } |
406 | | |
407 | | const char * |
408 | | find_home(void) |
409 | 0 | { |
410 | 0 | struct passwd *pw; |
411 | 0 | static const char *home; |
412 | |
|
413 | 0 | if (home != NULL) |
414 | 0 | return (home); |
415 | | |
416 | 0 | home = getenv("HOME"); |
417 | 0 | if (home == NULL || *home == '\0') { |
418 | 0 | pw = getpwuid(getuid()); |
419 | 0 | if (pw != NULL) |
420 | 0 | home = xstrdup(pw->pw_dir); |
421 | 0 | else |
422 | 0 | home = NULL; |
423 | 0 | } |
424 | |
|
425 | 0 | return (home); |
426 | 0 | } |
427 | | |
428 | | const char * |
429 | | getversion(void) |
430 | 266 | { |
431 | 266 | return (TMUX_VERSION); |
432 | 266 | } |
433 | | |
434 | | int |
435 | | main(int argc, char **argv) |
436 | 0 | { |
437 | 0 | char *path = NULL, *label = NULL; |
438 | 0 | char *cause, **var; |
439 | 0 | const char *s, *cwd; |
440 | 0 | int opt, keys, feat = 0, fflag = 0; |
441 | 0 | uint64_t flags = 0; |
442 | 0 | const struct options_table_entry *oe; |
443 | 0 | u_int i; |
444 | |
|
445 | 0 | if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL && |
446 | 0 | setlocale(LC_CTYPE, "C.UTF-8") == NULL) { |
447 | 0 | if (setlocale(LC_CTYPE, "") == NULL) |
448 | 0 | errx(1, "invalid LC_ALL, LC_CTYPE or LANG"); |
449 | 0 | s = nl_langinfo(CODESET); |
450 | 0 | if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0) |
451 | 0 | errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s); |
452 | 0 | } |
453 | |
|
454 | 0 | setlocale(LC_TIME, ""); |
455 | 0 | tzset(); |
456 | |
|
457 | 0 | if (**argv == '-') |
458 | 0 | flags = CLIENT_LOGIN; |
459 | |
|
460 | 0 | global_environ = environ_create(); |
461 | 0 | for (var = environ; *var != NULL; var++) |
462 | 0 | environ_put(global_environ, *var, 0); |
463 | 0 | if ((cwd = find_cwd()) != NULL) |
464 | 0 | environ_set(global_environ, "PWD", 0, "%s", cwd); |
465 | 0 | expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1); |
466 | |
|
467 | 0 | while ((opt = getopt(argc, argv, "2c:CDdf:hlL:NqS:T:uUvV")) != -1) { |
468 | 0 | switch (opt) { |
469 | 0 | case '2': |
470 | 0 | tty_parse_features("256", ":,", &feat, NULL); |
471 | 0 | break; |
472 | 0 | case 'c': |
473 | 0 | shell_command = optarg; |
474 | 0 | break; |
475 | 0 | case 'D': |
476 | 0 | flags |= CLIENT_NOFORK; |
477 | 0 | break; |
478 | 0 | case 'C': |
479 | 0 | if (flags & CLIENT_CONTROL) |
480 | 0 | flags |= CLIENT_CONTROLCONTROL; |
481 | 0 | else |
482 | 0 | flags |= CLIENT_CONTROL; |
483 | 0 | break; |
484 | 0 | case 'f': |
485 | 0 | if (!fflag) { |
486 | 0 | fflag = 1; |
487 | 0 | for (i = 0; i < cfg_nfiles; i++) |
488 | 0 | free(cfg_files[i]); |
489 | 0 | cfg_nfiles = 0; |
490 | 0 | } |
491 | 0 | cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1, |
492 | 0 | sizeof *cfg_files); |
493 | 0 | cfg_files[cfg_nfiles++] = xstrdup(optarg); |
494 | 0 | cfg_quiet = 0; |
495 | 0 | break; |
496 | 0 | case 'h': |
497 | 0 | usage(0); |
498 | 0 | case 'V': |
499 | 0 | printf("tmux %s\n", getversion()); |
500 | 0 | exit(0); |
501 | 0 | case 'l': |
502 | 0 | flags |= CLIENT_LOGIN; |
503 | 0 | break; |
504 | 0 | case 'L': |
505 | 0 | free(label); |
506 | 0 | label = xstrdup(optarg); |
507 | 0 | break; |
508 | 0 | case 'N': |
509 | 0 | flags |= CLIENT_NOSTARTSERVER; |
510 | 0 | break; |
511 | 0 | case 'q': |
512 | 0 | break; |
513 | 0 | case 'S': |
514 | 0 | free(path); |
515 | 0 | path = xstrdup(optarg); |
516 | 0 | break; |
517 | 0 | case 'T': |
518 | 0 | tty_parse_features(optarg, ":,", &feat, NULL); |
519 | 0 | break; |
520 | 0 | case 'u': |
521 | 0 | flags |= CLIENT_UTF8; |
522 | 0 | break; |
523 | 0 | case 'v': |
524 | 0 | log_add_level(); |
525 | 0 | break; |
526 | 0 | default: |
527 | 0 | usage(1); |
528 | 0 | } |
529 | 0 | } |
530 | 0 | argc -= optind; |
531 | 0 | argv += optind; |
532 | |
|
533 | 0 | if (shell_command != NULL && argc != 0) |
534 | 0 | usage(1); |
535 | 0 | if ((flags & CLIENT_NOFORK) && argc != 0) |
536 | 0 | usage(1); |
537 | | |
538 | 0 | if ((ptm_fd = getptmfd()) == -1) |
539 | 0 | err(1, "getptmfd"); |
540 | 0 | if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd " |
541 | 0 | "recvfd proc exec tty ps", NULL) != 0) |
542 | 0 | err(1, "pledge"); |
543 | | |
544 | | /* |
545 | | * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8. |
546 | | * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain |
547 | | * UTF-8, it is a safe assumption that either they are using a UTF-8 |
548 | | * terminal, or if not they know that output from UTF-8-capable |
549 | | * programs may be wrong. |
550 | | */ |
551 | 0 | if (getenv("TMUX") != NULL) |
552 | 0 | flags |= CLIENT_UTF8; |
553 | 0 | else { |
554 | 0 | s = getenv("LC_ALL"); |
555 | 0 | if (s == NULL || *s == '\0') |
556 | 0 | s = getenv("LC_CTYPE"); |
557 | 0 | if (s == NULL || *s == '\0') |
558 | 0 | s = getenv("LANG"); |
559 | 0 | if (s == NULL || *s == '\0') |
560 | 0 | s = ""; |
561 | 0 | if (strcasestr(s, "UTF-8") != NULL || |
562 | 0 | strcasestr(s, "UTF8") != NULL) |
563 | 0 | flags |= CLIENT_UTF8; |
564 | 0 | } |
565 | |
|
566 | 0 | global_options = options_create(NULL); |
567 | 0 | global_s_options = options_create(NULL); |
568 | 0 | global_w_options = options_create(NULL); |
569 | 0 | for (oe = options_table; oe->name != NULL; oe++) { |
570 | 0 | if (oe->scope & OPTIONS_TABLE_SERVER) |
571 | 0 | options_default(global_options, oe); |
572 | 0 | if (oe->scope & OPTIONS_TABLE_SESSION) |
573 | 0 | options_default(global_s_options, oe); |
574 | 0 | if (oe->scope & OPTIONS_TABLE_WINDOW) |
575 | 0 | options_default(global_w_options, oe); |
576 | 0 | } |
577 | | |
578 | | /* |
579 | | * The default shell comes from SHELL or from the user's passwd entry |
580 | | * if available. |
581 | | */ |
582 | 0 | options_set_string(global_s_options, "default-shell", 0, "%s", |
583 | 0 | getshell()); |
584 | | |
585 | | /* Override keys to vi if VISUAL or EDITOR are set. */ |
586 | 0 | if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { |
587 | 0 | options_set_string(global_options, "editor", 0, "%s", s); |
588 | 0 | if (strrchr(s, '/') != NULL) |
589 | 0 | s = strrchr(s, '/') + 1; |
590 | 0 | if (strstr(s, "vi") != NULL) |
591 | 0 | keys = MODEKEY_VI; |
592 | 0 | else |
593 | 0 | keys = MODEKEY_EMACS; |
594 | 0 | options_set_number(global_s_options, "status-keys", keys); |
595 | 0 | options_set_number(global_w_options, "mode-keys", keys); |
596 | 0 | } |
597 | | |
598 | | /* |
599 | | * If socket is specified on the command-line with -S or -L, it is |
600 | | * used. Otherwise, $TMUX is checked and if that fails "default" is |
601 | | * used. |
602 | | */ |
603 | 0 | if (path == NULL && label == NULL) { |
604 | 0 | s = getenv("TMUX"); |
605 | 0 | if (s != NULL && *s != '\0' && *s != ',') { |
606 | 0 | path = xstrdup(s); |
607 | 0 | path[strcspn(path, ",")] = '\0'; |
608 | 0 | } |
609 | 0 | } |
610 | 0 | if (path == NULL) { |
611 | 0 | if ((path = make_label(label, &cause)) == NULL) { |
612 | 0 | if (cause != NULL) { |
613 | 0 | fprintf(stderr, "%s\n", cause); |
614 | 0 | free(cause); |
615 | 0 | } |
616 | 0 | exit(1); |
617 | 0 | } |
618 | 0 | flags |= CLIENT_DEFAULTSOCKET; |
619 | 0 | } |
620 | 0 | socket_path = path; |
621 | 0 | free(label); |
622 | | |
623 | | /* Pass control to the client. */ |
624 | 0 | exit(client_main(osdep_event_init(), argc, argv, flags, feat)); |
625 | 0 | } |