[binder] Add support for dump transaction to Rust
Allow Rust Binder services to define a handler for dump transactions.
Binder services that want to handle this special transaction should
override the dump method in Interface, otherwise dump transactions will
be a no-op.
Test: atest rustBinderTest
Bug: 190174171
Change-Id: Ib87ca09a8f7b8eda08f8a1bf59ad8066ea93d5a9
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 695a83e..2a09afc 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -25,6 +25,7 @@
use std::cmp::Ordering;
use std::ffi::{c_void, CStr, CString};
use std::fmt;
+use std::fs::File;
use std::marker::PhantomData;
use std::ops::Deref;
use std::os::raw::c_char;
@@ -54,6 +55,14 @@
fn as_binder(&self) -> SpIBinder {
panic!("This object was not a Binder object and cannot be converted into an SpIBinder.")
}
+
+ /// Dump transaction handler for this Binder object.
+ ///
+ /// This handler is a no-op by default and should be implemented for each
+ /// Binder service struct that wishes to respond to dump transactions.
+ fn dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+ Ok(())
+ }
}
/// Interface stability promise
@@ -98,6 +107,10 @@
/// `reply` may be [`None`] if the sender does not expect a reply.
fn on_transact(&self, code: TransactionCode, data: &Parcel, reply: &mut Parcel) -> Result<()>;
+ /// Handle a request to invoke the dump transaction on this
+ /// object.
+ fn on_dump(&self, file: &File, args: &[&CStr]) -> Result<()>;
+
/// Retrieve the class of this remote object.
///
/// This method should always return the same InterfaceClass for the same
@@ -218,7 +231,7 @@
if class.is_null() {
panic!("Expected non-null class pointer from AIBinder_Class_define!");
}
- sys::AIBinder_Class_setOnDump(class, None);
+ sys::AIBinder_Class_setOnDump(class, Some(I::on_dump));
sys::AIBinder_Class_setHandleShellCommand(class, None);
class
};
@@ -492,6 +505,16 @@
/// returned by `on_create` for this class. This function takes ownership of
/// the provided pointer and destroys it.
unsafe extern "C" fn on_destroy(object: *mut c_void);
+
+ /// Called to handle the `dump` transaction.
+ ///
+ /// # Safety
+ ///
+ /// Must be called with a non-null, valid pointer to a local `AIBinder` that
+ /// contains a `T` pointer in its user data. fd should be a non-owned file
+ /// descriptor, and args must be an array of null-terminated string
+ /// poiinters with length num_args.
+ unsafe extern "C" fn on_dump(binder: *mut sys::AIBinder, fd: i32, args: *mut *const c_char, num_args: u32) -> status_t;
}
/// Interface for transforming a generic SpIBinder into a specific remote
@@ -778,6 +801,10 @@
}
}
+ fn on_dump(&self, file: &std::fs::File, args: &[&std::ffi::CStr]) -> $crate::Result<()> {
+ self.0.dump(file, args)
+ }
+
fn get_class() -> $crate::InterfaceClass {
static CLASS_INIT: std::sync::Once = std::sync::Once::new();
static mut CLASS: Option<$crate::InterfaceClass> = None;
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index 3920129..5e324b3 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -21,9 +21,13 @@
use crate::sys;
use std::convert::TryFrom;
-use std::ffi::{c_void, CString};
+use std::ffi::{c_void, CStr, CString};
+use std::fs::File;
use std::mem::ManuallyDrop;
use std::ops::Deref;
+use std::os::raw::c_char;
+use std::os::unix::io::FromRawFd;
+use std::slice;
/// Rust wrapper around Binder remotable objects.
///
@@ -289,6 +293,37 @@
// object created by Box.
args
}
+
+ /// Called to handle the `dump` transaction.
+ ///
+ /// # Safety
+ ///
+ /// Must be called with a non-null, valid pointer to a local `AIBinder` that
+ /// contains a `T` pointer in its user data. fd should be a non-owned file
+ /// descriptor, and args must be an array of null-terminated string
+ /// poiinters with length num_args.
+ unsafe extern "C" fn on_dump(binder: *mut sys::AIBinder, fd: i32, args: *mut *const c_char, num_args: u32) -> status_t {
+ if fd < 0 {
+ return StatusCode::UNEXPECTED_NULL as status_t;
+ }
+ // We don't own this file, so we need to be careful not to drop it.
+ let file = ManuallyDrop::new(File::from_raw_fd(fd));
+
+ if args.is_null() {
+ return StatusCode::UNEXPECTED_NULL as status_t;
+ }
+ let args = slice::from_raw_parts(args, num_args as usize);
+ let args: Vec<_> = args.iter().map(|s| CStr::from_ptr(*s)).collect();
+
+ let object = sys::AIBinder_getUserData(binder);
+ let binder: &T = &*(object as *const T);
+ let res = binder.on_dump(&file, &args);
+
+ match res {
+ Ok(()) => 0,
+ Err(e) => e as status_t,
+ }
+ }
}
impl<T: Remotable> Drop for Binder<T> {
@@ -409,6 +444,10 @@
Ok(())
}
+ fn on_dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+ Ok(())
+ }
+
binder_fn_get_class!(Binder::<Self>);
}
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 10b77f4..da8907d 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -23,6 +23,9 @@
FIRST_CALL_TRANSACTION,
};
use std::convert::{TryFrom, TryInto};
+use std::ffi::CStr;
+use std::fs::File;
+use std::sync::Mutex;
/// Name of service runner.
///
@@ -50,13 +53,11 @@
let extension_name = args.next();
{
- let mut service = Binder::new(BnTest(Box::new(TestService {
- s: service_name.clone(),
- })));
+ let mut service = Binder::new(BnTest(Box::new(TestService::new(&service_name))));
service.set_requesting_sid(true);
if let Some(extension_name) = extension_name {
let extension =
- BnTest::new_binder(TestService { s: extension_name }, BinderFeatures::default());
+ BnTest::new_binder(TestService::new(&extension_name), BinderFeatures::default());
service
.set_extension(&mut extension.as_binder())
.expect("Could not add extension");
@@ -80,14 +81,24 @@
));
}
-#[derive(Clone)]
struct TestService {
s: String,
+ dump_args: Mutex<Vec<String>>,
+}
+
+impl TestService {
+ fn new(s: &str) -> Self {
+ Self {
+ s: s.to_string(),
+ dump_args: Mutex::new(Vec::new()),
+ }
+ }
}
#[repr(u32)]
enum TestTransactionCode {
Test = FIRST_CALL_TRANSACTION,
+ GetDumpArgs,
GetSelinuxContext,
}
@@ -97,6 +108,7 @@
fn try_from(c: u32) -> Result<Self, Self::Error> {
match c {
_ if c == TestTransactionCode::Test as u32 => Ok(TestTransactionCode::Test),
+ _ if c == TestTransactionCode::GetDumpArgs as u32 => Ok(TestTransactionCode::GetDumpArgs),
_ if c == TestTransactionCode::GetSelinuxContext as u32 => {
Ok(TestTransactionCode::GetSelinuxContext)
}
@@ -105,13 +117,24 @@
}
}
-impl Interface for TestService {}
+impl Interface for TestService {
+ fn dump(&self, _file: &File, args: &[&CStr]) -> binder::Result<()> {
+ let mut dump_args = self.dump_args.lock().unwrap();
+ dump_args.extend(args.iter().map(|s| s.to_str().unwrap().to_owned()));
+ Ok(())
+ }
+}
impl ITest for TestService {
fn test(&self) -> binder::Result<String> {
Ok(self.s.clone())
}
+ fn get_dump_args(&self) -> binder::Result<Vec<String>> {
+ let args = self.dump_args.lock().unwrap().clone();
+ Ok(args)
+ }
+
fn get_selinux_context(&self) -> binder::Result<String> {
let sid =
ThreadState::with_calling_sid(|sid| sid.map(|s| s.to_string_lossy().into_owned()));
@@ -124,6 +147,9 @@
/// Returns a test string
fn test(&self) -> binder::Result<String>;
+ /// Return the arguments sent via dump
+ fn get_dump_args(&self) -> binder::Result<Vec<String>>;
+
/// Returns the caller's SELinux context
fn get_selinux_context(&self) -> binder::Result<String>;
}
@@ -145,6 +171,7 @@
) -> binder::Result<()> {
match code.try_into()? {
TestTransactionCode::Test => reply.write(&service.test()?),
+ TestTransactionCode::GetDumpArgs => reply.write(&service.get_dump_args()?),
TestTransactionCode::GetSelinuxContext => reply.write(&service.get_selinux_context()?),
}
}
@@ -157,6 +184,13 @@
reply.read()
}
+ fn get_dump_args(&self) -> binder::Result<Vec<String>> {
+ let reply =
+ self.binder
+ .transact(TestTransactionCode::GetDumpArgs as TransactionCode, 0, |_| Ok(()))?;
+ reply.read()
+ }
+
fn get_selinux_context(&self) -> binder::Result<String> {
let reply = self.binder.transact(
TestTransactionCode::GetSelinuxContext as TransactionCode,
@@ -172,6 +206,10 @@
self.0.test()
}
+ fn get_dump_args(&self) -> binder::Result<Vec<String>> {
+ self.0.get_dump_args()
+ }
+
fn get_selinux_context(&self) -> binder::Result<String> {
self.0.get_selinux_context()
}
@@ -432,18 +470,22 @@
{
let _process = ScopedServiceProcess::new(service_name);
- let mut remote = binder::get_service(service_name);
+ let test_client: Strong<dyn ITest> =
+ binder::get_interface(service_name)
+ .expect("Did not get test binder service");
+ let mut remote = test_client.as_binder();
assert!(remote.is_binder_alive());
remote.ping_binder().expect("Could not ping remote service");
- // We're not testing the output of dump here, as that's really a
- // property of the C++ implementation. There is the risk that the
- // method just does nothing, but we don't want to depend on any
- // particular output from the underlying library.
+ let dump_args = ["dump", "args", "for", "testing"];
+
let null_out = File::open("/dev/null").expect("Could not open /dev/null");
remote
- .dump(&null_out, &[])
+ .dump(&null_out, &dump_args)
.expect("Could not dump remote service");
+
+ let remote_args = test_client.get_dump_args().expect("Could not fetched dumped args");
+ assert_eq!(dump_args, remote_args[..], "Remote args don't match call to dump");
}
// get/set_extensions is tested in test_extensions()
@@ -504,9 +546,7 @@
/// rust_ndk_interop.rs
#[test]
fn associate_existing_class() {
- let service = Binder::new(BnTest(Box::new(TestService {
- s: "testing_service".to_string(),
- })));
+ let service = Binder::new(BnTest(Box::new(TestService::new("testing_service"))));
// This should succeed although we will have to treat the service as
// remote.
@@ -520,9 +560,7 @@
fn reassociate_rust_binder() {
let service_name = "testing_service";
let service_ibinder = BnTest::new_binder(
- TestService {
- s: service_name.to_string(),
- },
+ TestService::new(service_name),
BinderFeatures::default(),
)
.as_binder();
@@ -538,9 +576,7 @@
fn weak_binder_upgrade() {
let service_name = "testing_service";
let service = BnTest::new_binder(
- TestService {
- s: service_name.to_string(),
- },
+ TestService::new(service_name),
BinderFeatures::default(),
);
@@ -556,9 +592,7 @@
let service_name = "testing_service";
let weak = {
let service = BnTest::new_binder(
- TestService {
- s: service_name.to_string(),
- },
+ TestService::new(service_name),
BinderFeatures::default(),
);
@@ -572,9 +606,7 @@
fn weak_binder_clone() {
let service_name = "testing_service";
let service = BnTest::new_binder(
- TestService {
- s: service_name.to_string(),
- },
+ TestService::new(service_name),
BinderFeatures::default(),
);
@@ -593,15 +625,11 @@
#[allow(clippy::eq_op)]
fn binder_ord() {
let service1 = BnTest::new_binder(
- TestService {
- s: "testing_service1".to_string(),
- },
+ TestService::new("testing_service1"),
BinderFeatures::default(),
);
let service2 = BnTest::new_binder(
- TestService {
- s: "testing_service2".to_string(),
- },
+ TestService::new("testing_service2"),
BinderFeatures::default(),
);