diff --git a/ndk-sys/CHANGELOG.md b/ndk-sys/CHANGELOG.md index 13c873de..3719de63 100644 --- a/ndk-sys/CHANGELOG.md +++ b/ndk-sys/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased +- **Breaking:** Removed `__ANDROID_API__` constant as it is always defined to the value of `__ANDROID_API_FUTURE__`. (#479) - Regenerate bindings with `bindgen 0.71.1`. (#487) +- Include API bindings from `sys/system_properties.h`. (#495) # 0.6.0 (2024-04-26) diff --git a/ndk-sys/generate_bindings.sh b/ndk-sys/generate_bindings.sh index 395be9c5..b5001732 100755 --- a/ndk-sys/generate_bindings.sh +++ b/ndk-sys/generate_bindings.sh @@ -31,6 +31,7 @@ while read ARCH && read TARGET ; do --blocklist-item 'C?_?JNIEnv' \ --blocklist-item '_?JavaVM' \ --blocklist-item '_?j\w+' \ + --blocklist-item '__ANDROID_API__' \ --newtype-enum '\w+_(result|status)_t' \ --newtype-enum 'ACameraDevice_request_template' \ --newtype-enum 'ADataSpace' \ diff --git a/ndk-sys/src/ffi_aarch64.rs b/ndk-sys/src/ffi_aarch64.rs index 2adf9ef5..e316ed85 100644 --- a/ndk-sys/src/ffi_aarch64.rs +++ b/ndk-sys/src/ffi_aarch64.rs @@ -44,7 +44,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 64; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; @@ -1410,6 +1409,8 @@ pub const PROPERTY_VERSION: &[u8; 8] = b"version\0"; pub const PROPERTY_DESCRIPTION: &[u8; 12] = b"description\0"; pub const PROPERTY_ALGORITHMS: &[u8; 11] = b"algorithms\0"; pub const PROPERTY_DEVICE_UNIQUE_ID: &[u8; 15] = b"deviceUniqueId\0"; +pub const PROP_VALUE_MAX: u32 = 92; +pub const PROP_NAME_MAX: u32 = 32; extern "C" { pub fn android_get_application_target_sdk_version() -> ::std::os::raw::c_int; } @@ -21844,4 +21845,64 @@ extern "C" { extern "C" { pub fn AMediaMuxer_getTrackFormat(muxer: *mut AMediaMuxer, idx: usize) -> *mut AMediaFormat; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct prop_info { + _unused: [u8; 0], +} +extern "C" { + pub fn __system_property_set( + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find(__name: *const ::std::os::raw::c_char) -> *const prop_info; +} +extern "C" { + pub fn __system_property_read_callback( + __pi: *const prop_info, + __callback: ::std::option::Option< + unsafe extern "C" fn( + __cookie: *mut ::std::os::raw::c_void, + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + __serial: u32, + ), + >, + __cookie: *mut ::std::os::raw::c_void, + ); +} +extern "C" { + pub fn __system_property_foreach( + __callback: ::std::option::Option< + unsafe extern "C" fn(__pi: *const prop_info, __cookie: *mut ::std::os::raw::c_void), + >, + __cookie: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_wait( + __pi: *const prop_info, + __old_serial: u32, + __new_serial_ptr: *mut u32, + __relative_timeout: *const timespec, + ) -> bool; +} +extern "C" { + pub fn __system_property_read( + __pi: *const prop_info, + __name: *mut ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_get( + __name: *const ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find_nth(__n: ::std::os::raw::c_uint) -> *const prop_info; +} pub type __uint128_t = u128; diff --git a/ndk-sys/src/ffi_arm.rs b/ndk-sys/src/ffi_arm.rs index 023d2674..55b30afb 100644 --- a/ndk-sys/src/ffi_arm.rs +++ b/ndk-sys/src/ffi_arm.rs @@ -171,7 +171,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 32; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; @@ -1463,6 +1462,8 @@ pub const PROPERTY_VERSION: &[u8; 8] = b"version\0"; pub const PROPERTY_DESCRIPTION: &[u8; 12] = b"description\0"; pub const PROPERTY_ALGORITHMS: &[u8; 11] = b"algorithms\0"; pub const PROPERTY_DEVICE_UNIQUE_ID: &[u8; 15] = b"deviceUniqueId\0"; +pub const PROP_VALUE_MAX: u32 = 92; +pub const PROP_NAME_MAX: u32 = 32; extern "C" { pub fn android_get_application_target_sdk_version() -> ::std::os::raw::c_int; } @@ -22294,3 +22295,63 @@ extern "C" { extern "C" { pub fn AMediaMuxer_getTrackFormat(muxer: *mut AMediaMuxer, idx: usize) -> *mut AMediaFormat; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct prop_info { + _unused: [u8; 0], +} +extern "C" { + pub fn __system_property_set( + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find(__name: *const ::std::os::raw::c_char) -> *const prop_info; +} +extern "C" { + pub fn __system_property_read_callback( + __pi: *const prop_info, + __callback: ::std::option::Option< + unsafe extern "C" fn( + __cookie: *mut ::std::os::raw::c_void, + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + __serial: u32, + ), + >, + __cookie: *mut ::std::os::raw::c_void, + ); +} +extern "C" { + pub fn __system_property_foreach( + __callback: ::std::option::Option< + unsafe extern "C" fn(__pi: *const prop_info, __cookie: *mut ::std::os::raw::c_void), + >, + __cookie: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_wait( + __pi: *const prop_info, + __old_serial: u32, + __new_serial_ptr: *mut u32, + __relative_timeout: *const timespec, + ) -> bool; +} +extern "C" { + pub fn __system_property_read( + __pi: *const prop_info, + __name: *mut ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_get( + __name: *const ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find_nth(__n: ::std::os::raw::c_uint) -> *const prop_info; +} diff --git a/ndk-sys/src/ffi_i686.rs b/ndk-sys/src/ffi_i686.rs index 752766c0..8185b823 100644 --- a/ndk-sys/src/ffi_i686.rs +++ b/ndk-sys/src/ffi_i686.rs @@ -34,7 +34,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 32; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; @@ -1327,6 +1326,8 @@ pub const PROPERTY_VERSION: &[u8; 8] = b"version\0"; pub const PROPERTY_DESCRIPTION: &[u8; 12] = b"description\0"; pub const PROPERTY_ALGORITHMS: &[u8; 11] = b"algorithms\0"; pub const PROPERTY_DEVICE_UNIQUE_ID: &[u8; 15] = b"deviceUniqueId\0"; +pub const PROP_VALUE_MAX: u32 = 92; +pub const PROP_NAME_MAX: u32 = 32; extern "C" { pub fn android_get_application_target_sdk_version() -> ::std::os::raw::c_int; } @@ -23011,4 +23012,64 @@ extern "C" { extern "C" { pub fn AMediaMuxer_getTrackFormat(muxer: *mut AMediaMuxer, idx: usize) -> *mut AMediaFormat; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct prop_info { + _unused: [u8; 0], +} +extern "C" { + pub fn __system_property_set( + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find(__name: *const ::std::os::raw::c_char) -> *const prop_info; +} +extern "C" { + pub fn __system_property_read_callback( + __pi: *const prop_info, + __callback: ::std::option::Option< + unsafe extern "C" fn( + __cookie: *mut ::std::os::raw::c_void, + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + __serial: u32, + ), + >, + __cookie: *mut ::std::os::raw::c_void, + ); +} +extern "C" { + pub fn __system_property_foreach( + __callback: ::std::option::Option< + unsafe extern "C" fn(__pi: *const prop_info, __cookie: *mut ::std::os::raw::c_void), + >, + __cookie: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_wait( + __pi: *const prop_info, + __old_serial: u32, + __new_serial_ptr: *mut u32, + __relative_timeout: *const timespec, + ) -> bool; +} +extern "C" { + pub fn __system_property_read( + __pi: *const prop_info, + __name: *mut ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_get( + __name: *const ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find_nth(__n: ::std::os::raw::c_uint) -> *const prop_info; +} pub type __builtin_va_list = *mut ::std::os::raw::c_char; diff --git a/ndk-sys/src/ffi_x86_64.rs b/ndk-sys/src/ffi_x86_64.rs index 0416eb1f..55d24bbc 100644 --- a/ndk-sys/src/ffi_x86_64.rs +++ b/ndk-sys/src/ffi_x86_64.rs @@ -34,7 +34,6 @@ pub const __BIONIC__: u32 = 1; pub const __WORDSIZE: u32 = 64; pub const __bos_level: u32 = 0; pub const __ANDROID_API_FUTURE__: u32 = 10000; -pub const __ANDROID_API__: u32 = 10000; pub const __ANDROID_API_G__: u32 = 9; pub const __ANDROID_API_I__: u32 = 14; pub const __ANDROID_API_J__: u32 = 16; @@ -1372,6 +1371,8 @@ pub const PROPERTY_VERSION: &[u8; 8] = b"version\0"; pub const PROPERTY_DESCRIPTION: &[u8; 12] = b"description\0"; pub const PROPERTY_ALGORITHMS: &[u8; 11] = b"algorithms\0"; pub const PROPERTY_DEVICE_UNIQUE_ID: &[u8; 15] = b"deviceUniqueId\0"; +pub const PROP_VALUE_MAX: u32 = 92; +pub const PROP_NAME_MAX: u32 = 32; extern "C" { pub fn android_get_application_target_sdk_version() -> ::std::os::raw::c_int; } @@ -23049,6 +23050,66 @@ extern "C" { extern "C" { pub fn AMediaMuxer_getTrackFormat(muxer: *mut AMediaMuxer, idx: usize) -> *mut AMediaFormat; } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct prop_info { + _unused: [u8; 0], +} +extern "C" { + pub fn __system_property_set( + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find(__name: *const ::std::os::raw::c_char) -> *const prop_info; +} +extern "C" { + pub fn __system_property_read_callback( + __pi: *const prop_info, + __callback: ::std::option::Option< + unsafe extern "C" fn( + __cookie: *mut ::std::os::raw::c_void, + __name: *const ::std::os::raw::c_char, + __value: *const ::std::os::raw::c_char, + __serial: u32, + ), + >, + __cookie: *mut ::std::os::raw::c_void, + ); +} +extern "C" { + pub fn __system_property_foreach( + __callback: ::std::option::Option< + unsafe extern "C" fn(__pi: *const prop_info, __cookie: *mut ::std::os::raw::c_void), + >, + __cookie: *mut ::std::os::raw::c_void, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_wait( + __pi: *const prop_info, + __old_serial: u32, + __new_serial_ptr: *mut u32, + __relative_timeout: *const timespec, + ) -> bool; +} +extern "C" { + pub fn __system_property_read( + __pi: *const prop_info, + __name: *mut ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_get( + __name: *const ::std::os::raw::c_char, + __value: *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn __system_property_find_nth(__n: ::std::os::raw::c_uint) -> *const prop_info; +} pub type __builtin_va_list = [__va_list_tag; 1usize]; #[repr(C)] #[derive(Debug, Copy, Clone)] diff --git a/ndk-sys/wrapper.h b/ndk-sys/wrapper.h index efe16f1d..eb4e5492 100644 --- a/ndk-sys/wrapper.h +++ b/ndk-sys/wrapper.h @@ -89,3 +89,5 @@ #include #include #include + +#include diff --git a/ndk/CHANGELOG.md b/ndk/CHANGELOG.md index 4bc9a7e2..83af0e4c 100644 --- a/ndk/CHANGELOG.md +++ b/ndk/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased - image_reader: Add `ImageReader::new_with_data_space()` constructor and `ImageReader::data_space()` getter from API level 34. (#474) +- Add `api_level` module with definitions and bindings for Android API levels. (#479) +- Add bindings for querying and setting Android System Properties. (#495) # 0.9.0 (2024-04-26) diff --git a/ndk/src/api_level.rs b/ndk/src/api_level.rs new file mode 100644 index 00000000..0699eddf --- /dev/null +++ b/ndk/src/api_level.rs @@ -0,0 +1,134 @@ +//! Bindings for [API levels] +//! +//! Defines functions and constants for working with Android API levels. +//! +//! [API levels]: https://developer.android.com/ndk/reference/group/apilevels + +use num_enum::{FromPrimitive, IntoPrimitive}; +use thiserror::Error; + +/// Android API levels, equivalent to the constants defined in `` and the Java +/// [`Build.VERSION_CODES`] constants. +/// +/// [`Build.VERSION_CODES`]: https://developer.android.com/reference/android/os/Build.VERSION_CODES +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)] +#[repr(u32)] +#[non_exhaustive] +pub enum ApiLevel { + /// Magic version number for an Android OS build which has not yet turned into an official + /// release. + #[doc(alias = "__ANDROID_API_FUTURE__")] + Future = ffi::__ANDROID_API_FUTURE__, + + /// Names the Gingerbread API level (9) + #[doc(alias = "__ANDROID_API_G__")] + G = ffi::__ANDROID_API_G__, + /// Names the Ice-Cream Sandwich API level (14) + #[doc(alias = "__ANDROID_API_I__")] + I = ffi::__ANDROID_API_I__, + /// Names the Jellybean API level (16) + #[doc(alias = "__ANDROID_API_J__")] + J = ffi::__ANDROID_API_J__, + /// Names the Jellybean MR1 API level (17) + #[doc(alias = "__ANDROID_API_J_MR1__")] + JMr1 = ffi::__ANDROID_API_J_MR1__, + /// Names the Jellybean MR2 API level (18) + #[doc(alias = "__ANDROID_API_J_MR2__")] + JMr2 = ffi::__ANDROID_API_J_MR2__, + /// Names the KitKat API level (19) + #[doc(alias = "__ANDROID_API_K__")] + K = ffi::__ANDROID_API_K__, + /// Names the Lollipop API level (21) + #[doc(alias = "__ANDROID_API_L__")] + L = ffi::__ANDROID_API_L__, + /// Names the Lollipop MR1 API level (22) + #[doc(alias = "__ANDROID_API_L_MR1__")] + LMr1 = ffi::__ANDROID_API_L_MR1__, + /// Names the Marshmallow API level (23) + #[doc(alias = "__ANDROID_API_M__")] + M = ffi::__ANDROID_API_M__, + /// Names the Nougat API level (24) + #[doc(alias = "__ANDROID_API_N__")] + N = ffi::__ANDROID_API_N__, + /// Names the Nougat MR1 API level (25) + #[doc(alias = "__ANDROID_API_N_MR1__")] + NMr1 = ffi::__ANDROID_API_N_MR1__, + /// Names the Oreo API level (26) + #[doc(alias = "__ANDROID_API_O__")] + O = ffi::__ANDROID_API_O__, + /// Names the Oreo MR1 API level (27) + #[doc(alias = "__ANDROID_API_O_MR1__")] + OMr1 = ffi::__ANDROID_API_O_MR1__, + /// Names the Pie API level (28) + #[doc(alias = "__ANDROID_API_P__")] + P = ffi::__ANDROID_API_P__, + /// Names the Android 10 (aka "Q" or "Quince Tart") API level (29) + #[doc(alias = "__ANDROID_API_Q__")] + Q = ffi::__ANDROID_API_Q__, + /// Names the Android 11 (aka "R" or "Red Velvet Cake") API level (30) + #[doc(alias = "__ANDROID_API_R__")] + R = ffi::__ANDROID_API_R__, + /// Names the Android 12 (aka "S" or "Snowcone") API level (31) + #[doc(alias = "__ANDROID_API_S__")] + S = ffi::__ANDROID_API_S__, + /// Names the Android 13 (aka "T" or "Tiramisu") API level (33) + #[doc(alias = "__ANDROID_API_T__")] + T = ffi::__ANDROID_API_T__, + /// Names the Android 14 (aka "U" or "UpsideDownCake") API level (34) + #[doc(alias = "__ANDROID_API_U__")] + U = ffi::__ANDROID_API_U__, + /// Names the Android 15 (aka "V" or "VanillaIceCream") API level (35) + #[doc(alias = "__ANDROID_API_V__")] + V = ffi::__ANDROID_API_V__, + #[doc(hidden)] + #[num_enum(catch_all)] + __Unknown(u32), +} + +/// Returns the `targetSdkVersion` from `AndroidManifest.xml` of the caller, or [`ApiLevel::Future`] +/// if there is no known target SDK version (for code not running in the context of an app). +/// +/// See also [`device_api_level()`]. +#[cfg(feature = "api-level-24")] +#[doc(alias = "android_get_application_target_sdk_version")] +pub fn application_target_sdk_version() -> ApiLevel { + let version = unsafe { ffi::android_get_application_target_sdk_version() }; + u32::try_from(version) + // Docs suggest that it would only return `Future` + .expect("Unexpected sign bit in `application_target_sdk_version()`") + .into() +} + +/// Possible failures returned by [`device_api_level()`]. +#[derive(Debug, Error)] +pub enum DeviceApiLevelError { + #[cfg(not(feature = "api-level-29"))] + #[error("`__system_property_get(\"ro.build.version.sdk\")` failed")] + FallbackPropertyGetFailed(#[from] super::system_properties::GetError), + #[error("device_api_level() encountered a negative version code")] + TryFromIntError(#[from] std::num::TryFromIntError), +} + +/// Returns the API level of the device we're actually running on. +/// +/// The returned value is equivalent to the Java [`Build.VERSION.SDK_INT`] API. +/// +/// [`Build.VERSION.SDK_INT`]: https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT +/// +/// Below `api-level-29` this falls back to reading the `"ro.build.version.sdk"` system property, +/// with the possibility to return more types of errors. +/// +/// See also [`application_target_sdk_version()`]. +#[doc(alias = "android_get_device_api_level")] +pub fn device_api_level() -> Result { + #[cfg(not(feature = "api-level-29"))] + let version = super::system_properties::get::(unsafe { + // TODO: Switch to C-string literal since MSRV 1.77 + std::ffi::CStr::from_bytes_with_nul_unchecked(b"ro.build.version.sdk\0") + })?; + + #[cfg(feature = "api-level-29")] + let version = unsafe { ffi::android_get_device_api_level() }; + + Ok(u32::try_from(version)?.into()) +} diff --git a/ndk/src/lib.rs b/ndk/src/lib.rs index 81e2ef61..e6e8ffa2 100644 --- a/ndk/src/lib.rs +++ b/ndk/src/lib.rs @@ -11,6 +11,7 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +pub mod api_level; pub mod asset; pub mod audio; pub mod bitmap; @@ -29,5 +30,6 @@ pub mod native_window; pub mod shared_memory; pub mod surface_texture; pub mod sync; +pub mod system_properties; pub mod trace; mod utils; diff --git a/ndk/src/system_properties.rs b/ndk/src/system_properties.rs new file mode 100644 index 00000000..19246ff2 --- /dev/null +++ b/ndk/src/system_properties.rs @@ -0,0 +1,325 @@ +//! Bindings for [System Properties] +//! +//! [System Properties]: https://source.android.com/docs/core/architecture/configuration/add-system-properties + +use std::{ + ffi::{c_char, c_void, CStr, CString, FromBytesWithNulError, FromVecWithNulError}, + fmt, + io::Error, + ptr::NonNull, + str::{FromStr, Utf8Error}, +}; +#[cfg(feature = "api-level-26")] +use std::{mem::MaybeUninit, num::NonZeroU32, time::Duration}; + +use thiserror::Error; + +use crate::utils::{abort_on_panic, status_to_io_result}; + +/// Possible failures returned by [`get_raw()`]. +#[derive(Debug, Error)] +pub enum GetRawError { + #[error("Property is missing or empty")] + MissingOrEmpty, + #[error(transparent)] + NulError(#[from] FromVecWithNulError), + #[error(transparent)] + Io(#[from] Error), +} + +/// Internal helper to deduplicate the implementation between [`get_raw()`] and +/// [`Property::read_raw()`]. +fn process_owned(get: impl FnOnce(*mut c_char) -> i32) -> Result { + // Pre-allocate a `Vec` which we can move to the user with the result + let mut value = Vec::with_capacity(ffi::PROP_VALUE_MAX as usize); + let ret = get(value.as_mut_ptr()); + match ret { + 0 => Err(GetRawError::MissingOrEmpty), + ..=-1 => Err(Error::from_raw_os_error(-ret).into()), + 1.. => { + // TODO: This "smart" implementation leaves the user with a 92-byte allocation since + // set_len() currently doesn't shrink. Any such operation would likely reallocate, + // making this have no advantage over the stack-local variant that allocates after the + // fact. + unsafe { value.set_len(ret as usize + 1) } + Ok(CString::from_vec_with_nul(value)?) + } + } +} + +/// Returns the property value as an owned [`CString`] with possibly invalid UTF-8 [but no interor +/// NULs]. The maximum length can be up to 92 ([`ffi::PROP_VALUE_MAX`]) including NUL terminator. +/// +/// [but no interor NULs]: GetRawError::NulError +/// +/// See [`get()`] for a more convenient API that validates this string for UTF-8 and directly parses +/// it into a [`FromStr`]-compatible type. +/// +/// # Deprecation +/// Deprecated since Android O (API level 26), use [`Property::find()`] with +/// [`Property::read_callback()`] instead which does not have a limit on `value` nor `name` length. +#[doc(alias = "__system_property_get")] +pub fn get_raw(name: &CStr) -> Result { + process_owned(|value| unsafe { ffi::__system_property_get(name.as_ptr(), value) }) +} + +/// Possible failures returned by [`get()`]. +#[derive(Debug, Error)] +pub enum GetError { + #[error("Property is missing or empty")] + MissingOrEmpty, + #[error(transparent)] + NulError(#[from] FromBytesWithNulError), + #[error("Property does not contain valid UTF-8")] + Utf8Error(#[from] Utf8Error), + #[error(transparent)] + Io(#[from] Error), + #[error(transparent)] + ParseError(T), +} + +/// Internal helper to deduplicate the implementation between [`get()`] and [`Property::read()`]. +fn process_parse(get: impl FnOnce(*mut c_char) -> i32) -> Result> { + let mut value = [0u8; ffi::PROP_VALUE_MAX as usize]; + let ret = get(value.as_mut_ptr()); + match ret { + 0 => Err(GetError::MissingOrEmpty), + ..=-1 => Err(Error::from_raw_os_error(-ret).into()), + 1.. => { + let c_str = CStr::from_bytes_with_nul(&value[..ret as usize + 1])?; + c_str.to_str()?.parse().map_err(GetError::ParseError) + } + } +} + +/// Returns the property value as a [`FromStr`]-parsed type from a source string of at most 92 +/// ([`ffi::PROP_VALUE_MAX`]) characters, including NUL terminator. +/// +/// # Implementation details +/// This is implemented without any up-front allocations like [`get_raw()`], but requires a trip +/// through [`CStr`] and [`str`] (for calling [`FromStr::from_str()`]) meaning the resulting +/// string has to be compliant with [`CStr`] ([no interior NULs]) and [`str`] ([valid UTF-8]). In +/// other words, parsing into a [`String`] will never contain interior NULs (and it is unknown and +/// unlikely whether the property API allows for this). +/// +/// [no interior NULs]: GetError::NulError +/// [valid UTF-8]: GetError::Utf8Error +/// +/// # Deprecation +/// Deprecated since Android O (API level 26), use [`Property::find()`] with +/// [`Property::read_callback()`] instead which does not have a limit on `value` nor `name` length. +#[doc(alias = "__system_property_get")] +pub fn get(name: &CStr) -> Result> { + process_parse(|value| unsafe { ffi::__system_property_get(name.as_ptr(), value) }) +} + +/// Sets system property `name` to `value`, creating the system property if it doesn't already +/// exist. +#[doc(alias = "__system_property_set")] +pub fn set(name: &CStr, value: &CStr) -> std::io::Result<()> { + let ret = unsafe { ffi::__system_property_set(name.as_ptr(), value.as_ptr()) }; + match ret { + 0 => Ok(()), + ..=-1 => Err(Error::from_raw_os_error(-ret)), + 1.. => panic!("Unexpected non-zero non-negative return value `{ret}`"), + } +} + +/// Modern abstraction to [find], cache, [read] and [wait] on properties. +/// +/// [find]: Property::find() +/// [read]: Property::read() +/// [wait]: Property::wait() +#[doc(alias = "prop_info")] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct Property(NonNull); + +/// The name, value and serial of a property during [`Property::read_callback()`]. +#[cfg(feature = "api-level-26")] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct PropertyValue<'a> { + pub name: &'a CStr, + pub value: &'a CStr, + pub serial: u32, +} + +impl fmt::Debug for Property { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[cfg(feature = "api-level-26")] + { + let mut result = None; + self.read_callback(|pv: &PropertyValue<'_>| result = Some(pv.fmt(f))); + result.expect("Read callback never called") + } + #[cfg(not(feature = "api-level-26"))] + { + f.debug_struct("Property") + .field("value", &self.read_raw()) + .finish_non_exhaustive() + } + } +} + +impl Property { + /// Returns a [`Property`] corresponding to the system property `name`, or [`None`] if it + /// doesn't exist. Use [`Property::read()`] or [`Property::read_callback()`] to query the + /// current value. + /// + /// Property lookup is expensive, so it can be useful to cache the result of this function. + #[doc(alias = "__system_property_find")] + pub fn find(name: &CStr) -> Option { + let prop = unsafe { ffi::__system_property_find(name.as_ptr()) }; + // TODO: No lifetime information available for this pointer + NonNull::new(prop.cast_mut()).map(Self) + } + + /// Calls the `callback` for every system property with a [`Property`] handle. Use in + /// conjunction with [`Property::read_callback()`] to get its name and value. + /// + /// This method is for inspecting and debugging the property system, and not generally useful. + #[doc(alias = "__system_property_foreach")] + pub fn foreach(mut callback: F) -> std::io::Result<()> { + unsafe extern "C" fn ffi_callback( + pi: *const ffi::prop_info, + cookie: *mut c_void, + ) { + abort_on_panic(|| { + let callback = cookie as *mut F; + + // TODO: No lifetime information available for this pointer + (*callback)(Property(NonNull::new(pi.cast_mut()).unwrap())) + }) + } + + let ret = unsafe { + ffi::__system_property_foreach(Some(ffi_callback::), <*mut _>::cast(&mut callback)) + }; + + status_to_io_result(ret) + } + + /// Returns an owned [`CString`] with possibly invalid UTF-8 [but no interor NULs]. The maximum + /// length can be up to 92 ([`ffi::PROP_VALUE_MAX`]) including NUL terminator. + /// + /// [but no interor NULs]: GetRawError::NulError + /// + /// See [`Property::read()`] for a more convenient API that validates this string for UTF-8 and + /// directly parses it into a [`FromStr`]-compatible type. + /// + /// # Deprecation + /// Deprecated since Android O (API level 26), use [`Self::read_callback()`] instead which does + /// not have a limit on `value` nor `name` length. + #[doc(alias = "__system_property_read")] + pub fn read_raw(&self) -> Result { + process_owned(|value| unsafe { + // TODO: should we return the name of ffi::PROP_NAME_MAX? + ffi::__system_property_read(self.0.as_ptr(), std::ptr::null_mut(), value) + }) + } + + /// Returns the property value as a [`FromStr`]-parsed type from a source string of at most 92 + /// ([`ffi::PROP_VALUE_MAX`]) characters, including NUL terminator. + /// + /// # Implementation details + /// This is implemented without any up-front allocations like [`get_raw()`], but requires a trip + /// through [`CStr`] and [`str`] (for calling [`FromStr::from_str()`]) meaning the resulting + /// string has to be compliant with [`CStr`] ([no interior NULs]) and [`str`] ([valid UTF-8]). + /// In other words, parsing into a [`String`] will never contain interior NULs (and it is + /// unknown and unlikely whether the property API allows for this). + /// + /// [no interior NULs]: GetError::NulError + /// [valid UTF-8]: GetError::Utf8Error + /// + /// # Deprecation + /// Deprecated since Android O (API level 26), use [`Self::read_callback()`] instead which does + /// not have a limit on `value` nor `name` length. + #[doc(alias = "__system_property_read")] + pub fn read(&self) -> Result> { + process_parse(|value| unsafe { + // TODO: should we return the name of ffi::PROP_NAME_MAX? + ffi::__system_property_read(self.0.as_ptr(), std::ptr::null_mut(), value) + }) + } + + /// Calls `callback` with a consistent trio of `name`, `value` and `serial` number (stored in + /// [`PropertyValue`]) for this [`Property`]. + #[cfg(feature = "api-level-26")] + #[doc(alias = "__system_property_read_callback")] + pub fn read_callback)>(&self, callback: F) { + // Wrap the callback in a MaybeUninit so that ffi_callback() can "copy from" a pointer to it + // and consume the FnOnce, leaving the original callback "invalid" but inaccessible without + // unsafe. + let mut callback = MaybeUninit::new(callback); + unsafe extern "C" fn ffi_callback)>( + cookie: *mut c_void, + name: *const c_char, + value: *const c_char, + serial: u32, + ) { + abort_on_panic(|| { + let callback: F = std::ptr::read(cookie.cast()); + let name = CStr::from_ptr(name); + let value = CStr::from_ptr(value); + + callback(&PropertyValue { + name, + value, + serial, + }) + }) + } + + unsafe { + ffi::__system_property_read_callback( + self.0.as_ptr(), + Some(ffi_callback::), + callback.as_mut_ptr().cast(), + ) + } + } + + /// Waits for this specific system property to be updated past `old_serial`. Waits no longer + /// than `timeout`, or forever if `timeout` is [`None`]. + /// + /// Returns the new serial in [`Some`], or [`None`] on timeout. + #[cfg(feature = "api-level-26")] + #[doc(alias = "__system_property_wait")] + pub fn wait(&self, old_serial: Option, timeout: Option) -> Option { + wait_optional_prop(Some(self), old_serial, timeout) + } +} + +/// Internal helper to deduplicate the implementation between [`wait()`] and [`Property::wait()`]. +#[cfg(feature = "api-level-26")] +fn wait_optional_prop( + prop: Option<&Property>, + old_serial: Option, + timeout: Option, +) -> Option { + let mut new_serial = MaybeUninit::uninit(); + let timeout = timeout.map_or(std::ptr::null(), |t| &ffi::timespec { + tv_sec: t.as_secs() as i64, + tv_nsec: t.subsec_nanos() as i64, + }); + unsafe { + ffi::__system_property_wait( + prop.map_or(std::ptr::null(), |p| p.0.as_ptr()), + old_serial.map_or(0, NonZeroU32::get), + new_serial.as_mut_ptr(), + timeout, + ) + } + .then(|| unsafe { new_serial.assume_init() }) +} + +/// Waits for this specific system property to be updated past `old_serial`. Waits no longer than +/// `timeout`, or forever if `timeout` is [`None`]. +/// +/// Same as [`Property::wait()`], but for the global serial number. +/// +/// Returns the new serial in [`Some`], or [`None`] on timeout. +#[cfg(feature = "api-level-26")] +#[doc(alias = "__system_property_wait")] +pub fn wait(old_serial: Option, timeout: Option) -> Option { + wait_optional_prop(None, old_serial, timeout) +} diff --git a/ndk/src/utils.rs b/ndk/src/utils.rs index fe1ec5b6..a1bbb328 100644 --- a/ndk/src/utils.rs +++ b/ndk/src/utils.rs @@ -10,8 +10,8 @@ use std::io::{Error, Result}; pub(crate) fn status_to_io_result(status: i32) -> Result<()> { match status { 0 => Ok(()), - r if r < 0 => Err(Error::from_raw_os_error(-r)), - r => unreachable!("Status is positive integer {}", r), + ..=-1 => Err(Error::from_raw_os_error(-status)), + 1.. => unreachable!("Status is positive integer {status}"), } }