blob: 5c8abef2cdb84dffabd64875480c3b8d2a2c271c [file] [log] [blame]
// 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<File>,
/// The file descriptor for proto output.
pub proto_output: Option<File>,
}
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<Self, Error> {
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.");
}
}