Home | History | Annotate | Line # | Download | only in dist
tmux.c revision 1.1.1.4
      1 /* Id */
      2 
      3 /*
      4  * Copyright (c) 2007 Nicholas Marriott <nicm (at) users.sourceforge.net>
      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 
     22 #include <errno.h>
     23 #include <event.h>
     24 #include <fcntl.h>
     25 #include <locale.h>
     26 #include <pwd.h>
     27 #include <stdlib.h>
     28 #include <string.h>
     29 #include <unistd.h>
     30 
     31 #include "tmux.h"
     32 
     33 #if defined(DEBUG) && defined(__OpenBSD__)
     34 extern char	*malloc_options;
     35 #endif
     36 
     37 struct options	 global_options;	/* server options */
     38 struct options	 global_s_options;	/* session options */
     39 struct options	 global_w_options;	/* window options */
     40 struct environ	 global_environ;
     41 
     42 struct event_base *ev_base;
     43 
     44 char		*cfg_file;
     45 char		*shell_cmd;
     46 int		 debug_level;
     47 time_t		 start_time;
     48 char		 socket_path[MAXPATHLEN];
     49 int		 login_shell;
     50 char		*environ_path;
     51 
     52 __dead void	 usage(void);
     53 char 		*makesocketpath(const char *);
     54 
     55 #ifndef HAVE___PROGNAME
     56 char      *__progname = (char *) "tmux";
     57 #endif
     58 
     59 __dead void
     60 usage(void)
     61 {
     62 	fprintf(stderr,
     63 	    "usage: %s [-2lquvV] [-c shell-command] [-f file] [-L socket-name]\n"
     64 	    "            [-S socket-path] [command [flags]]\n",
     65 	    __progname);
     66 	exit(1);
     67 }
     68 
     69 void
     70 logfile(const char *name)
     71 {
     72 	char	*path;
     73 
     74 	if (debug_level > 0) {
     75 		xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid());
     76 		log_open(debug_level, path);
     77 		free(path);
     78 	}
     79 }
     80 
     81 const char *
     82 getshell(void)
     83 {
     84 	struct passwd	*pw;
     85 	const char	*shell;
     86 
     87 	shell = getenv("SHELL");
     88 	if (checkshell(shell))
     89 		return (shell);
     90 
     91 	pw = getpwuid(getuid());
     92 	if (pw != NULL && checkshell(pw->pw_shell))
     93 		return (pw->pw_shell);
     94 
     95 	return (_PATH_BSHELL);
     96 }
     97 
     98 int
     99 checkshell(const char *shell)
    100 {
    101 	if (shell == NULL || *shell == '\0' || *shell != '/')
    102 		return (0);
    103 	if (areshell(shell))
    104 		return (0);
    105 	if (access(shell, X_OK) != 0)
    106 		return (0);
    107 	return (1);
    108 }
    109 
    110 int
    111 areshell(const char *shell)
    112 {
    113 	const char	*progname, *ptr;
    114 
    115 	if ((ptr = strrchr(shell, '/')) != NULL)
    116 		ptr++;
    117 	else
    118 		ptr = shell;
    119 	progname = __progname;
    120 	if (*progname == '-')
    121 		progname++;
    122 	if (strcmp(ptr, progname) == 0)
    123 		return (1);
    124 	return (0);
    125 }
    126 
    127 char *
    128 makesocketpath(const char *label)
    129 {
    130 	char		base[MAXPATHLEN], realbase[MAXPATHLEN], *path, *s;
    131 	struct stat	sb;
    132 	u_int		uid;
    133 
    134 	uid = getuid();
    135 	if ((s = getenv("TMUX_TMPDIR")) != NULL && *s != '\0')
    136 		xsnprintf(base, sizeof base, "%s/", s);
    137 	else if ((s = getenv("TMPDIR")) != NULL && *s != '\0')
    138 		xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid);
    139 	else
    140 		xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid);
    141 
    142 	if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST)
    143 		return (NULL);
    144 
    145 	if (lstat(base, &sb) != 0)
    146 		return (NULL);
    147 	if (!S_ISDIR(sb.st_mode)) {
    148 		errno = ENOTDIR;
    149 		return (NULL);
    150 	}
    151 	if (sb.st_uid != uid || (!S_ISDIR(sb.st_mode) &&
    152 		sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) {
    153 		errno = EACCES;
    154 		return (NULL);
    155 	}
    156 
    157 	if (realpath(base, realbase) == NULL)
    158 		strlcpy(realbase, base, sizeof realbase);
    159 
    160 	xasprintf(&path, "%s/%s", realbase, label);
    161 	return (path);
    162 }
    163 
    164 void
    165 setblocking(int fd, int state)
    166 {
    167 	int mode;
    168 
    169 	if ((mode = fcntl(fd, F_GETFL)) != -1) {
    170 		if (!state)
    171 			mode |= O_NONBLOCK;
    172 		else
    173 			mode &= ~O_NONBLOCK;
    174 		fcntl(fd, F_SETFL, mode);
    175 	}
    176 }
    177 
    178 __dead void
    179 shell_exec(const char *shell, const char *shellcmd)
    180 {
    181 	const char	*shellname, *ptr;
    182 	char		*argv0;
    183 
    184 	ptr = strrchr(shell, '/');
    185 	if (ptr != NULL && *(ptr + 1) != '\0')
    186 		shellname = ptr + 1;
    187 	else
    188 		shellname = shell;
    189 	if (login_shell)
    190 		xasprintf(&argv0, "-%s", shellname);
    191 	else
    192 		xasprintf(&argv0, "%s", shellname);
    193 	setenv("SHELL", shell, 1);
    194 
    195 	setblocking(STDIN_FILENO, 1);
    196 	setblocking(STDOUT_FILENO, 1);
    197 	setblocking(STDERR_FILENO, 1);
    198 	closefrom(STDERR_FILENO + 1);
    199 
    200 	execl(shell, argv0, "-c", shellcmd, (char *) NULL);
    201 	fatal("execl failed");
    202 }
    203 
    204 int
    205 main(int argc, char **argv)
    206 {
    207 	struct passwd	*pw;
    208 	char		*s, *path, *label, **var, tmp[MAXPATHLEN];
    209 	char		 in[256];
    210 	const char	*home;
    211 	long long	 pid;
    212 	int	 	 opt, flags, quiet, keys, session;
    213 
    214 #if defined(DEBUG) && defined(__OpenBSD__)
    215 	malloc_options = (char *) "AFGJPX";
    216 #endif
    217 
    218 	setlocale(LC_TIME, "");
    219 
    220 	quiet = flags = 0;
    221 	label = path = NULL;
    222 	login_shell = (**argv == '-');
    223 	while ((opt = getopt(argc, argv, "2c:Cdf:lL:qS:uUVv")) != -1) {
    224 		switch (opt) {
    225 		case '2':
    226 			flags |= CLIENT_256COLOURS;
    227 			break;
    228 		case 'c':
    229 			free(shell_cmd);
    230 			shell_cmd = xstrdup(optarg);
    231 			break;
    232 		case 'C':
    233 			if (flags & CLIENT_CONTROL)
    234 				flags |= CLIENT_CONTROLCONTROL;
    235 			else
    236 				flags |= CLIENT_CONTROL;
    237 			break;
    238 		case 'V':
    239 			printf("%s %s\n", __progname, VERSION);
    240 			exit(0);
    241 		case 'f':
    242 			free(cfg_file);
    243 			cfg_file = xstrdup(optarg);
    244 			break;
    245 		case 'l':
    246 			login_shell = 1;
    247 			break;
    248 		case 'L':
    249 			free(label);
    250 			label = xstrdup(optarg);
    251 			break;
    252 		case 'q':
    253 			quiet = 1;
    254 			break;
    255 		case 'S':
    256 			free(path);
    257 			path = xstrdup(optarg);
    258 			break;
    259 		case 'u':
    260 			flags |= CLIENT_UTF8;
    261 			break;
    262 		case 'v':
    263 			debug_level++;
    264 			break;
    265 		default:
    266 			usage();
    267 		}
    268 	}
    269 	argc -= optind;
    270 	argv += optind;
    271 
    272 	if (shell_cmd != NULL && argc != 0)
    273 		usage();
    274 
    275 	if (!(flags & CLIENT_UTF8)) {
    276 		/*
    277 		 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG
    278 		 * exist (in that order) to contain UTF-8, it is a safe
    279 		 * assumption that either they are using a UTF-8 terminal, or
    280 		 * if not they know that output from UTF-8-capable programs may
    281 		 * be wrong.
    282 		 */
    283 		if ((s = getenv("LC_ALL")) == NULL || *s == '\0') {
    284 			if ((s = getenv("LC_CTYPE")) == NULL || *s == '\0')
    285 				s = getenv("LANG");
    286 		}
    287 		if (s != NULL && (strcasestr(s, "UTF-8") != NULL ||
    288 		    strcasestr(s, "UTF8") != NULL))
    289 			flags |= CLIENT_UTF8;
    290 	}
    291 
    292 	environ_init(&global_environ);
    293 	for (var = environ; *var != NULL; var++)
    294 		environ_put(&global_environ, *var);
    295 	if (getcwd(tmp, sizeof tmp) != NULL)
    296 		environ_set(&global_environ, "PWD", tmp);
    297 
    298 	options_init(&global_options, NULL);
    299 	options_table_populate_tree(server_options_table, &global_options);
    300 	options_set_number(&global_options, "quiet", quiet);
    301 
    302 	options_init(&global_s_options, NULL);
    303 	options_table_populate_tree(session_options_table, &global_s_options);
    304 	options_set_string(&global_s_options, "default-shell", "%s", getshell());
    305 
    306 	options_init(&global_w_options, NULL);
    307 	options_table_populate_tree(window_options_table, &global_w_options);
    308 
    309 	/* Enable UTF-8 if the first client is on UTF-8 terminal. */
    310 	if (flags & CLIENT_UTF8) {
    311 		options_set_number(&global_s_options, "status-utf8", 1);
    312 		options_set_number(&global_s_options, "mouse-utf8", 1);
    313 		options_set_number(&global_w_options, "utf8", 1);
    314 	}
    315 
    316 	/* Override keys to vi if VISUAL or EDITOR are set. */
    317 	if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) {
    318 		if (strrchr(s, '/') != NULL)
    319 			s = strrchr(s, '/') + 1;
    320 		if (strstr(s, "vi") != NULL)
    321 			keys = MODEKEY_VI;
    322 		else
    323 			keys = MODEKEY_EMACS;
    324 		options_set_number(&global_s_options, "status-keys", keys);
    325 		options_set_number(&global_w_options, "mode-keys", keys);
    326 	}
    327 
    328 	/* Locate the configuration file. */
    329 	if (cfg_file == NULL) {
    330 		home = getenv("HOME");
    331 		if (home == NULL || *home == '\0') {
    332 			pw = getpwuid(getuid());
    333 			if (pw != NULL)
    334 				home = pw->pw_dir;
    335 			else
    336 				home = NULL;
    337 		}
    338 		if (home != NULL) {
    339 			xasprintf(&cfg_file, "%s/.tmux.conf", home);
    340 			if (access(cfg_file, R_OK) != 0 && errno == ENOENT) {
    341 				free(cfg_file);
    342 				cfg_file = NULL;
    343 			}
    344 		}
    345 	}
    346 
    347 	/* Get path from environment. */
    348 	s = getenv("TMUX");
    349 	if (s != NULL && sscanf(s, "%255[^,],%lld,%d", in, &pid, &session) == 3)
    350 		environ_path = xstrdup(in);
    351 
    352 	/*
    353 	 * Figure out the socket path. If specified on the command-line with -S
    354 	 * or -L, use it, otherwise try $TMUX or assume -L default.
    355 	 */
    356 	if (path == NULL) {
    357 		/* If no -L, use the environment. */
    358 		if (label == NULL) {
    359 			if (environ_path != NULL)
    360 				path = xstrdup(environ_path);
    361 			else
    362 				label = xstrdup("default");
    363 		}
    364 
    365 		/* -L or default set. */
    366 		if (label != NULL) {
    367 			if ((path = makesocketpath(label)) == NULL) {
    368 				fprintf(stderr, "can't create socket: %s\n",
    369 					strerror(errno));
    370 				exit(1);
    371 			}
    372 		}
    373 	}
    374 	free(label);
    375 
    376 	if (strlcpy(socket_path, path, sizeof socket_path) >= sizeof socket_path) {
    377 		fprintf(stderr, "socket path too long: %s\n", path);
    378 		exit(1);
    379 	}
    380 	free(path);
    381 
    382 #ifdef HAVE_SETPROCTITLE
    383 	/* Set process title. */
    384 	setproctitle("%s (%s)", __progname, socket_path);
    385 #endif
    386 
    387 	/* Pass control to the client. */
    388 	ev_base = osdep_event_init();
    389 	exit(client_main(argc, argv, flags));
    390 }
    391