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>
21 #include <sys/utsname.h>
40 struct options
*global_options
; /* server options */
41 struct options
*global_s_options
; /* session options */
42 struct options
*global_w_options
; /* window options */
43 struct environ
*global_environ
;
45 struct timeval start_time
;
46 const char *socket_path
;
48 const char *shell_command
;
50 static __dead
void usage(void);
51 static char *make_label(const char *, char **);
53 static int areshell(const char *);
54 static const char *getshell(void);
60 "usage: %s [-2CDlNuvV] [-c shell-command] [-f file] [-L socket-name]\n"
61 " [-S socket-path] [-T features] [command [flags]]\n",
72 shell
= getenv("SHELL");
73 if (checkshell(shell
))
76 pw
= getpwuid(getuid());
77 if (pw
!= NULL
&& checkshell(pw
->pw_shell
))
78 return (pw
->pw_shell
);
80 return (_PATH_BSHELL
);
84 checkshell(const char *shell
)
86 if (shell
== NULL
|| *shell
!= '/')
90 if (access(shell
, X_OK
) != 0)
96 areshell(const char *shell
)
98 const char *progname
, *ptr
;
100 if ((ptr
= strrchr(shell
, '/')) != NULL
)
104 progname
= getprogname();
105 if (*progname
== '-')
107 if (strcmp(ptr
, progname
) == 0)
113 expand_path(const char *path
, const char *home
)
115 char *expanded
, *name
;
117 struct environ_entry
*value
;
119 if (strncmp(path
, "~/", 2) == 0) {
122 xasprintf(&expanded
, "%s%s", home
, path
+ 1);
127 end
= strchr(path
, '/');
129 name
= xstrdup(path
+ 1);
131 name
= xstrndup(path
+ 1, end
- path
- 1);
132 value
= environ_find(global_environ
, name
);
138 xasprintf(&expanded
, "%s%s", value
->value
, end
);
142 return (xstrdup(path
));
146 expand_paths(const char *s
, char ***paths
, u_int
*n
, int ignore_errors
)
148 const char *home
= find_home();
149 char *copy
, *next
, *tmp
, resolved
[PATH_MAX
], *expanded
;
156 copy
= tmp
= xstrdup(s
);
157 while ((next
= strsep(&tmp
, ":")) != NULL
) {
158 expanded
= expand_path(next
, home
);
159 if (expanded
== NULL
) {
160 log_debug("%s: invalid path: %s", __func__
, next
);
163 if (realpath(expanded
, resolved
) == NULL
) {
164 log_debug("%s: realpath(\"%s\") failed: %s", __func__
,
165 expanded
, strerror(errno
));
172 path
= xstrdup(resolved
);
175 for (i
= 0; i
< *n
; i
++) {
176 if (strcmp(path
, (*paths
)[i
]) == 0)
180 log_debug("%s: duplicate path: %s", __func__
, path
);
184 *paths
= xreallocarray(*paths
, (*n
) + 1, sizeof *paths
);
185 (*paths
)[(*n
)++] = path
;
191 make_label(const char *label
, char **cause
)
193 char **paths
, *path
, *base
;
203 expand_paths(TMUX_SOCK
, &paths
, &n
, 1);
205 xasprintf(cause
, "no suitable socket path");
208 path
= paths
[0]; /* can only have one socket! */
209 for (i
= 1; i
< n
; i
++)
213 xasprintf(&base
, "%s/tmux-%ld", path
, (long)uid
);
215 if (mkdir(base
, S_IRWXU
) != 0 && errno
!= EEXIST
) {
216 xasprintf(cause
, "couldn't create directory %s (%s)", base
,
220 if (lstat(base
, &sb
) != 0) {
221 xasprintf(cause
, "couldn't read directory %s (%s)", base
,
225 if (!S_ISDIR(sb
.st_mode
)) {
226 xasprintf(cause
, "%s is not a directory", base
);
229 if (sb
.st_uid
!= uid
|| (sb
.st_mode
& S_IRWXO
) != 0) {
230 xasprintf(cause
, "directory %s has unsafe permissions", base
);
233 xasprintf(&path
, "%s/%s", base
, label
);
243 setblocking(int fd
, int state
)
247 if ((mode
= fcntl(fd
, F_GETFL
)) != -1) {
252 fcntl(fd
, F_SETFL
, mode
);
262 * We want a timestamp in milliseconds suitable for time measurement,
263 * so prefer the monotonic clock.
265 if (clock_gettime(CLOCK_MONOTONIC
, &ts
) != 0)
266 clock_gettime(CLOCK_REALTIME
, &ts
);
267 return ((ts
.tv_sec
* 1000ULL) + (ts
.tv_nsec
/ 1000000ULL));
275 if (signo
> 0 && signo
< NSIG
)
276 return (sys_signame
[signo
]);
277 xsnprintf(s
, sizeof s
, "%d", signo
);
284 char resolved1
[PATH_MAX
], resolved2
[PATH_MAX
];
285 static char cwd
[PATH_MAX
];
288 if (getcwd(cwd
, sizeof cwd
) == NULL
)
290 if ((pwd
= getenv("PWD")) == NULL
|| *pwd
== '\0')
294 * We want to use PWD so that symbolic links are maintained,
295 * but only if it matches the actual working directory.
297 if (realpath(pwd
, resolved1
) == NULL
)
299 if (realpath(cwd
, resolved2
) == NULL
)
301 if (strcmp(resolved1
, resolved2
) != 0)
310 static const char *home
;
315 home
= getenv("HOME");
316 if (home
== NULL
|| *home
== '\0') {
317 pw
= getpwuid(getuid());
330 static char *version
;
333 if (version
== NULL
) {
335 fatalx("uname failed");
336 xasprintf(&version
, "openbsd-%s", u
.release
);
342 main(int argc
, char **argv
)
344 char *path
= NULL
, *label
= NULL
;
347 int opt
, keys
, feat
= 0, fflag
= 0;
349 const struct options_table_entry
*oe
;
352 if (setlocale(LC_CTYPE
, "en_US.UTF-8") == NULL
&&
353 setlocale(LC_CTYPE
, "C.UTF-8") == NULL
) {
354 if (setlocale(LC_CTYPE
, "") == NULL
)
355 errx(1, "invalid LC_ALL, LC_CTYPE or LANG");
356 s
= nl_langinfo(CODESET
);
357 if (strcasecmp(s
, "UTF-8") != 0 && strcasecmp(s
, "UTF8") != 0)
358 errx(1, "need UTF-8 locale (LC_CTYPE) but have %s", s
);
361 setlocale(LC_TIME
, "");
365 flags
= CLIENT_LOGIN
;
367 global_environ
= environ_create();
368 for (var
= environ
; *var
!= NULL
; var
++)
369 environ_put(global_environ
, *var
, 0);
370 if ((cwd
= find_cwd()) != NULL
)
371 environ_set(global_environ
, "PWD", 0, "%s", cwd
);
372 expand_paths(TMUX_CONF
, &cfg_files
, &cfg_nfiles
, 1);
374 while ((opt
= getopt(argc
, argv
, "2c:CDdf:lL:NqS:T:uUvV")) != -1) {
377 tty_add_features(&feat
, "256", ":,");
380 shell_command
= optarg
;
383 flags
|= CLIENT_NOFORK
;
386 if (flags
& CLIENT_CONTROL
)
387 flags
|= CLIENT_CONTROLCONTROL
;
389 flags
|= CLIENT_CONTROL
;
394 for (i
= 0; i
< cfg_nfiles
; i
++)
398 cfg_files
= xreallocarray(cfg_files
, cfg_nfiles
+ 1,
400 cfg_files
[cfg_nfiles
++] = xstrdup(optarg
);
404 printf("%s %s\n", getprogname(), getversion());
407 flags
|= CLIENT_LOGIN
;
411 label
= xstrdup(optarg
);
414 flags
|= CLIENT_NOSTARTSERVER
;
420 path
= xstrdup(optarg
);
423 tty_add_features(&feat
, optarg
, ":,");
426 flags
|= CLIENT_UTF8
;
438 if (shell_command
!= NULL
&& argc
!= 0)
440 if ((flags
& CLIENT_NOFORK
) && argc
!= 0)
443 if ((ptm_fd
= getptmfd()) == -1)
445 if (pledge("stdio rpath wpath cpath flock fattr unix getpw sendfd "
446 "recvfd proc exec tty ps", NULL
) != 0)
450 * tmux is a UTF-8 terminal, so if TMUX is set, assume UTF-8.
451 * Otherwise, if the user has set LC_ALL, LC_CTYPE or LANG to contain
452 * UTF-8, it is a safe assumption that either they are using a UTF-8
453 * terminal, or if not they know that output from UTF-8-capable
454 * programs may be wrong.
456 if (getenv("TMUX") != NULL
)
457 flags
|= CLIENT_UTF8
;
459 s
= getenv("LC_ALL");
460 if (s
== NULL
|| *s
== '\0')
461 s
= getenv("LC_CTYPE");
462 if (s
== NULL
|| *s
== '\0')
464 if (s
== NULL
|| *s
== '\0')
466 if (strcasestr(s
, "UTF-8") != NULL
||
467 strcasestr(s
, "UTF8") != NULL
)
468 flags
|= CLIENT_UTF8
;
471 global_options
= options_create(NULL
);
472 global_s_options
= options_create(NULL
);
473 global_w_options
= options_create(NULL
);
474 for (oe
= options_table
; oe
->name
!= NULL
; oe
++) {
475 if (oe
->scope
& OPTIONS_TABLE_SERVER
)
476 options_default(global_options
, oe
);
477 if (oe
->scope
& OPTIONS_TABLE_SESSION
)
478 options_default(global_s_options
, oe
);
479 if (oe
->scope
& OPTIONS_TABLE_WINDOW
)
480 options_default(global_w_options
, oe
);
484 * The default shell comes from SHELL or from the user's passwd entry
487 options_set_string(global_s_options
, "default-shell", 0, "%s",
490 /* Override keys to vi if VISUAL or EDITOR are set. */
491 if ((s
= getenv("VISUAL")) != NULL
|| (s
= getenv("EDITOR")) != NULL
) {
492 options_set_string(global_options
, "editor", 0, "%s", s
);
493 if (strrchr(s
, '/') != NULL
)
494 s
= strrchr(s
, '/') + 1;
495 if (strstr(s
, "vi") != NULL
)
498 keys
= MODEKEY_EMACS
;
499 options_set_number(global_s_options
, "status-keys", keys
);
500 options_set_number(global_w_options
, "mode-keys", keys
);
504 * If socket is specified on the command-line with -S or -L, it is
505 * used. Otherwise, $TMUX is checked and if that fails "default" is
508 if (path
== NULL
&& label
== NULL
) {
510 if (s
!= NULL
&& *s
!= '\0' && *s
!= ',') {
512 path
[strcspn(path
, ",")] = '\0';
516 if ((path
= make_label(label
, &cause
)) == NULL
) {
518 fprintf(stderr
, "%s\n", cause
);
523 flags
|= CLIENT_DEFAULTSOCKET
;
528 /* Pass control to the client. */
529 exit(client_main(event_init(), argc
, argv
, flags
, feat
));