portable: fixup merge
[tmux.git] / tmux.c
bloba01ed4233ac96d6226675b02d33b206d552816da
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 & S_IRWXO) != 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 void
239 setblocking(int fd, int state)
241 int mode;
243 if ((mode = fcntl(fd, F_GETFL)) != -1) {
244 if (!state)
245 mode |= O_NONBLOCK;
246 else
247 mode &= ~O_NONBLOCK;
248 fcntl(fd, F_SETFL, mode);
252 uint64_t
253 get_timer(void)
255 struct timespec ts;
258 * We want a timestamp in milliseconds suitable for time measurement,
259 * so prefer the monotonic clock.
261 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0)
262 clock_gettime(CLOCK_REALTIME, &ts);
263 return ((ts.tv_sec * 1000ULL) + (ts.tv_nsec / 1000000ULL));
266 const char *
267 sig2name(int signo)
269 static char s[11];
271 #ifdef HAVE_SYS_SIGNAME
272 if (signo > 0 && signo < NSIG)
273 return (sys_signame[signo]);
274 #endif
275 xsnprintf(s, sizeof s, "%d", signo);
276 return (s);
279 const char *
280 find_cwd(void)
282 char resolved1[PATH_MAX], resolved2[PATH_MAX];
283 static char cwd[PATH_MAX];
284 const char *pwd;
286 if (getcwd(cwd, sizeof cwd) == NULL)
287 return (NULL);
288 if ((pwd = getenv("PWD")) == NULL || *pwd == '\0')
289 return (cwd);
292 * We want to use PWD so that symbolic links are maintained,
293 * but only if it matches the actual working directory.
295 if (realpath(pwd, resolved1) == NULL)
296 return (cwd);
297 if (realpath(cwd, resolved2) == NULL)
298 return (cwd);
299 if (strcmp(resolved1, resolved2) != 0)
300 return (cwd);
301 return (pwd);
304 const char *
305 find_home(void)
307 struct passwd *pw;
308 static const char *home;
310 if (home != NULL)
311 return (home);
313 home = getenv("HOME");
314 if (home == NULL || *home == '\0') {
315 pw = getpwuid(getuid());
316 if (pw != NULL)
317 home = pw->pw_dir;
318 else
319 home = NULL;
322 return (home);
325 const char *
326 getversion(void)
328 return (TMUX_VERSION);
332 main(int argc, char **argv)
334 char *path = NULL, *label = NULL;
335 char *cause, **var;
336 const char *s, *cwd;
337 int opt, keys, feat = 0, fflag = 0;
338 uint64_t flags = 0;
339 const struct options_table_entry *oe;
340 u_int i;
342 if (setlocale(LC_CTYPE, "en_US.UTF-8") == NULL &&
343 setlocale(LC_CTYPE, "C.UTF-8") == NULL) {
344 if (setlocale(LC_CTYPE, "") == NULL)
345 errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
346 s = nl_langinfo(CODESET);
347 if (strcasecmp(s, "UTF-8") != 0 && strcasecmp(s, "UTF8") != 0)
348 errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s);
351 setlocale(LC_TIME, "");
352 tzset();
354 if (**argv == '-')
355 flags = CLIENT_LOGIN;
357 global_environ = environ_create();
358 for (var = environ; *var != NULL; var++)
359 environ_put(global_environ, *var, 0);
360 if ((cwd = find_cwd()) != NULL)
361 environ_set(global_environ, "PWD", 0, "%s", cwd);
362 expand_paths(TMUX_CONF, &cfg_files, &cfg_nfiles, 1);
364 while ((opt = getopt(argc, argv, "2c:CDdf:lL:NqS:T:uUvV")) != -1) {
365 switch (opt) {
366 case '2':
367 tty_add_features(&feat, "256", ":,");
368 break;
369 case 'c':
370 shell_command = optarg;
371 break;
372 case 'D':
373 flags |= CLIENT_NOFORK;
374 break;
375 case 'C':
376 if (flags & CLIENT_CONTROL)
377 flags |= CLIENT_CONTROLCONTROL;
378 else
379 flags |= CLIENT_CONTROL;
380 break;
381 case 'f':
382 if (!fflag) {
383 fflag = 1;
384 for (i = 0; i < cfg_nfiles; i++)
385 free(cfg_files[i]);
386 cfg_nfiles = 0;
388 cfg_files = xreallocarray(cfg_files, cfg_nfiles + 1,
389 sizeof *cfg_files);
390 cfg_files[cfg_nfiles++] = xstrdup(optarg);
391 cfg_quiet = 0;
392 break;
393 case 'V':
394 printf("tmux %s\n", getversion());
395 exit(0);
396 case 'l':
397 flags |= CLIENT_LOGIN;
398 break;
399 case 'L':
400 free(label);
401 label = xstrdup(optarg);
402 break;
403 case 'N':
404 flags |= CLIENT_NOSTARTSERVER;
405 break;
406 case 'q':
407 break;
408 case 'S':
409 free(path);
410 path = xstrdup(optarg);
411 break;
412 case 'T':
413 tty_add_features(&feat, optarg, ":,");
414 break;
415 case 'u':
416 flags |= CLIENT_UTF8;
417 break;
418 case 'v':
419 log_add_level();
420 break;
421 default:
422 usage();
425 argc -= optind;
426 argv += optind;
428 if (shell_command != NULL && argc != 0)
429 usage();
430 if ((flags & CLIENT_NOFORK) && argc != 0)
431 usage();
433 if ((ptm_fd = getptmfd()) == -1)
434 err(1, "getptmfd");
435 if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
436 "recvfd proc exec tty ps", NULL) != 0)
437 err(1, "pledge");
440 * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
441 * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
442 * UTF-8, it is a safe assumption that either they are using a UTF-8
443 * terminal, or if not they know that output from UTF-8-capable
444 * programs may be wrong.
446 if (getenv("TMUX") != NULL)
447 flags |= CLIENT_UTF8;
448 else {
449 s = getenv("LC_ALL");
450 if (s == NULL || *s == '\0')
451 s = getenv("LC_CTYPE");
452 if (s == NULL || *s == '\0')
453 s = getenv("LANG");
454 if (s == NULL || *s == '\0')
455 s = "";
456 if (strcasestr(s, "UTF-8") != NULL ||
457 strcasestr(s, "UTF8") != NULL)
458 flags |= CLIENT_UTF8;
461 global_options = options_create(NULL);
462 global_s_options = options_create(NULL);
463 global_w_options = options_create(NULL);
464 for (oe = options_table; oe->name != NULL; oe++) {
465 if (oe->scope & OPTIONS_TABLE_SERVER)
466 options_default(global_options, oe);
467 if (oe->scope & OPTIONS_TABLE_SESSION)
468 options_default(global_s_options, oe);
469 if (oe->scope & OPTIONS_TABLE_WINDOW)
470 options_default(global_w_options, oe);
474 * The default shell comes from SHELL or from the user's passwd entry
475 * if available.
477 options_set_string(global_s_options, "default-shell", 0, "%s",
478 getshell());
480 /* Override keys to vi if VISUAL or EDITOR are set. */
481 if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
482 options_set_string(global_options, "editor", 0, "%s", s);
483 if (strrchr(s, '/') != NULL)
484 s = strrchr(s, '/') + 1;
485 if (strstr(s, "vi") != NULL)
486 keys = MODEKEY_VI;
487 else
488 keys = MODEKEY_EMACS;
489 options_set_number(global_s_options, "status-keys", keys);
490 options_set_number(global_w_options, "mode-keys", keys);
494 * If socket is specified on the command-line with -S or -L, it is
495 * used. Otherwise, $TMUX is checked and if that fails "default" is
496 * used.
498 if (path == NULL && label == NULL) {
499 s = getenv("TMUX");
500 if (s != NULL && *s != '\0' && *s != ',') {
501 path = xstrdup(s);
502 path[strcspn(path, ",")] = '\0';
505 if (path == NULL) {
506 if ((path = make_label(label, &cause)) == NULL) {
507 if (cause != NULL) {
508 fprintf(stderr, "%s\n", cause);
509 free(cause);
511 exit(1);
513 flags |= CLIENT_DEFAULTSOCKET;
515 socket_path = path;
516 free(label);
518 /* Pass control to the client. */
519 exit(client_main(osdep_event_init(), argc, argv, flags, feat));