| // Copyright 2019 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use std::env; |
| use std::fs::File; |
| use std::io::{stderr, Read}; |
| use std::os::unix::io::{FromRawFd, IntoRawFd}; |
| use std::panic::{self, PanicInfo}; |
| use std::process::abort; |
| use std::string::String; |
| |
| use libc::{close, dup, dup2, pipe2, O_NONBLOCK, STDERR_FILENO}; |
| use sys_util::error; |
| |
| // Opens a pipe and puts the write end into the stderr FD slot. On success, returns the read end of |
| // the pipe and the old stderr as a pair of files. |
| fn redirect_stderr() -> Option<(File, File)> { |
| let mut fds = [-1, -1]; |
| unsafe { |
| // Trivially safe because the return value is checked. |
| let old_stderr = dup(STDERR_FILENO); |
| if old_stderr == -1 { |
| return None; |
| } |
| // Safe because pipe2 will only ever write two integers to our array and we check output. |
| let mut ret = pipe2(fds.as_mut_ptr(), O_NONBLOCK); |
| if ret != 0 { |
| // Leaks FDs, but not important right before abort. |
| return None; |
| } |
| // Safe because the FD we are duplicating is owned by us. |
| ret = dup2(fds[1], STDERR_FILENO); |
| if ret == -1 { |
| // Leaks FDs, but not important right before abort. |
| return None; |
| } |
| // The write end is no longer needed. |
| close(fds[1]); |
| // Safe because each of the fds was the result of a successful FD creation syscall. |
| Some((File::from_raw_fd(fds[0]), File::from_raw_fd(old_stderr))) |
| } |
| } |
| |
| // Sets stderr to the given file. Returns true on success. |
| fn restore_stderr(stderr: File) -> bool { |
| let fd = stderr.into_raw_fd(); |
| |
| // Safe because fd is guaranteed to be valid and replacing stderr should be an atomic operation. |
| unsafe { dup2(fd, STDERR_FILENO) != -1 } |
| } |
| |
| // Sends as much information about the panic as possible to syslog. |
| fn log_panic_info(default_panic: &(dyn Fn(&PanicInfo) + Sync + Send + 'static), info: &PanicInfo) { |
| // Grab a lock of stderr to prevent concurrent threads from trampling on our stderr capturing |
| // procedure. The default_panic procedure likely uses stderr.lock as well, but the mutex inside |
| // stderr is reentrant, so it will not dead-lock on this thread. |
| let stderr = stderr(); |
| let _stderr_lock = stderr.lock(); |
| |
| // Redirect stderr to a pipe we can read from later. |
| let (mut read_file, old_stderr) = match redirect_stderr() { |
| Some(f) => f, |
| None => { |
| error!("failed to capture stderr during panic"); |
| return; |
| } |
| }; |
| // Only through the default panic handler can we get a stacktrace. It only ever prints to |
| // stderr, hence all the previous code to redirect it to a pipe we can read. |
| env::set_var("RUST_BACKTRACE", "1"); |
| default_panic(info); |
| |
| // Closes the write end of the pipe so that we can reach EOF in read_to_string. Also allows |
| // others to write to stderr without failure. |
| if !restore_stderr(old_stderr) { |
| error!("failed to restore stderr during panic"); |
| return; |
| } |
| drop(_stderr_lock); |
| |
| let mut panic_output = String::new(); |
| // Ignore errors and print what we got. |
| let _ = read_file.read_to_string(&mut panic_output); |
| // Split by line because the logging facilities do not handle embedded new lines well. |
| for line in panic_output.lines() { |
| error!("{}", line); |
| } |
| } |
| |
| /// The intent of our panic hook is to get panic info and a stacktrace into the syslog, even for |
| /// jailed subprocesses. It will always abort on panic to ensure a minidump is generated. |
| /// |
| /// Note that jailed processes will usually have a stacktrace of <unknown> because the backtrace |
| /// routines attempt to open this binary and are unable to do so in a jail. |
| pub fn set_panic_hook() { |
| let default_panic = panic::take_hook(); |
| panic::set_hook(Box::new(move |info| { |
| log_panic_info(default_panic.as_ref(), info); |
| // Abort to trigger the crash reporter so that a minidump is generated. |
| abort(); |
| })); |
| } |