/* * Copyright (C) 2020 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. */ use crate::binder::AsNative; use crate::sys; use std::error; use std::ffi::{CStr, CString}; use std::fmt::{Debug, Display, Formatter, Result as FmtResult}; use std::ptr; use std::result; pub use sys::binder_status_t as status_t; /// Low-level status codes from Android `libutils`. // All error codes are negative integer values. Derived from the anonymous enum // in utils/Errors.h pub use sys::android_c_interface_StatusCode as StatusCode; /// A specialized [`Result`](result::Result) for binder operations. pub type Result = result::Result; /// Convert a low-level status code into an empty result. /// /// An OK status is converted into an `Ok` result, any other status is converted /// into an `Err` result holding the status code. pub fn status_result(status: status_t) -> Result<()> { match parse_status_code(status) { StatusCode::OK => Ok(()), e => Err(e), } } fn parse_status_code(code: i32) -> StatusCode { match code { e if e == StatusCode::OK as i32 => StatusCode::OK, e if e == StatusCode::NO_MEMORY as i32 => StatusCode::NO_MEMORY, e if e == StatusCode::INVALID_OPERATION as i32 => StatusCode::INVALID_OPERATION, e if e == StatusCode::BAD_VALUE as i32 => StatusCode::BAD_VALUE, e if e == StatusCode::BAD_TYPE as i32 => StatusCode::BAD_TYPE, e if e == StatusCode::NAME_NOT_FOUND as i32 => StatusCode::NAME_NOT_FOUND, e if e == StatusCode::PERMISSION_DENIED as i32 => StatusCode::PERMISSION_DENIED, e if e == StatusCode::NO_INIT as i32 => StatusCode::NO_INIT, e if e == StatusCode::ALREADY_EXISTS as i32 => StatusCode::ALREADY_EXISTS, e if e == StatusCode::DEAD_OBJECT as i32 => StatusCode::DEAD_OBJECT, e if e == StatusCode::FAILED_TRANSACTION as i32 => StatusCode::FAILED_TRANSACTION, e if e == StatusCode::BAD_INDEX as i32 => StatusCode::BAD_INDEX, e if e == StatusCode::NOT_ENOUGH_DATA as i32 => StatusCode::NOT_ENOUGH_DATA, e if e == StatusCode::WOULD_BLOCK as i32 => StatusCode::WOULD_BLOCK, e if e == StatusCode::TIMED_OUT as i32 => StatusCode::TIMED_OUT, e if e == StatusCode::UNKNOWN_TRANSACTION as i32 => StatusCode::UNKNOWN_TRANSACTION, e if e == StatusCode::FDS_NOT_ALLOWED as i32 => StatusCode::FDS_NOT_ALLOWED, e if e == StatusCode::UNEXPECTED_NULL as i32 => StatusCode::UNEXPECTED_NULL, _ => StatusCode::UNKNOWN_ERROR, } } pub use sys::android_c_interface_ExceptionCode as ExceptionCode; fn parse_exception_code(code: i32) -> ExceptionCode { match code { e if e == ExceptionCode::NONE as i32 => ExceptionCode::NONE, e if e == ExceptionCode::SECURITY as i32 => ExceptionCode::SECURITY, e if e == ExceptionCode::BAD_PARCELABLE as i32 => ExceptionCode::BAD_PARCELABLE, e if e == ExceptionCode::ILLEGAL_ARGUMENT as i32 => ExceptionCode::ILLEGAL_ARGUMENT, e if e == ExceptionCode::NULL_POINTER as i32 => ExceptionCode::NULL_POINTER, e if e == ExceptionCode::ILLEGAL_STATE as i32 => ExceptionCode::ILLEGAL_STATE, e if e == ExceptionCode::NETWORK_MAIN_THREAD as i32 => ExceptionCode::NETWORK_MAIN_THREAD, e if e == ExceptionCode::UNSUPPORTED_OPERATION as i32 => { ExceptionCode::UNSUPPORTED_OPERATION } e if e == ExceptionCode::SERVICE_SPECIFIC as i32 => ExceptionCode::SERVICE_SPECIFIC, _ => ExceptionCode::TRANSACTION_FAILED, } } // Safety: `Status` always contains a owning pointer to a valid `AStatus`. The // lifetime of the contained pointer is the same as the `Status` object. /// High-level binder status object that encapsulates a standard way to keep /// track of and chain binder errors along with service specific errors. /// /// Used in AIDL transactions to represent failed transactions. pub struct Status(ptr::NonNull); // Safety: The `AStatus` that the `Status` points to must have an entirely thread-safe API for the // duration of the `Status` object's lifetime. We ensure this by not allowing mutation of a `Status` // in Rust, and the NDK API says we're the owner of our `AStatus` objects so outside code should not // be mutating them underneath us. unsafe impl Sync for Status {} // Safety: `Status` always contains an owning pointer to a global, immutable, interned `AStatus`. // A thread-local `AStatus` would not be valid. unsafe impl Send for Status {} fn to_cstring>(message: T) -> Option { CString::new(message.as_ref()).ok() } impl Status { /// Create a status object representing a successful transaction. pub fn ok() -> Self { // Safety: `AStatus_newOk` always returns a new, heap allocated // pointer to an `ASTatus` object, so we know this pointer will be // valid. // // Rust takes ownership of the returned pointer. let ptr = unsafe { sys::AStatus_newOk() }; Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer")) } /// Create a status object from a service specific error pub fn new_service_specific_error(err: i32, message: Option<&CStr>) -> Status { let ptr = if let Some(message) = message { // Safety: Any i32 is a valid service specific error for the // error code parameter. We construct a valid, null-terminated // `CString` from the message, which must be a valid C-style // string to pass as the message. This function always returns a // new, heap allocated pointer to an `AStatus` object, so we // know the returned pointer will be valid. // // Rust takes ownership of the returned pointer. unsafe { sys::AStatus_fromServiceSpecificErrorWithMessage(err, message.as_ptr()) } } else { // Safety: Any i32 is a valid service specific error for the // error code parameter. This function always returns a new, // heap allocated pointer to an `AStatus` object, so we know the // returned pointer will be valid. // // Rust takes ownership of the returned pointer. unsafe { sys::AStatus_fromServiceSpecificError(err) } }; Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer")) } /// Creates a status object from a service specific error. pub fn new_service_specific_error_str>(err: i32, message: Option) -> Status { Self::new_service_specific_error(err, message.and_then(to_cstring).as_deref()) } /// Create a status object from an exception code pub fn new_exception(exception: ExceptionCode, message: Option<&CStr>) -> Status { if let Some(message) = message { // Safety: the C string pointer is valid and not retained by the // function. let ptr = unsafe { sys::AStatus_fromExceptionCodeWithMessage(exception as i32, message.as_ptr()) }; Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer")) } else { exception.into() } } /// Creates a status object from an exception code and message. pub fn new_exception_str>( exception: ExceptionCode, message: Option, ) -> Status { Self::new_exception(exception, message.and_then(to_cstring).as_deref()) } /// Create a status object from a raw `AStatus` pointer. /// /// # Safety /// /// This constructor is safe iff `ptr` is a valid pointer to an `AStatus`. pub(crate) unsafe fn from_ptr(ptr: *mut sys::AStatus) -> Self { Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer")) } /// Returns `true` if this status represents a successful transaction. pub fn is_ok(&self) -> bool { // Safety: `Status` always contains a valid `AStatus` pointer, so we // are always passing a valid pointer to `AStatus_isOk` here. unsafe { sys::AStatus_isOk(self.as_native()) } } /// Returns a description of the status. pub fn get_description(&self) -> String { // Safety: `Status` always contains a valid `AStatus` pointer, so we // are always passing a valid pointer to `AStatus_getDescription` // here. // // `AStatus_getDescription` always returns a valid pointer to a null // terminated C string. Rust is responsible for freeing this pointer // via `AStatus_deleteDescription`. let description_ptr = unsafe { sys::AStatus_getDescription(self.as_native()) }; // Safety: `AStatus_getDescription` always returns a valid C string, // which can be safely converted to a `CStr`. let description = unsafe { CStr::from_ptr(description_ptr) }; let description = description.to_string_lossy().to_string(); // Safety: `description_ptr` was returned from // `AStatus_getDescription` above, and must be freed via // `AStatus_deleteDescription`. We must not access the pointer after // this call, so we copy it into an owned string above and return // that string. unsafe { sys::AStatus_deleteDescription(description_ptr); } description } /// Returns the exception code of the status. pub fn exception_code(&self) -> ExceptionCode { // Safety: `Status` always contains a valid `AStatus` pointer, so we // are always passing a valid pointer to `AStatus_getExceptionCode` // here. let code = unsafe { sys::AStatus_getExceptionCode(self.as_native()) }; parse_exception_code(code) } /// Return a status code representing a transaction failure, or /// `StatusCode::OK` if there was no transaction failure. /// /// If this method returns `OK`, the status may still represent a different /// exception or a service specific error. To find out if this transaction /// as a whole is okay, use [`is_ok`](Self::is_ok) instead. pub fn transaction_error(&self) -> StatusCode { // Safety: `Status` always contains a valid `AStatus` pointer, so we // are always passing a valid pointer to `AStatus_getStatus` here. let code = unsafe { sys::AStatus_getStatus(self.as_native()) }; parse_status_code(code) } /// Return a service specific error if this status represents one. /// /// This function will only ever return a non-zero result if /// [`exception_code`](Self::exception_code) returns /// `ExceptionCode::SERVICE_SPECIFIC`. If this function returns 0, the /// status object may still represent a different exception or status. To /// find out if this transaction as a whole is okay, use /// [`is_ok`](Self::is_ok) instead. pub fn service_specific_error(&self) -> i32 { // Safety: `Status` always contains a valid `AStatus` pointer, so we // are always passing a valid pointer to // `AStatus_getServiceSpecificError` here. unsafe { sys::AStatus_getServiceSpecificError(self.as_native()) } } /// Calls `op` if the status was ok, otherwise returns an `Err` value of /// `self`. pub fn and_then(self, op: F) -> result::Result where F: FnOnce() -> result::Result, { >::from(self)?; op() } } impl error::Error for Status {} impl Display for Status { fn fmt(&self, f: &mut Formatter) -> FmtResult { f.write_str(&self.get_description()) } } impl Debug for Status { fn fmt(&self, f: &mut Formatter) -> FmtResult { f.write_str(&self.get_description()) } } impl PartialEq for Status { fn eq(&self, other: &Status) -> bool { let self_code = self.exception_code(); let other_code = other.exception_code(); match (self_code, other_code) { (ExceptionCode::NONE, ExceptionCode::NONE) => true, (ExceptionCode::TRANSACTION_FAILED, ExceptionCode::TRANSACTION_FAILED) => { self.transaction_error() == other.transaction_error() && self.get_description() == other.get_description() } (ExceptionCode::SERVICE_SPECIFIC, ExceptionCode::SERVICE_SPECIFIC) => { self.service_specific_error() == other.service_specific_error() && self.get_description() == other.get_description() } (e1, e2) => e1 == e2 && self.get_description() == other.get_description(), } } } impl Eq for Status {} impl From for Status { fn from(status: StatusCode) -> Status { (status as status_t).into() } } impl From for Status { fn from(status: status_t) -> Status { // Safety: `AStatus_fromStatus` expects any `status_t` integer, so // this is a safe FFI call. Unknown values will be coerced into // UNKNOWN_ERROR. let ptr = unsafe { sys::AStatus_fromStatus(status) }; Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer")) } } impl From for Status { fn from(code: ExceptionCode) -> Status { // Safety: `AStatus_fromExceptionCode` expects any // `binder_exception_t` (i32) integer, so this is a safe FFI call. // Unknown values will be coerced into EX_TRANSACTION_FAILED. let ptr = unsafe { sys::AStatus_fromExceptionCode(code as i32) }; Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer")) } } // TODO: impl Try for Status when try_trait is stabilized // https://github.com/rust-lang/rust/issues/42327 impl From for result::Result<(), Status> { fn from(status: Status) -> result::Result<(), Status> { if status.is_ok() { Ok(()) } else { Err(status) } } } impl From for status_t { fn from(status: Status) -> status_t { status.transaction_error() as status_t } } impl Drop for Status { fn drop(&mut self) { // Safety: `Status` manages the lifetime of its inner `AStatus` // pointee, so we need to delete it here. We know that the pointer // will be valid here since `Status` always contains a valid pointer // while it is alive. unsafe { sys::AStatus_delete(self.0.as_mut()); } } } /// Safety: `Status` always contains a valid pointer to an `AStatus` object, so /// we can trivially convert it to a correctly-typed raw pointer. /// /// Care must be taken that the returned pointer is only dereferenced while the /// `Status` object is still alive. unsafe impl AsNative for Status { fn as_native(&self) -> *const sys::AStatus { self.0.as_ptr() } fn as_native_mut(&mut self) -> *mut sys::AStatus { // Safety: The pointer will be valid here since `Status` always contains // a valid and initialized pointer while it is alive. unsafe { self.0.as_mut() } } } /// A conversion from `std::result::Result` to `binder::Result`. If this type is `Ok(T)`, /// it's returned as is. If this type is `Err(E)`, `E` is converted into `Status` which can be /// either a general binder exception, or a service-specific exception. /// /// # Examples /// /// ``` /// // std::io::Error is formatted as the exception's message /// fn file_exists(name: &str) -> binder::Result { /// std::fs::metadata(name) /// .or_service_specific_exception(NOT_FOUND)? /// } /// /// // A custom function is used to create the exception's message /// fn file_exists(name: &str) -> binder::Result { /// std::fs::metadata(name) /// .or_service_specific_exception_with(NOT_FOUND, /// |e| format!("file {} not found: {:?}", name, e))? /// } /// /// // anyhow::Error is formatted as the exception's message /// use anyhow::{Context, Result}; /// fn file_exists(name: &str) -> binder::Result { /// std::fs::metadata(name) /// .context("file {} not found") /// .or_service_specific_exception(NOT_FOUND)? /// } /// /// // General binder exceptions can be created similarly /// fn file_exists(name: &str) -> binder::Result { /// std::fs::metadata(name) /// .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)? /// } /// ``` pub trait IntoBinderResult { /// Converts the embedded error into a general binder exception of code `exception`. The /// message of the exception is set by formatting the error for debugging. fn or_binder_exception(self, exception: ExceptionCode) -> result::Result; /// Converts the embedded error into a general binder exception of code `exception`. The /// message of the exception is set by lazily evaluating the `op` function. fn or_binder_exception_with, O: FnOnce(E) -> M>( self, exception: ExceptionCode, op: O, ) -> result::Result; /// Converts the embedded error into a service-specific binder exception. `error_code` is used /// to distinguish different service-specific binder exceptions. The message of the exception /// is set by formatting the error for debugging. fn or_service_specific_exception(self, error_code: i32) -> result::Result; /// Converts the embedded error into a service-specific binder exception. `error_code` is used /// to distinguish different service-specific binder exceptions. The message of the exception /// is set by lazily evaluating the `op` function. fn or_service_specific_exception_with, O: FnOnce(E) -> M>( self, error_code: i32, op: O, ) -> result::Result; } impl IntoBinderResult for result::Result { fn or_binder_exception(self, exception: ExceptionCode) -> result::Result { self.or_binder_exception_with(exception, |e| format!("{:?}", e)) } fn or_binder_exception_with, O: FnOnce(E) -> M>( self, exception: ExceptionCode, op: O, ) -> result::Result { self.map_err(|e| Status::new_exception_str(exception, Some(op(e)))) } fn or_service_specific_exception(self, error_code: i32) -> result::Result { self.or_service_specific_exception_with(error_code, |e| format!("{:?}", e)) } fn or_service_specific_exception_with, O: FnOnce(E) -> M>( self, error_code: i32, op: O, ) -> result::Result { self.map_err(|e| Status::new_service_specific_error_str(error_code, Some(op(e)))) } } #[cfg(test)] mod tests { use super::*; #[test] fn make_service_specific_error() { let status = Status::new_service_specific_error_str(-42, Some("message")); assert!(!status.is_ok()); assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC); assert_eq!(status.service_specific_error(), -42); assert_eq!( status.get_description(), "Status(-8, EX_SERVICE_SPECIFIC): '-42: message'".to_string() ); } #[test] fn make_exception() { let status = Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some("message")); assert!(!status.is_ok()); assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE); assert_eq!(status.service_specific_error(), 0); assert_eq!(status.get_description(), "Status(-5, EX_ILLEGAL_STATE): 'message'".to_string()); } #[test] fn make_exception_null() { let status = Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some("one\0two")); assert!(!status.is_ok()); assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE); assert_eq!(status.service_specific_error(), 0); assert_eq!(status.get_description(), "Status(-5, EX_ILLEGAL_STATE): ''".to_string()); } #[test] fn convert_to_service_specific_exception() { let res: std::result::Result<(), Status> = Err("message").or_service_specific_exception(-42); assert!(res.is_err()); let status = res.unwrap_err(); assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC); assert_eq!(status.service_specific_error(), -42); assert_eq!( status.get_description(), "Status(-8, EX_SERVICE_SPECIFIC): '-42: \"message\"'".to_string() ); } #[test] fn convert_to_service_specific_exception_with() { let res: std::result::Result<(), Status> = Err("message") .or_service_specific_exception_with(-42, |e| format!("outer message: {:?}", e)); assert!(res.is_err()); let status = res.unwrap_err(); assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC); assert_eq!(status.service_specific_error(), -42); assert_eq!( status.get_description(), "Status(-8, EX_SERVICE_SPECIFIC): '-42: outer message: \"message\"'".to_string() ); } #[test] fn convert_to_binder_exception() { let res: std::result::Result<(), Status> = Err("message").or_binder_exception(ExceptionCode::ILLEGAL_STATE); assert!(res.is_err()); let status = res.unwrap_err(); assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE); assert_eq!(status.service_specific_error(), 0); assert_eq!( status.get_description(), "Status(-5, EX_ILLEGAL_STATE): '\"message\"'".to_string() ); } #[test] fn convert_to_binder_exception_with() { let res: std::result::Result<(), Status> = Err("message") .or_binder_exception_with(ExceptionCode::ILLEGAL_STATE, |e| { format!("outer message: {:?}", e) }); assert!(res.is_err()); let status = res.unwrap_err(); assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE); assert_eq!(status.service_specific_error(), 0); assert_eq!( status.get_description(), "Status(-5, EX_ILLEGAL_STATE): 'outer message: \"message\"'".to_string() ); } }