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