| ... | @@ -662,10 +662,143 @@ pub fn move_cursor() -> winit::window::CursorIcon { | ... | @@ -662,10 +662,143 @@ pub fn move_cursor() -> winit::window::CursorIcon { |
| 662 | /// No system border lies under the chrome. | 662 | /// No system border lies under the chrome. |
| 663 | pub fn cover_border_line(_: &mut Ui, _: f32) {} | 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 | pub fn with_pool<R>(run: impl FnOnce() -> R) -> R { | 671 | pub fn with_pool<R>(run: impl FnOnce() -> R) -> R { |
| | 672 | log_crashes(); |
| 666 | run() | 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 | /// The interface keeps fontconfig's font and scrollbars overlay the content. | 802 | /// The interface keeps fontconfig's font and scrollbars overlay the content. |
| 670 | pub fn system_interface(_: &mut Ui) {} | 803 | pub fn system_interface(_: &mut Ui) {} |
| 671 | | 804 | |