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