1use crate::xmalloc::xstrndup;
15use crate::*;
16
17unsafe extern "C" {
18 fn errx(_: c_int, _: *const c_char, ...);
20 fn err(_: c_int, _: *const c_char, ...);
21
22 fn tzset();
23}
24
25use crate::compat::{S_ISDIR, fdforkpty::getptmfd, getprogname::getprogname, optarg, optind};
26use libc::{
27 CLOCK_MONOTONIC, CLOCK_REALTIME, CODESET, EEXIST, F_GETFL, F_SETFL, LC_CTYPE, LC_TIME,
28 O_NONBLOCK, PATH_MAX, S_IRWXO, S_IRWXU, X_OK, access, clock_gettime, fcntl, getcwd, getenv,
29 getopt, getpwuid, getuid, lstat, mkdir, nl_langinfo, printf, realpath, setlocale, stat,
30 strcasecmp, strcasestr, strchr, strcspn, strerror, strncmp, strrchr, strstr, timespec,
31};
32
33pub static mut global_options: *mut options = null_mut();
34
35pub static mut global_s_options: *mut options = null_mut();
36
37pub static mut global_w_options: *mut options = null_mut();
38
39pub static mut global_environ: *mut environ = null_mut();
40
41pub static mut start_time: timeval = unsafe { zeroed() };
42
43pub static mut socket_path: *const c_char = null_mut();
44
45pub static mut ptm_fd: c_int = -1;
46
47pub static mut shell_command: *mut c_char = null_mut();
48
49pub extern "C" fn usage() -> ! {
50 unsafe {
51 libc::fprintf(stderr, c"usage: %s [-2CDlNuVv] [-c shell-command] [-f file] [-L socket-name]\n [-S socket-path] [-T features] [command [flags]]\n".as_ptr(), getprogname());
52 std::process::exit(1)
53 }
54}
55
56pub unsafe extern "C" fn getshell() -> *const c_char {
57 unsafe {
58 let shell = getenv(c"SHELL".as_ptr());
59 if checkshell(shell) {
60 return shell;
61 }
62
63 let pw = getpwuid(getuid());
64 if !pw.is_null() && checkshell((*pw).pw_shell) {
65 return (*pw).pw_shell;
66 }
67
68 _PATH_BSHELL
69 }
70}
71
72pub unsafe extern "C" fn checkshell(shell: *const c_char) -> bool {
73 unsafe {
74 if shell.is_null() || *shell != b'/' as c_char {
75 return false;
76 }
77 if areshell(shell) != 0 {
78 return false;
79 }
80 if access(shell, X_OK) != 0 {
81 return false;
82 }
83 }
84 true
85}
86
87pub unsafe extern "C" fn areshell(shell: *const c_char) -> c_int {
88 unsafe {
89 let ptr = strrchr(shell, b'/' as c_int);
90 let ptr = if !ptr.is_null() {
91 ptr.wrapping_add(1)
92 } else {
93 shell
94 };
95 let mut progname = getprogname();
96 if *progname == b'-' as c_char {
97 progname = progname.wrapping_add(1);
98 }
99 if libc::strcmp(ptr, progname) == 0 {
100 1
101 } else {
102 0
103 }
104 }
105}
106
107pub unsafe extern "C" fn expand_path(path: *const c_char, home: *const c_char) -> *mut c_char {
108 unsafe {
109 let mut expanded: *mut c_char = null_mut();
110 let mut end: *const c_char = null_mut();
111
112 if strncmp(path, c"~/".as_ptr(), 2) == 0 {
113 if home.is_null() {
114 return null_mut();
115 }
116 return format_nul!("{}{}", _s(home), _s(path.add(1)));
117 }
118
119 if *path == b'$' as c_char {
120 end = strchr(path, b'/' as i32);
121 let name = if end.is_null() {
122 xstrdup(path.add(1)).cast().as_ptr()
123 } else {
124 xstrndup(path.add(1), end.addr() - path.addr() - 1)
125 .cast()
126 .as_ptr()
127 };
128 let value = environ_find(global_environ, name);
129 free_(name);
130 if value.is_null() {
131 return null_mut();
132 }
133 if end.is_null() {
134 end = c"".as_ptr();
135 }
136 return format_nul!("{}{}", _s(transmute_ptr((*value).value)), _s(end));
137 }
138
139 xstrdup(path).cast().as_ptr()
140 }
141}
142
143unsafe extern "C" fn expand_paths(
144 s: *const c_char,
145 paths: *mut *mut *mut c_char,
146 n: *mut u32,
147 ignore_errors: i32,
148) {
149 unsafe {
150 let home = find_home();
151 let mut next: *const c_char = null_mut();
152 let mut resolved: [c_char; PATH_MAX as usize] = zeroed(); let mut path = null_mut();
154
155 let func = "expand_paths";
156
157 *paths = null_mut();
158 *n = 0;
159
160 let mut tmp: *mut c_char = xstrdup(s).cast().as_ptr();
161 let copy = tmp;
162 while {
163 next = strsep(&raw mut tmp as _, c":".as_ptr().cast());
164 !next.is_null()
165 } {
166 let expanded = expand_path(next, home);
167 if expanded.is_null() {
168 log_debug!("{}: invalid path: {}", func, _s(next));
169 continue;
170 }
171 if realpath(expanded, resolved.as_mut_ptr()).is_null() {
172 log_debug!(
173 "{}: realpath(\"{}\") failed: {}",
174 func,
175 _s(expanded),
176 _s(strerror(errno!())),
177 );
178 if ignore_errors != 0 {
179 free_(expanded);
180 continue;
181 }
182 path = expanded;
183 } else {
184 path = xstrdup(resolved.as_ptr()).cast().as_ptr();
185 free_(expanded);
186 }
187 let mut i = 0;
188 for j in 0..*n {
189 i = j;
190 if libc::strcmp(path as _, *(*paths).add(i as usize)) == 0 {
191 break;
192 }
193 }
194 if i != *n {
195 log_debug!("{}: duplicate path: {}", func, _s(path));
196 free_(path);
197 continue;
198 }
199 *paths = xreallocarray_::<*mut c_char>(*paths, (*n + 1) as usize).as_ptr();
200 *(*paths).add((*n) as usize) = path;
201 *n += 1;
202 }
203 free_(copy);
204 }
205}
206
207unsafe extern "C" fn make_label(
208 mut label: *const c_char,
209 cause: *mut *mut c_char,
210) -> *const c_char {
211 let mut paths: *mut *mut c_char = null_mut();
212 let mut path: *mut c_char = null_mut();
213 let mut base: *mut c_char = null_mut();
214 let mut sb: stat = unsafe { zeroed() }; let mut n: u32 = 0;
216
217 unsafe {
218 'fail: {
219 *cause = null_mut();
220 if label.is_null() {
221 label = c"default".as_ptr();
222 }
223 let uid = getuid();
224
225 expand_paths(TMUX_SOCK.as_ptr(), &raw mut paths, &raw mut n, 1);
226 if n == 0 {
227 *cause = format_nul!("no suitable socket path");
228 return null_mut();
229 }
230 path = *paths; for i in 1..n {
232 free_(*paths.add(i as usize));
233 }
234 free_(paths);
235
236 base = format_nul!("{}/tmux-{}", _s(path), uid);
237 free_(path);
238 if mkdir(base, S_IRWXU) != 0 && errno!() != EEXIST {
239 *cause = format_nul!(
240 "couldn't create directory {} ({})",
241 _s(base),
242 _s(strerror(errno!()))
243 );
244 break 'fail;
245 }
246 if lstat(base, &raw mut sb) != 0 {
247 *cause = format_nul!(
248 "couldn't read directory {} ({})",
249 _s(base),
250 _s(strerror(errno!())),
251 );
252 break 'fail;
253 }
254 if !S_ISDIR(sb.st_mode) {
255 *cause = format_nul!("{} is not a directory", _s(base));
256 break 'fail;
257 }
258 if sb.st_uid != uid || (sb.st_mode & S_IRWXO) != 0 {
259 *cause = format_nul!("directory {} has unsafe permissions", _s(base));
260 break 'fail;
261 }
262 path = format_nul!("{}/{}", _s(base), _s(label));
263 free_(base);
264 return path;
265 }
266
267 free_(base);
269 null_mut()
270 }
271}
272
273pub unsafe extern "C" fn shell_argv0(shell: *const c_char, is_login: c_int) -> *mut c_char {
274 unsafe {
275 let slash = strrchr(shell, b'/' as _);
276 let name = if !slash.is_null() && *slash.add(1) != b'\0' as c_char {
277 slash.add(1)
278 } else {
279 shell
280 };
281
282 if is_login != 0 {
283 format_nul!("-{}", _s(name))
284 } else {
285 format_nul!("{}", _s(name))
286 }
287 }
288}
289
290pub unsafe extern "C" fn setblocking(fd: c_int, state: c_int) {
291 unsafe {
292 let mut mode = fcntl(fd, F_GETFL);
293
294 if mode != -1 {
295 if state == 0 {
296 mode |= O_NONBLOCK;
297 } else {
298 mode &= !O_NONBLOCK;
299 }
300 fcntl(fd, F_SETFL, mode);
301 }
302 }
303}
304
305pub unsafe extern "C" fn get_timer() -> u64 {
306 unsafe {
307 let mut ts: timespec = zeroed();
308 if clock_gettime(CLOCK_MONOTONIC, &raw mut ts) != 0 {
311 clock_gettime(CLOCK_REALTIME, &raw mut ts);
312 }
313 (ts.tv_sec as u64 * 1000) + (ts.tv_nsec as u64 / 1000000)
314 }
315}
316
317pub unsafe extern "C" fn find_cwd() -> *mut c_char {
318 static mut cwd: [c_char; PATH_MAX as usize] = [0; PATH_MAX as usize];
319 unsafe {
320 let mut resolved1: [c_char; PATH_MAX as usize] = [0; PATH_MAX as usize];
321 let mut resolved2: [c_char; PATH_MAX as usize] = [0; PATH_MAX as usize];
322
323 if getcwd(&raw mut cwd as _, size_of::<[c_char; PATH_MAX as usize]>()).is_null() {
324 return null_mut();
325 }
326 let pwd = getenv(c"PWD".as_ptr());
327 if pwd.is_null() || *pwd == b'\0' as c_char {
328 return &raw mut cwd as _;
329 }
330
331 if realpath(pwd, &raw mut resolved1 as _).is_null() {
335 return &raw mut cwd as _;
336 }
337 if realpath(&raw mut cwd as _, &raw mut resolved2 as _).is_null() {
338 return &raw mut cwd as _;
339 }
340 if libc::strcmp(&raw mut resolved1 as _, &raw mut resolved2 as _) != 0 {
341 return &raw mut cwd as _;
342 }
343 pwd
344 }
345}
346
347pub unsafe extern "C" fn find_home() -> *mut c_char {
348 static mut home: *mut c_char = null_mut();
349
350 unsafe {
351 if !home.is_null() {
352 home
353 } else {
354 home = getenv(c"HOME".as_ptr());
355 if home.is_null() || *home == b'\0' as c_char {
356 let pw = getpwuid(getuid());
357 if !pw.is_null() {
358 home = (*pw).pw_dir;
359 } else {
360 home = null_mut();
361 }
362 }
363
364 home
365 }
366 }
367}
368
369pub fn getversion() -> &'static str {
370 "3.5rs"
371}
372
373pub fn getversion_c() -> *const c_char {
374 c"3.5rs".as_ptr()
375}
376
377#[cfg_attr(not(test), unsafe(no_mangle))]
379pub unsafe extern "C" fn main(mut argc: i32, mut argv: *mut *mut c_char, env: *mut *mut c_char) {
380 std::panic::set_hook(Box::new(|panic_info| {
381 let backtrace = std::backtrace::Backtrace::capture();
382 let err_str = format!("{backtrace:#?}");
383 std::fs::write("client-panic.txt", err_str).unwrap();
384 }));
385
386 unsafe {
387 let mut cause: *mut c_char = null_mut();
389 let mut path: *const c_char = null_mut();
390 let mut label: *mut c_char = null_mut();
391 let mut feat: i32 = 0;
392 let mut fflag: i32 = 0;
393 let mut flags: client_flag = client_flag::empty();
394
395 if setlocale(LC_CTYPE, c"en_US.UTF-8".as_ptr()).is_null()
396 && setlocale(LC_CTYPE, c"C.UTF-8".as_ptr()).is_null()
397 {
398 if setlocale(LC_CTYPE, c"".as_ptr()).is_null() {
399 errx(1, c"invalid LC_ALL, LC_CTYPE or LANG".as_ptr());
400 }
401 let s = nl_langinfo(CODESET);
402 if strcasecmp(s, c"UTF-8".as_ptr()) != 0 && strcasecmp(s, c"UTF8".as_ptr()) != 0 {
403 errx(1, c"need UTF-8 locale (LC_CTYPE) but have %s".as_ptr(), s);
404 }
405 }
406
407 setlocale(LC_TIME, c"".as_ptr());
408 tzset();
409
410 if **argv == b'-' as c_char {
411 flags = client_flag::LOGIN;
412 }
413
414 global_environ = environ_create().as_ptr();
415
416 let mut var = environ;
417 while !(*var).is_null() {
418 environ_put(global_environ, *var, 0);
419 var = var.add(1);
420 }
421
422 let cwd = find_cwd();
423 if !cwd.is_null() {
424 environ_set!(global_environ, c"PWD".as_ptr(), 0, "{}", _s(cwd));
425 }
426 expand_paths(
427 TMUX_CONF.as_ptr(),
428 &raw mut cfg_files,
429 &raw mut cfg_nfiles,
430 1,
431 );
432
433 let mut opt;
434 while {
435 opt = getopt(argc, argv, c"2c:CDdf:lL:NqS:T:uUvV".as_ptr());
436 opt != -1
437 } {
438 match opt as u8 {
439 b'2' => tty_add_features(&raw mut feat, c"256".as_ptr(), c":,".as_ptr()),
440 b'c' => shell_command = optarg,
441 b'D' => flags |= client_flag::NOFORK,
442 b'C' => {
443 if flags.intersects(client_flag::CONTROL) {
444 flags |= client_flag::CONTROLCONTROL;
445 } else {
446 flags |= client_flag::CONTROL;
447 }
448 }
449 b'f' => {
450 if fflag == 0 {
451 fflag = 1;
452 for i in 0..cfg_nfiles {
453 free((*cfg_files.add(i as usize)) as _);
454 }
455 cfg_nfiles = 0;
456 }
457 cfg_files =
458 xreallocarray_::<*mut c_char>(cfg_files, cfg_nfiles as usize + 1).as_ptr();
459 *cfg_files.add(cfg_nfiles as usize) = xstrdup(optarg).cast().as_ptr();
460 cfg_nfiles += 1;
461 cfg_quiet = 0;
462 }
463 b'V' => {
464 println!("tmux {}", getversion());
465 std::process::exit(0);
466 }
467 b'l' => flags |= client_flag::LOGIN,
468 b'L' => {
469 free(label as _);
470 label = xstrdup(optarg).cast().as_ptr();
471 }
472 b'N' => flags |= client_flag::NOSTARTSERVER,
473 b'q' => (),
474 b'S' => {
475 free(path as _);
476 path = xstrdup(optarg).cast().as_ptr();
477 }
478 b'T' => tty_add_features(&raw mut feat, optarg, c":,".as_ptr()),
479 b'u' => flags |= client_flag::UTF8,
480 b'v' => log_add_level(),
481 _ => usage(),
482 }
483 }
484 argc -= optind;
485 argv = argv.add(optind as usize);
486
487 if !shell_command.is_null() && argc != 0 {
488 usage();
489 }
490 if flags.intersects(client_flag::NOFORK) && argc != 0 {
491 usage();
492 }
493
494 ptm_fd = getptmfd();
495 if ptm_fd == -1 {
496 err(1, c"getptmfd".as_ptr());
497 }
498
499 if !getenv(c"TMUX".as_ptr()).is_null() {
512 flags |= client_flag::UTF8;
513 } else {
514 let mut s = getenv(c"LC_ALL".as_ptr()) as *const c_char;
515 if s.is_null() || *s == b'\0' as c_char {
516 s = getenv(c"LC_CTYPE".as_ptr()) as *const c_char;
517 }
518 if s.is_null() || *s == b'\0' as c_char {
519 s = getenv(c"LANG".as_ptr()) as *const c_char;
520 }
521 if s.is_null() || *s == b'\0' as c_char {
522 s = c"".as_ptr();
523 }
524 if !strcasestr(s, c"UTF-8".as_ptr()).is_null()
525 || !strcasestr(s, c"UTF8".as_ptr()).is_null()
526 {
527 flags |= client_flag::UTF8;
528 }
529 }
530
531 global_options = options_create(null_mut());
532 global_s_options = options_create(null_mut());
533 global_w_options = options_create(null_mut());
534
535 let mut oe: *const options_table_entry = &raw const options_table as _;
536 while !(*oe).name.is_null() {
537 if (*oe).scope & OPTIONS_TABLE_SERVER != 0 {
538 options_default(global_options, oe);
539 }
540 if (*oe).scope & OPTIONS_TABLE_SESSION != 0 {
541 options_default(global_s_options, oe);
542 }
543 if (*oe).scope & OPTIONS_TABLE_WINDOW != 0 {
544 options_default(global_w_options, oe);
545 }
546 oe = oe.add(1);
547 }
548
549 options_set_string!(
551 global_s_options,
552 c"default-shell".as_ptr(),
553 0,
554 "{}",
555 _s(getshell()),
556 );
557
558 let mut s = getenv(c"VISUAL".as_ptr());
560 if !s.is_null()
561 || ({
562 s = getenv(c"EDITOR".as_ptr());
563 !s.is_null()
564 })
565 {
566 options_set_string!(global_options, c"editor".as_ptr(), 0, "{}", _s(s));
567 if !strrchr(s, b'/' as _).is_null() {
568 s = strrchr(s, b'/' as _).add(1);
569 }
570 let keys = if !strstr(s, c"vi".as_ptr()).is_null() {
571 modekey::MODEKEY_VI
572 } else {
573 modekey::MODEKEY_EMACS
574 };
575 options_set_number(global_s_options, c"status-keys".as_ptr(), keys as _);
576 options_set_number(global_w_options, c"mode-keys".as_ptr(), keys as _);
577 }
578
579 if path.is_null() && label.is_null() {
583 s = getenv(c"TMUX".as_ptr());
584 if !s.is_null() && *s != b'\0' as c_char && *s != b',' as c_char {
585 let tmp: *mut c_char = xstrdup(s).cast().as_ptr();
586 *tmp.add(strcspn(tmp, c",".as_ptr())) = b'\0' as c_char;
587 path = tmp;
588 }
589 }
590 if path.is_null() {
591 path = make_label(label.cast(), &raw mut cause);
592 if path.is_null() {
593 if !cause.is_null() {
594 libc::fprintf(stderr, c"%s\n".as_ptr(), cause);
595 free(cause as _);
596 }
597 std::process::exit(1);
598 }
599 flags |= client_flag::DEFAULTSOCKET;
600 }
601 socket_path = path;
602 free_(label);
603
604 std::process::exit(client_main(osdep_event_init(), argc, argv, flags, feat))
606 }
607}