xref: /aosp_15_r20/system/core/debuggerd/rust/tombstoned_client/src/lib.rs (revision 00c7fec1bb09f3284aad6a6f96d2f63dfc3650ad)
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 {
42     /// # Safety
43     ///
44     /// The file descriptors must be valid and open.
from_raw_fds( tombstoned_socket: RawFd, text_output_fd: RawFd, proto_output_fd: RawFd, ) -> Self45     unsafe fn from_raw_fds(
46         tombstoned_socket: RawFd,
47         text_output_fd: RawFd,
48         proto_output_fd: RawFd,
49     ) -> Self {
50         Self {
51             // SAFETY: The caller guarantees that the file descriptor is valid and open.
52             tombstoned_socket: unsafe { File::from_raw_fd(tombstoned_socket) },
53             text_output: if text_output_fd >= 0 {
54                 // SAFETY: The caller guarantees that the file descriptor is valid and open.
55                 Some(unsafe { File::from_raw_fd(text_output_fd) })
56             } else {
57                 None
58             },
59             proto_output: if proto_output_fd >= 0 {
60                 // SAFETY: The caller guarantees that the file descriptor is valid and open.
61                 Some(unsafe { File::from_raw_fd(proto_output_fd) })
62             } else {
63                 None
64             },
65         }
66     }
67 
68     /// Connects to tombstoned.
connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error>69     pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error> {
70         let mut tombstoned_socket = -1;
71         let mut text_output_fd = -1;
72         let mut proto_output_fd = -1;
73         if ffi::tombstoned_connect_files(
74             pid,
75             &mut tombstoned_socket,
76             &mut text_output_fd,
77             &mut proto_output_fd,
78             dump_type,
79         ) {
80             // SAFETY: If tombstoned_connect_files returns successfully then they file descriptors
81             // are valid and open.
82             Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) })
83         } else {
84             Err(Error)
85         }
86     }
87 
88     /// Notifies tombstoned that the dump is complete.
notify_completion(&self) -> Result<(), Error>89     pub fn notify_completion(&self) -> Result<(), Error> {
90         if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) {
91             Ok(())
92         } else {
93             Err(Error)
94         }
95     }
96 }
97 
98 #[cxx::bridge]
99 mod ffi {
100     /// The type of dump.
101     enum DebuggerdDumpType {
102         /// A native backtrace.
103         #[cxx_name = "kDebuggerdNativeBacktrace"]
104         NativeBacktrace,
105         /// A tombstone.
106         #[cxx_name = "kDebuggerdTombstone"]
107         Tombstone,
108         /// A Java backtrace.
109         #[cxx_name = "kDebuggerdJavaBacktrace"]
110         JavaBacktrace,
111         /// Any intercept.
112         #[cxx_name = "kDebuggerdAnyIntercept"]
113         AnyIntercept,
114         /// A tombstone proto.
115         #[cxx_name = "kDebuggerdTombstoneProto"]
116         TombstoneProto,
117     }
118 
119     unsafe extern "C++" {
120         include!("wrapper.hpp");
121 
122         type DebuggerdDumpType;
123 
tombstoned_connect_files( pid: i32, tombstoned_socket: &mut i32, text_output_fd: &mut i32, proto_output_fd: &mut i32, dump_type: DebuggerdDumpType, ) -> bool124         fn tombstoned_connect_files(
125             pid: i32,
126             tombstoned_socket: &mut i32,
127             text_output_fd: &mut i32,
128             proto_output_fd: &mut i32,
129             dump_type: DebuggerdDumpType,
130         ) -> bool;
131 
tombstoned_notify_completion(tombstoned_socket: i32) -> bool132         fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool;
133     }
134 }
135 
136 #[cfg(test)]
137 mod tests {
138     use super::*;
139     use std::{io::Write, process};
140 
141     // Verify that we can connect to tombstoned, write something to the file descriptor it returns,
142     // and notify completion, without any errors.
143     #[test]
test()144     fn test() {
145         let connection =
146             TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone)
147                 .expect("Failed to connect to tombstoned.");
148 
149         assert!(connection.proto_output.is_none());
150         connection
151             .text_output
152             .as_ref()
153             .expect("No text output FD returned.")
154             .write_all(b"test data")
155             .expect("Failed to write to text output FD.");
156 
157         connection.notify_completion().expect("Failed to notify completion.");
158     }
159 }
160