| ... | ... | @@ -662,10 +662,143 @@ pub fn move_cursor() -> winit::window::CursorIcon { |
| 662 | 662 | /// No system border lies under the chrome. |
| 663 | 663 | pub fn cover_border_line(_: &mut Ui, _: f32) {} |
| 664 | 664 | |
| 665 | /// The log's path, and the log itself where stderr isn't it. |
| 666 | static LOG: OnceLock<(PathBuf, Option<std::fs::File>)> = OnceLock::new(); |
| 667 | |
| 668 | /// Runs the app. Where stderr reaches no one, as when the desktop starts the app, stderr goes |
| 669 | /// to `snowbound.log`; panics and the signals that end the process log a backtrace there |
| 670 | /// either way, and say where the log is. |
| 665 | 671 | pub fn with_pool<R>(run: impl FnOnce() -> R) -> R { |
| 672 | log_crashes(); |
| 666 | 673 | run() |
| 667 | 674 | } |
| 668 | 675 | |
| 676 | /// Opens `snowbound.log` in the state folder, keeping the last run's as `snowbound.old.log`, |
| 677 | /// and takes stderr there unless it reaches a terminal, a pipe or a file. |
| 678 | fn log_crashes() { |
| 679 | use std::{io::Write, os::fd::AsRawFd}; |
| 680 | let Some(folder) = xdg_dir("XDG_STATE_HOME", ".local/state") else { |
| 681 | return; |
| 682 | }; |
| 683 | let path = folder.join("snowbound.log"); |
| 684 | let _ = std::fs::create_dir_all(&folder); |
| 685 | let _ = std::fs::rename(&path, folder.join("snowbound.old.log")); |
| 686 | let Ok(mut log) = std::fs::File::create(&path) else { |
| 687 | return; |
| 688 | }; |
| 689 | let mut system: libc::utsname = unsafe { std::mem::zeroed() }; |
| 690 | unsafe { libc::uname(&mut system) }; |
| 691 | let field = |
| 692 | |field: &[libc::c_char]| unsafe { CStr::from_ptr(field.as_ptr()) }.to_string_lossy(); |
| 693 | let session = |name| std::env::var(name).unwrap_or_default(); |
| 694 | let _ = writeln!( |
| 695 | log, |
| 696 | "Snowbound {} on Linux {} {}, {} {}", |
| 697 | option_env!("SNOWBOUND_BUILD").unwrap_or("development"), |
| 698 | field(&system.release), |
| 699 | field(&system.machine), |
| 700 | session("XDG_SESSION_TYPE"), |
| 701 | session("XDG_CURRENT_DESKTOP"), |
| 702 | ); |
| 703 | let mut stderr: libc::stat = unsafe { std::mem::zeroed() }; |
| 704 | let seen = unsafe { libc::isatty(2) } == 1 |
| 705 | || unsafe { libc::fstat(2, &mut stderr) } == 0 |
| 706 | && [libc::S_IFIFO, libc::S_IFREG].contains(&(stderr.st_mode & libc::S_IFMT)); |
| 707 | let copy = if seen { |
| 708 | Some(log) |
| 709 | } else { |
| 710 | unsafe { libc::dup2(log.as_raw_fd(), 2) }; |
| 711 | None |
| 712 | }; |
| 713 | let _ = LOG.set((path, copy)); |
| 714 | std::panic::set_hook(Box::new(|info| { |
| 715 | let thread = std::thread::current(); |
| 716 | let backtrace = std::backtrace::Backtrace::force_capture(); |
| 717 | let report = format!( |
| 718 | "Thread {:?} {info}\n{backtrace}\n", |
| 719 | thread.name().unwrap_or("") |
| 720 | ); |
| 721 | crashed(|fd| write_all(fd, report.as_bytes())); |
| 722 | })); |
| 723 | // glibc's backtrace loads its unwinder on first use, which a signal handler can't. |
| 724 | unsafe { libc::backtrace([std::ptr::null_mut(); 1].as_mut_ptr(), 1) }; |
| 725 | for signal in FATAL.map(|(signal, _)| signal) { |
| 726 | let mut action: libc::sigaction = unsafe { std::mem::zeroed() }; |
| 727 | action.sa_sigaction = fatal as *const () as libc::sighandler_t; |
| 728 | action.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK | libc::SA_RESETHAND; |
| 729 | unsafe { libc::sigaction(signal, &action, std::ptr::null_mut()) }; |
| 730 | } |
| 731 | } |
| 732 | |
| 733 | const FATAL: [(i32, &str); 5] = [ |
| 734 | (libc::SIGSEGV, "SIGSEGV"), |
| 735 | (libc::SIGBUS, "SIGBUS"), |
| 736 | (libc::SIGILL, "SIGILL"), |
| 737 | (libc::SIGFPE, "SIGFPE"), |
| 738 | (libc::SIGABRT, "SIGABRT"), |
| 739 | ]; |
| 740 | |
| 741 | /// Logs the signal ending the process with the stack it arrived on, then lets it end the |
| 742 | /// process as it would have. Calls only what a signal handler may. |
| 743 | extern "C" fn fatal(signal: i32, info: *mut libc::siginfo_t, _: *mut libc::c_void) { |
| 744 | let name = FATAL |
| 745 | .iter() |
| 746 | .find(|(fatal, _)| *fatal == signal) |
| 747 | .map_or("", |(_, name)| name); |
| 748 | let mut thread = [0u8; 16]; |
| 749 | unsafe { libc::prctl(libc::PR_GET_NAME, thread.as_mut_ptr()) }; |
| 750 | let thread = &thread[..thread.iter().position(|&byte| byte == 0).unwrap_or(15)]; |
| 751 | let mut address = *b"0x0000000000000000"; |
| 752 | let mut value = unsafe { (*info).si_addr() } as usize; |
| 753 | for digit in address[2..].iter_mut().rev() { |
| 754 | *digit = b"0123456789abcdef"[value & 15]; |
| 755 | value >>= 4; |
| 756 | } |
| 757 | let mut frames = [std::ptr::null_mut(); 128]; |
| 758 | let count = unsafe { libc::backtrace(frames.as_mut_ptr(), frames.len() as i32) }; |
| 759 | crashed(|fd| { |
| 760 | let parts: [&[u8]; 7] = [ |
| 761 | b"Thread \"", |
| 762 | thread, |
| 763 | b"\" received ", |
| 764 | name.as_bytes(), |
| 765 | b" at ", |
| 766 | &address, |
| 767 | b"\n", |
| 768 | ]; |
| 769 | for part in parts { |
| 770 | write_all(fd, part); |
| 771 | } |
| 772 | unsafe { libc::backtrace_symbols_fd(frames.as_ptr(), count, fd) }; |
| 773 | write_all(fd, b"\n"); |
| 774 | }); |
| 775 | unsafe { libc::raise(signal) }; |
| 776 | } |
| 777 | |
| 778 | /// Writes a report, through `write` on a descriptor, to stderr and the log, then says where the |
| 779 | /// log is. |
| 780 | fn crashed(write: impl Fn(i32)) { |
| 781 | use std::os::{fd::AsRawFd, unix::ffi::OsStrExt}; |
| 782 | write(2); |
| 783 | if let Some((path, Some(log))) = LOG.get() { |
| 784 | write(log.as_raw_fd()); |
| 785 | let parts: [&[u8]; 3] = [b"The log is at ", path.as_os_str().as_bytes(), b".\n"]; |
| 786 | for part in parts { |
| 787 | write_all(2, part); |
| 788 | } |
| 789 | } |
| 790 | } |
| 791 | |
| 792 | fn write_all(fd: i32, mut bytes: &[u8]) { |
| 793 | while !bytes.is_empty() { |
| 794 | let written = unsafe { libc::write(fd, bytes.as_ptr().cast(), bytes.len()) }; |
| 795 | if written <= 0 { |
| 796 | return; |
| 797 | } |
| 798 | bytes = &bytes[written as usize..]; |
| 799 | } |
| 800 | } |
| 801 | |
| 669 | 802 | /// The interface keeps fontconfig's font and scrollbars overlay the content. |
| 670 | 803 | pub fn system_interface(_: &mut Ui) {} |
| 671 | 804 | |