// Copyright 2022, The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Rust wrapper for tombstoned client. pub use ffi::DebuggerdDumpType; use std::fs::File; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; use thiserror::Error; /// Error communicating with tombstoned. #[derive(Clone, Debug, Error, Eq, PartialEq)] #[error("Error communicating with tombstoned")] pub struct Error; /// File descriptors for communicating with tombstoned. pub struct TombstonedConnection { /// The socket connection to tombstoned. /// /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds` /// or something when all we do is pass it back to C++ or close it. tombstoned_socket: File, /// The file descriptor for text output. pub text_output: Option, /// The file descriptor for proto output. pub proto_output: Option, } impl TombstonedConnection { unsafe fn from_raw_fds( tombstoned_socket: RawFd, text_output_fd: RawFd, proto_output_fd: RawFd, ) -> Self { Self { tombstoned_socket: File::from_raw_fd(tombstoned_socket), text_output: if text_output_fd >= 0 { Some(File::from_raw_fd(text_output_fd)) } else { None }, proto_output: if proto_output_fd >= 0 { Some(File::from_raw_fd(proto_output_fd)) } else { None }, } } /// Connects to tombstoned. pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result { let mut tombstoned_socket = -1; let mut text_output_fd = -1; let mut proto_output_fd = -1; if ffi::tombstoned_connect_files( pid, &mut tombstoned_socket, &mut text_output_fd, &mut proto_output_fd, dump_type, ) { Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) }) } else { Err(Error) } } /// Notifies tombstoned that the dump is complete. pub fn notify_completion(&self) -> Result<(), Error> { if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) { Ok(()) } else { Err(Error) } } } #[cxx::bridge] mod ffi { /// The type of dump. enum DebuggerdDumpType { /// A native backtrace. #[cxx_name = "kDebuggerdNativeBacktrace"] NativeBacktrace, /// A tombstone. #[cxx_name = "kDebuggerdTombstone"] Tombstone, /// A Java backtrace. #[cxx_name = "kDebuggerdJavaBacktrace"] JavaBacktrace, /// Any intercept. #[cxx_name = "kDebuggerdAnyIntercept"] AnyIntercept, /// A tombstone proto. #[cxx_name = "kDebuggerdTombstoneProto"] TombstoneProto, } unsafe extern "C++" { include!("wrapper.hpp"); type DebuggerdDumpType; fn tombstoned_connect_files( pid: i32, tombstoned_socket: &mut i32, text_output_fd: &mut i32, proto_output_fd: &mut i32, dump_type: DebuggerdDumpType, ) -> bool; fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool; } } #[cfg(test)] mod tests { use super::*; use std::{io::Write, process}; // Verify that we can connect to tombstoned, write something to the file descriptor it returns, // and notify completion, without any errors. #[test] fn test() { let connection = TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone) .expect("Failed to connect to tombstoned."); assert!(connection.proto_output.is_none()); connection .text_output .as_ref() .expect("No text output FD returned.") .write_all(b"test data") .expect("Failed to write to text output FD."); connection .notify_completion() .expect("Failed to notify completion."); } }