1142425Snectar/* $Id: tmux.c,v 1.1.1.2 2011/08/17 18:40:05 jmmv Exp $ */ 2160814Ssimon 3142425Snectar/* 4142425Snectar * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> 5142425Snectar * 6142425Snectar * Permission to use, copy, modify, and distribute this software for any 7142425Snectar * purpose with or without fee is hereby granted, provided that the above 8142425Snectar * copyright notice and this permission notice appear in all copies. 9142425Snectar * 10142425Snectar * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11142425Snectar * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12142425Snectar * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13142425Snectar * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14142425Snectar * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15142425Snectar * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16142425Snectar * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17142425Snectar */ 18142425Snectar 19142425Snectar#include <sys/types.h> 20142425Snectar#include <sys/stat.h> 21238405Sjkim 22142425Snectar#include <errno.h> 23142425Snectar#include <event.h> 24238405Sjkim#include <fcntl.h> 25142425Snectar#include <pwd.h> 26142425Snectar#include <stdlib.h> 27142425Snectar#include <string.h> 28142425Snectar#include <unistd.h> 29142425Snectar 30142425Snectar#include "tmux.h" 31142425Snectar 32142425Snectar#if defined(DEBUG) && defined(__OpenBSD__) 33142425Snectarextern char *malloc_options; 34142425Snectar#endif 35142425Snectar 36142425Snectarstruct options global_options; /* server options */ 37142425Snectarstruct options global_s_options; /* session options */ 38142425Snectarstruct options global_w_options; /* window options */ 39238405Sjkimstruct environ global_environ; 40142425Snectar 41142425Snectarstruct event_base *ev_base; 42142425Snectar 43142425Snectarchar *cfg_file; 44142425Snectarchar *shell_cmd; 45142425Snectarint debug_level; 46142425Snectartime_t start_time; 47142425Snectarchar socket_path[MAXPATHLEN]; 48142425Snectarint login_shell; 49142425Snectarchar *environ_path; 50142425Snectarpid_t environ_pid = -1; 51142425Snectarint environ_idx = -1; 52160814Ssimon 53160814Ssimon__dead void usage(void); 54142425Snectarvoid parseenvironment(void); 55142425Snectarchar *makesocketpath(const char *); 56142425Snectar 57142425Snectar#ifndef HAVE___PROGNAME 58142425Snectarchar *__progname = (char *) "tmux"; 59142425Snectar#endif 60142425Snectar 61142425Snectar__dead void 62142425Snectarusage(void) 63142425Snectar{ 64142425Snectar fprintf(stderr, 65142425Snectar "usage: %s [-28lquvV] [-c shell-command] [-f file] [-L socket-name]\n" 66142425Snectar " [-S socket-path] [command [flags]]\n", 67142425Snectar __progname); 68160814Ssimon exit(1); 69142425Snectar} 70142425Snectar 71142425Snectarvoid 72142425Snectarlogfile(const char *name) 73142425Snectar{ 74142425Snectar char *path; 75142425Snectar 76142425Snectar log_close(); 77142425Snectar if (debug_level > 0) { 78142425Snectar xasprintf(&path, "tmux-%s-%ld.log", name, (long) getpid()); 79142425Snectar log_open_file(debug_level, path); 80160814Ssimon xfree(path); 81160814Ssimon } 82160814Ssimon} 83142425Snectar 84160814Ssimonconst char * 85160814Ssimongetshell(void) 86238405Sjkim{ 87238405Sjkim struct passwd *pw; 88238405Sjkim const char *shell; 89238405Sjkim 90238405Sjkim shell = getenv("SHELL"); 91238405Sjkim if (checkshell(shell)) 92238405Sjkim return (shell); 93238405Sjkim 94238405Sjkim pw = getpwuid(getuid()); 95160814Ssimon if (pw != NULL && checkshell(pw->pw_shell)) 96160814Ssimon return (pw->pw_shell); 97160814Ssimon 98160814Ssimon return (_PATH_BSHELL); 99160814Ssimon} 100238405Sjkim 101238405Sjkimint 102238405Sjkimcheckshell(const char *shell) 103238405Sjkim{ 104238405Sjkim if (shell == NULL || *shell == '\0' || areshell(shell)) 105238405Sjkim return (0); 106238405Sjkim if (access(shell, X_OK) != 0) 107238405Sjkim return (0); 108160814Ssimon return (1); 109160814Ssimon} 110160814Ssimon 111160814Ssimonint 112160814Ssimonareshell(const char *shell) 113142425Snectar{ 114238405Sjkim const char *progname, *ptr; 115238405Sjkim 116142425Snectar if ((ptr = strrchr(shell, '/')) != NULL) 117142425Snectar ptr++; 118160814Ssimon else 119142425Snectar ptr = shell; 120142425Snectar progname = __progname; 121142425Snectar if (*progname == '-') 122142425Snectar progname++; 123160814Ssimon if (strcmp(ptr, progname) == 0) 124160814Ssimon return (1); 125142425Snectar return (0); 126160814Ssimon} 127160814Ssimon 128238405Sjkimvoid 129238405Sjkimparseenvironment(void) 130194206Ssimon{ 131194206Ssimon char *env, path[256]; 132194206Ssimon long pid; 133194206Ssimon int idx; 134194206Ssimon 135194206Ssimon if ((env = getenv("TMUX")) == NULL) 136194206Ssimon return; 137194206Ssimon 138194206Ssimon if (sscanf(env, "%255[^,],%ld,%d", path, &pid, &idx) != 3) 139238405Sjkim return; 140238405Sjkim environ_path = xstrdup(path); 141160814Ssimon environ_pid = pid; 142160814Ssimon environ_idx = idx; 143160814Ssimon} 144160814Ssimon 145160814Ssimonchar * 146238405Sjkimmakesocketpath(const char *label) 147238405Sjkim{ 148238405Sjkim char base[MAXPATHLEN], *path, *s; 149238405Sjkim struct stat sb; 150238405Sjkim u_int uid; 151238405Sjkim 152238405Sjkim uid = getuid(); 153238405Sjkim if ((s = getenv("TMPDIR")) == NULL || *s == '\0') 154238405Sjkim xsnprintf(base, sizeof base, "%s/tmux-%u", _PATH_TMP, uid); 155160814Ssimon else 156160814Ssimon xsnprintf(base, sizeof base, "%s/tmux-%u", s, uid); 157160814Ssimon 158160814Ssimon if (mkdir(base, S_IRWXU) != 0 && errno != EEXIST) 159160814Ssimon return (NULL); 160238405Sjkim 161238405Sjkim if (lstat(base, &sb) != 0) 162238405Sjkim return (NULL); 163238405Sjkim if (!S_ISDIR(sb.st_mode)) { 164238405Sjkim errno = ENOTDIR; 165238405Sjkim return (NULL); 166238405Sjkim } 167238405Sjkim if (sb.st_uid != uid || (sb.st_mode & (S_IRWXG|S_IRWXO)) != 0) { 168238405Sjkim errno = EACCES; 169238405Sjkim return (NULL); 170160814Ssimon } 171160814Ssimon 172160814Ssimon xasprintf(&path, "%s/%s", base, label); 173160814Ssimon return (path); 174238405Sjkim} 175238405Sjkim 176194206Ssimonvoid 177194206Ssimonsetblocking(int fd, int state) 178142425Snectar{ 179142425Snectar int mode; 180142425Snectar 181160814Ssimon if ((mode = fcntl(fd, F_GETFL)) != -1) { 182160814Ssimon if (!state) 183160814Ssimon mode |= O_NONBLOCK; 184238405Sjkim else 185238405Sjkim mode &= ~O_NONBLOCK; 186160814Ssimon fcntl(fd, F_SETFL, mode); 187160814Ssimon } 188160814Ssimon} 189160814Ssimon 190160814Ssimon__dead void 191238405Sjkimshell_exec(const char *shell, const char *shellcmd) 192238405Sjkim{ 193238405Sjkim const char *shellname, *ptr; 194142425Snectar char *argv0; 195142425Snectar 196142425Snectar ptr = strrchr(shell, '/'); 197142425Snectar if (ptr != NULL && *(ptr + 1) != '\0') 198142425Snectar shellname = ptr + 1; 199160814Ssimon else 200142425Snectar shellname = shell; 201160814Ssimon if (login_shell) 202160814Ssimon xasprintf(&argv0, "-%s", shellname); 203160814Ssimon else 204160814Ssimon xasprintf(&argv0, "%s", shellname); 205160814Ssimon setenv("SHELL", shell, 1); 206238405Sjkim 207238405Sjkim setblocking(STDIN_FILENO, 1); 208238405Sjkim setblocking(STDOUT_FILENO, 1); 209142425Snectar setblocking(STDERR_FILENO, 1); 210142425Snectar closefrom(STDERR_FILENO + 1); 211142425Snectar 212160814Ssimon execl(shell, argv0, "-c", shellcmd, (char *) NULL); 213160814Ssimon fatal("execl failed"); 214160814Ssimon} 215160814Ssimon 216160814Ssimonint 217160814Ssimonmain(int argc, char **argv) 218160814Ssimon{ 219160814Ssimon struct passwd *pw; 220160814Ssimon struct keylist *keylist; 221238405Sjkim char *s, *path, *label, *home, **var; 222238405Sjkim int opt, flags, quiet, keys; 223238405Sjkim 224142425Snectar#if defined(DEBUG) && defined(__OpenBSD__) 225142425Snectar malloc_options = (char *) "AFGJPX"; 226160814Ssimon#endif 227160814Ssimon 228160814Ssimon quiet = flags = 0; 229160814Ssimon label = path = NULL; 230160814Ssimon login_shell = (**argv == '-'); 231160814Ssimon while ((opt = getopt(argc, argv, "28c:df:lL:qS:uUvV")) != -1) { 232160814Ssimon switch (opt) { 233160814Ssimon case '2': 234160814Ssimon flags |= IDENTIFY_256COLOURS; 235238405Sjkim flags &= ~IDENTIFY_88COLOURS; 236238405Sjkim break; 237238405Sjkim case '8': 238142425Snectar flags |= IDENTIFY_88COLOURS; 239142425Snectar flags &= ~IDENTIFY_256COLOURS; 240160814Ssimon break; 241160814Ssimon case 'c': 242160814Ssimon if (shell_cmd != NULL) 243160814Ssimon xfree(shell_cmd); 244238405Sjkim shell_cmd = xstrdup(optarg); 245238405Sjkim break; 246238405Sjkim case 'V': 247238405Sjkim printf("%s %s\n", __progname, VERSION); 248238405Sjkim exit(0); 249238405Sjkim case 'f': 250238405Sjkim if (cfg_file != NULL) 251238405Sjkim xfree(cfg_file); 252238405Sjkim cfg_file = xstrdup(optarg); 253238405Sjkim break; 254238405Sjkim case 'l': 255238405Sjkim login_shell = 1; 256238405Sjkim break; 257238405Sjkim case 'L': 258238405Sjkim if (label != NULL) 259 xfree(label); 260 label = xstrdup(optarg); 261 break; 262 case 'q': 263 quiet = 1; 264 break; 265 case 'S': 266 if (path != NULL) 267 xfree(path); 268 path = xstrdup(optarg); 269 break; 270 case 'u': 271 flags |= IDENTIFY_UTF8; 272 break; 273 case 'v': 274 debug_level++; 275 break; 276 default: 277 usage(); 278 } 279 } 280 argc -= optind; 281 argv += optind; 282 283 if (shell_cmd != NULL && argc != 0) 284 usage(); 285 286 log_open_tty(debug_level); 287 288 if (!(flags & IDENTIFY_UTF8)) { 289 /* 290 * If the user has set whichever of LC_ALL, LC_CTYPE or LANG 291 * exist (in that order) to contain UTF-8, it is a safe 292 * assumption that either they are using a UTF-8 terminal, or 293 * if not they know that output from UTF-8-capable programs may 294 * be wrong. 295 */ 296 if ((s = getenv("LC_ALL")) == NULL) { 297 if ((s = getenv("LC_CTYPE")) == NULL) 298 s = getenv("LANG"); 299 } 300 if (s != NULL && (strcasestr(s, "UTF-8") != NULL || 301 strcasestr(s, "UTF8") != NULL)) 302 flags |= IDENTIFY_UTF8; 303 } 304 305 environ_init(&global_environ); 306 for (var = environ; *var != NULL; var++) 307 environ_put(&global_environ, *var); 308 309 options_init(&global_options, NULL); 310 options_table_populate_tree(server_options_table, &global_options); 311 options_set_number(&global_options, "quiet", quiet); 312 313 options_init(&global_s_options, NULL); 314 options_table_populate_tree(session_options_table, &global_s_options); 315 options_set_string(&global_s_options, "default-shell", "%s", getshell()); 316 317 options_init(&global_w_options, NULL); 318 options_table_populate_tree(window_options_table, &global_w_options); 319 320 /* Set the prefix option (its a list, so not in the table). */ 321 keylist = xmalloc(sizeof *keylist); 322 ARRAY_INIT(keylist); 323 ARRAY_ADD(keylist, '\002'); 324 options_set_data(&global_s_options, "prefix", keylist, xfree); 325 326 /* Enable UTF-8 if the first client is on UTF-8 terminal. */ 327 if (flags & IDENTIFY_UTF8) { 328 options_set_number(&global_s_options, "status-utf8", 1); 329 options_set_number(&global_s_options, "mouse-utf8", 1); 330 options_set_number(&global_w_options, "utf8", 1); 331 } 332 333 /* Override keys to vi if VISUAL or EDITOR are set. */ 334 if ((s = getenv("VISUAL")) != NULL || (s = getenv("EDITOR")) != NULL) { 335 if (strrchr(s, '/') != NULL) 336 s = strrchr(s, '/') + 1; 337 if (strstr(s, "vi") != NULL) 338 keys = MODEKEY_VI; 339 else 340 keys = MODEKEY_EMACS; 341 options_set_number(&global_s_options, "status-keys", keys); 342 options_set_number(&global_w_options, "mode-keys", keys); 343 } 344 345 /* Locate the configuration file. */ 346 if (cfg_file == NULL) { 347 home = getenv("HOME"); 348 if (home == NULL || *home == '\0') { 349 pw = getpwuid(getuid()); 350 if (pw != NULL) 351 home = pw->pw_dir; 352 } 353 xasprintf(&cfg_file, "%s/%s", home, DEFAULT_CFG); 354 if (access(cfg_file, R_OK) != 0 && errno == ENOENT) { 355 xfree(cfg_file); 356 cfg_file = NULL; 357 } 358 } 359 360 /* 361 * Figure out the socket path. If specified on the command-line with -S 362 * or -L, use it, otherwise try $TMUX or assume -L default. 363 */ 364 parseenvironment(); 365 if (path == NULL) { 366 /* If no -L, use the environment. */ 367 if (label == NULL) { 368 if (environ_path != NULL) 369 path = xstrdup(environ_path); 370 else 371 label = xstrdup("default"); 372 } 373 374 /* -L or default set. */ 375 if (label != NULL) { 376 if ((path = makesocketpath(label)) == NULL) { 377 log_warn("can't create socket"); 378 exit(1); 379 } 380 } 381 } 382 if (label != NULL) 383 xfree(label); 384 if (realpath(path, socket_path) == NULL) 385 strlcpy(socket_path, path, sizeof socket_path); 386 xfree(path); 387 388#ifdef HAVE_SETPROCTITLE 389 /* Set process title. */ 390 setproctitle("%s (%s)", __progname, socket_path); 391#endif 392 393 /* Pass control to the client. */ 394 ev_base = osdep_event_init(); 395 exit(client_main(argc, argv, flags)); 396} 397