1 // Copyright 2022, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Rust wrapper for tombstoned client.
16 
17 pub use ffi::DebuggerdDumpType;
18 use std::fs::File;
19 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
20 use thiserror::Error;
21 
22 /// Error communicating with tombstoned.
23 #[derive(Clone, Debug, Error, Eq, PartialEq)]
24 #[error("Error communicating with tombstoned")]
25 pub struct Error;
26 
27 /// File descriptors for communicating with tombstoned.
28 pub struct TombstonedConnection {
29     /// The socket connection to tombstoned.
30     ///
31     /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library
32     /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds`
33     /// or something when all we do is pass it back to C++ or close it.
34     tombstoned_socket: File,
35     /// The file descriptor for text output.
36     pub text_output: Option<File>,
37     /// The file descriptor for proto output.
38     pub proto_output: Option<File>,
39 }
40 
41 impl TombstonedConnection {
from_raw_fds( tombstoned_socket: RawFd, text_output_fd: RawFd, proto_output_fd: RawFd, ) -> Self42     unsafe fn from_raw_fds(
43         tombstoned_socket: RawFd,
44         text_output_fd: RawFd,
45         proto_output_fd: RawFd,
46     ) -> Self {
47         Self {
48             tombstoned_socket: File::from_raw_fd(tombstoned_socket),
49             text_output: if text_output_fd >= 0 {
50                 Some(File::from_raw_fd(text_output_fd))
51             } else {
52                 None
53             },
54             proto_output: if proto_output_fd >= 0 {
55                 Some(File::from_raw_fd(proto_output_fd))
56             } else {
57                 None
58             },
59         }
60     }
61 
62     /// Connects to tombstoned.
connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error>63     pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error> {
64         let mut tombstoned_socket = -1;
65         let mut text_output_fd = -1;
66         let mut proto_output_fd = -1;
67         if ffi::tombstoned_connect_files(
68             pid,
69             &mut tombstoned_socket,
70             &mut text_output_fd,
71             &mut proto_output_fd,
72             dump_type,
73         ) {
74             Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) })
75         } else {
76             Err(Error)
77         }
78     }
79 
80     /// Notifies tombstoned that the dump is complete.
notify_completion(&self) -> Result<(), Error>81     pub fn notify_completion(&self) -> Result<(), Error> {
82         if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) {
83             Ok(())
84         } else {
85             Err(Error)
86         }
87     }
88 }
89 
90 #[cxx::bridge]
91 mod ffi {
92     /// The type of dump.
93     enum DebuggerdDumpType {
94         /// A native backtrace.
95         #[cxx_name = "kDebuggerdNativeBacktrace"]
96         NativeBacktrace,
97         /// A tombstone.
98         #[cxx_name = "kDebuggerdTombstone"]
99         Tombstone,
100         /// A Java backtrace.
101         #[cxx_name = "kDebuggerdJavaBacktrace"]
102         JavaBacktrace,
103         /// Any intercept.
104         #[cxx_name = "kDebuggerdAnyIntercept"]
105         AnyIntercept,
106         /// A tombstone proto.
107         #[cxx_name = "kDebuggerdTombstoneProto"]
108         TombstoneProto,
109     }
110 
111     unsafe extern "C++" {
112         include!("wrapper.hpp");
113 
114         type DebuggerdDumpType;
115 
tombstoned_connect_files( pid: i32, tombstoned_socket: &mut i32, text_output_fd: &mut i32, proto_output_fd: &mut i32, dump_type: DebuggerdDumpType, ) -> bool116         fn tombstoned_connect_files(
117             pid: i32,
118             tombstoned_socket: &mut i32,
119             text_output_fd: &mut i32,
120             proto_output_fd: &mut i32,
121             dump_type: DebuggerdDumpType,
122         ) -> bool;
123 
tombstoned_notify_completion(tombstoned_socket: i32) -> bool124         fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool;
125     }
126 }
127 
128 #[cfg(test)]
129 mod tests {
130     use super::*;
131     use std::{io::Write, process};
132 
133     // Verify that we can connect to tombstoned, write something to the file descriptor it returns,
134     // and notify completion, without any errors.
135     #[test]
test()136     fn test() {
137         let connection =
138             TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone)
139                 .expect("Failed to connect to tombstoned.");
140 
141         assert!(connection.proto_output.is_none());
142         connection
143             .text_output
144             .as_ref()
145             .expect("No text output FD returned.")
146             .write_all(b"test data")
147             .expect("Failed to write to text output FD.");
148 
149         connection
150             .notify_completion()
151             .expect("Failed to notify completion.");
152     }
153 }
154