Merge branch 'obsd-master'
[tmux.git] / tmux.c
blob6659e1c3aed23e386332150601d72738e380d7b1
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
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.
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.
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <sys/utsname.h>
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>
34 #include "tmux.h"
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;
41 struct timeval start_time;
42 const char *socket_path;
43 int ptm_fd = -1;
44 const char *shell_command;
46 static __dead void usage(void);
47 static char *make_label(const char *, char **);
49 static int areshell(const char *);
50 static const char *getshell(void);
52 static __dead void
53 usage(void)
55 fprintf(stderr,
56 "usage: %s [-2CDlNuVv] [-c shell-command] [-f file] [-L socket-name]\n"
57 " [-S socket-path] [-T features] [command [flags]]\n",
58 getprogname());
59 exit(1);
62 static const char *
63 getshell(void)
65 struct passwd *pw;
66 const char *shell;
68 shell = getenv("SHELL");
69 if (checkshell(shell))
70 return (shell);
72 pw = getpwuid(getuid());
73 if (pw != NULL && checkshell(pw->pw_shell))
74 return (pw->pw_shell);
76 return (_PATH_BSHELL);
79 int
80 checkshell(const char *shell)
82 if (shell == NULL || *shell != '/')
83 return (0);
84 if (areshell(shell))
85 return (0);
86 if (access(shell, X_OK) != 0)
87 return (0);
88 return (1);
91 static int
92 areshell(const char *shell)
94 const char *progname, *ptr;
96 if ((ptr = strrchr(shell, '/')) != NULL)
97 ptr++;
98 else
99 ptr = shell;
100 progname = getprogname();
101 if (*progname == '-')
102 progname++;
103 if (strcmp(ptr, progname) == 0)
104 return (1);
105 return (0);
108 static char *
109 expand_path(const char *path, const char *home)
111 char *expanded, *name;
112 const char *end;
113 struct environ_entry *value;
115 if (strncmp(path, "~/", 2) == 0) {
116 if (home == NULL)
117 return (NULL);
118 xasprintf(&expanded, "%s%s", home, path + 1);
119 return (expanded);
122 if (*path == '$') {
123 end = strchr(path, '/');
124 if (end == NULL)
125 name = xstrdup(path + 1);
126 else
127 name = xstrndup(path + 1, end - path - 1);
128 value = environ_find(global_environ, name);
129 free(name);
130 if (value == NULL)
131 return (NULL);
132 if (end == NULL)
133 end = "";
134 xasprintf(&expanded, "%s%s", value->value, end);
135 return (expanded);
138 return (xstrdup(path));
141 static void
142 expand_paths(const char *s, char ***paths, u_int *n, int ignore_errors)
144 const char *home = find_home();
145 char *copy, *next, *tmp, resolved[PATH_MAX], *expanded;
146 char *path;
147 u_int i;
149 *paths = NULL;
150 *n = 0;
152 copy = tmp = xstrdup(s);
153 while ((next = strsep(&tmp, ":")) != NULL) {
154 expanded = expand_path(next, home);
155 if (expanded == NULL) {
156 log_debug("%s: invalid path: %s", __func__, next);
157 continue;
159 if (realpath(expanded, resolved) == NULL) {
160 log_debug("%s: realpath(\"%s\") failed: %s", __func__,
161 expanded, strerror(errno));
162 if (ignore_errors) {
163 free(expanded);
164 continue;
166 path = expanded;
167 } else {
168 path = xstrdup(resolved);
169 free(expanded);
171 for (i = 0; i < *n; i++) {
172 if (strcmp(path, (*paths)[i]) == 0)
173 break;
175 if (i != *n) {
176 log_debug("%s: duplicate path: %s", __func__, path);
177 free(path);
178 continue;
180 *paths = xreallocarray(*paths, (*n) + 1, sizeof *paths);
181 (*paths)[(*n)++] = path;
183 free(copy);
186 static char *
187 make_label(const char *label, char **cause)
189 char **paths, *path, *base;
190 u_int i, n;
191 struct stat sb;
192 uid_t uid;
194 *cause = NULL;
195 if (label == NULL)
196 label = "default";
197 uid = getuid();
199 expand_paths(TMUX_SOCK, &paths, &n, 1);
200 if (n == 0) {
201 xasprintf(cause, "no suitable socket path");
202 return (NULL);
204 path = paths[0]; /* can only have one socket! */
205 for (i = 1; i < n; i++)
206 free(paths[i]);
207 free(paths);
209 xasprintf(&base, "%s/tmux-%ld", path, (long)uid);
210 free(path);
211 if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) {
212 xasprintf(cause, "couldn't create directory %s (%s)", base,
213 strerror(errno));
214 goto fail;
216 if (lstat(base, &sb) != 0) {
217 xasprintf(cause, "couldn't read directory %s (%s)", base,
218 strerror(errno));
219 goto fail;
221 if (!S_ISDIR(sb.st_mode)) {
222 xasprintf(cause, "%s is not a directory", base);
223 goto fail;
225 if (sb.st_uid != uid || (sb.st_mode & TMUX_SOCK_PERM) != 0) {
226 xasprintf(cause, "directory %s has unsafe permissions", base);
227 goto fail;
229 xasprintf(&path, "%s/%s", base, label);
230 free(base);
231 return (path);
233 fail:
234 free(base);
235 return (NULL);
238 char *
239 shell_argv0(const char *shell, int is_login)
241 const char *slash, *name;
242 char *argv0;
244 slash = strrchr(shell, '/');
245 if (slash != NULL && slash[1] != '\0')
246 name = slash + 1;
247 else
248 name = shell;
249 if (is_login)
250 xasprintf(&argv0, "-%s", name);
251 else
252 xasprintf(&argv0, "%s", name);
253 return (argv0);
256 void
257 setblocking(int fd, int state)
259 int mode;
261 if ((mode = fcntl(fd, F_GETFL)) != -1) {
262 if (!state)
263 mode |= O_NONBLOCK;
264 else
265 mode &= ~O_NONBLOCK;
266 fcntl(fd, F_SETFL, mode);
270 uint64_t
271 get_timer(void)
273 struct timespec ts;
276 * We want a timestamp in milliseconds suitable for time measurement,
277 * so prefer the monotonic clock.
279 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
280 clock_gettime(CLOCK_REALTIME, &ts);
281 return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL));
284 const char *
285 sig2name(int signo)
287 static char s[11];
289 #ifdef HAVE_SYS_SIGNAME
290 if (signo > 0 && signo < NSIG)
291 return (sys_signame[signo]);
292 #endif
293 xsnprintf(s, sizeof s, "%d", signo);
294 return (s);
297 const char *
298 find_cwd(void)
300 char resolved1[PATH_MAX], resolved2[PATH_MAX];
301 static char cwd[PATH_MAX];
302 const char *pwd;
304 if (getcwd(cwd, sizeof cwd) == NULL)
305 return (NULL);
306 if ((pwd = getenv("PWD")) == NULL || *pwd == '\0')
307 return (cwd);
310 * We want to use PWD so that symbolic links are maintained,
311 * but only if it matches the actual working directory.
313 if (realpath(pwd, resolved1) == NULL)
314 return (cwd);
315 if (realpath(cwd, resolved2) == NULL)
316 return (cwd);
317 if (strcmp(resolved1, resolved2) != 0)
318 return (cwd);
319 return (pwd);
322 const char *
323 find_home(void)
325 struct passwd *pw;
326 static const char *home;
328 if (home != NULL)
329 return (home);
331 home = getenv("HOME");
332 if (home == NULL || *home == '\0') {
333 pw = getpwuid(getuid());
334 if (pw != NULL)
335 home = pw->pw_dir;
336 else
337 home = NULL;
340 return (home);
343 const char *
344 getversion(void)
346 return (TMUX_VERSION);
350 main(int argc, char **argv)
352 char *path = NULL, *label = NULL;
353 char *cause, **var;
354 const char *s, *cwd;
355 int opt, keys, feat = 0, fflag = 0;
356 uint64_t flags = 0;
357 const struct options_table_entry *oe;
358 u_int i;
360 if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL &&
361 setlocale(LC_CTYPE, "C.UTF-8") == NULL) {
362 if (setlocale(LC_CTYPE, "") == NULL)
363 errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
364 s = nl_langinfo(CODESET);
365 if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0)
366 errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s);
369 setlocale(LC_TIME, "");
370 tzset();
372 if (**argv == '-')
373 flags = CLIENT_LOGIN;
375 global_environ = environ_create();
376 for (var = environ; *var != NULL; var++)
377 environ_put(global_environ, *var, 0);
378 if ((cwd = find_cwd()) != NULL)
379 environ_set(global_environ, "PWD", 0, "%s", cwd);
380 expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1);
382 while ((opt = getopt(argc, argv, "2c:CDdf:lL:NqS:T:uUvV")) != -1) {
383 switch (opt) {
384 case '2':
385 tty_add_features(&feat, "256", ":,");
386 break;
387 case 'c':
388 shell_command = optarg;
389 break;
390 case 'D':
391 flags |= CLIENT_NOFORK;
392 break;
393 case 'C':
394 if (flags & CLIENT_CONTROL)
395 flags |= CLIENT_CONTROLCONTROL;
396 else
397 flags |= CLIENT_CONTROL;
398 break;
399 case 'f':
400 if (!fflag) {
401 fflag = 1;
402 for (i = 0; i < cfg_nfiles; i++)
403 free(cfg_files[i]);
404 cfg_nfiles = 0;
406 cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1,
407 sizeof *cfg_files);
408 cfg_files[cfg_nfiles++] = xstrdup(optarg);
409 cfg_quiet = 0;
410 break;
411 case 'V':
412 printf("tmux %s\n", getversion());
413 exit(0);
414 case 'l':
415 flags |= CLIENT_LOGIN;
416 break;
417 case 'L':
418 free(label);
419 label = xstrdup(optarg);
420 break;
421 case 'N':
422 flags |= CLIENT_NOSTARTSERVER;
423 break;
424 case 'q':
425 break;
426 case 'S':
427 free(path);
428 path = xstrdup(optarg);
429 break;
430 case 'T':
431 tty_add_features(&feat, optarg, ":,");
432 break;
433 case 'u':
434 flags |= CLIENT_UTF8;
435 break;
436 case 'v':
437 log_add_level();
438 break;
439 default:
440 usage();
443 argc -= optind;
444 argv += optind;
446 if (shell_command != NULL && argc != 0)
447 usage();
448 if ((flags & CLIENT_NOFORK) && argc != 0)
449 usage();
451 if ((ptm_fd = getptmfd()) == -1)
452 err(1, "getptmfd");
453 if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
454 "recvfd proc exec tty ps", NULL) != 0)
455 err(1, "pledge");
458 * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
459 * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
460 * UTF-8, it is a safe assumption that either they are using a UTF-8
461 * terminal, or if not they know that output from UTF-8-capable
462 * programs may be wrong.
464 if (getenv("TMUX") != NULL)
465 flags |= CLIENT_UTF8;
466 else {
467 s = getenv("LC_ALL");
468 if (s == NULL || *s == '\0')
469 s = getenv("LC_CTYPE");
470 if (s == NULL || *s == '\0')
471 s = getenv("LANG");
472 if (s == NULL || *s == '\0')
473 s = "";
474 if (strcasestr(s, "UTF-8") != NULL ||
475 strcasestr(s, "UTF8") != NULL)
476 flags |= CLIENT_UTF8;
479 global_options = options_create(NULL);
480 global_s_options = options_create(NULL);
481 global_w_options = options_create(NULL);
482 for (oe = options_table; oe->name != NULL; oe++) {
483 if (oe->scope & OPTIONS_TABLE_SERVER)
484 options_default(global_options, oe);
485 if (oe->scope & OPTIONS_TABLE_SESSION)
486 options_default(global_s_options, oe);
487 if (oe->scope & OPTIONS_TABLE_WINDOW)
488 options_default(global_w_options, oe);
492 * The default shell comes from SHELL or from the user's passwd entry
493 * if available.
495 options_set_string(global_s_options, "default-shell", 0, "%s",
496 getshell());
498 /* Override keys to vi if VISUAL or EDITOR are set. */
499 if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
500 options_set_string(global_options, "editor", 0, "%s", s);
501 if (strrchr(s, '/') != NULL)
502 s = strrchr(s, '/') + 1;
503 if (strstr(s, "vi") != NULL)
504 keys = MODEKEY_VI;
505 else
506 keys = MODEKEY_EMACS;
507 options_set_number(global_s_options, "status-keys", keys);
508 options_set_number(global_w_options, "mode-keys", keys);
512 * If socket is specified on the command-line with -S or -L, it is
513 * used. Otherwise, $TMUX is checked and if that fails "default" is
514 * used.
516 if (path == NULL && label == NULL) {
517 s = getenv("TMUX");
518 if (s != NULL && *s != '\0' && *s != ',') {
519 path = xstrdup(s);
520 path[strcspn(path, ",")] = '\0';
523 if (path == NULL) {
524 if ((path = make_label(label, &cause)) == NULL) {
525 if (cause != NULL) {
526 fprintf(stderr, "%s\n", cause);
527 free(cause);
529 exit(1);
531 flags |= CLIENT_DEFAULTSOCKET;
533 socket_path = path;
534 free(label);
536 /* Pass control to the client. */
537 exit(client_main(osdep_event_init(), argc, argv, flags, feat));