cargo : android_system_properties @ 0.1.5
PE Patrick Elsen signed 2026-05-27 published 2026-05-27

src/lib.rs

222 lines · rust · 5 line annotations

//! A thin rust wrapper for Android system properties.//!//! This crate is similar to the `android-properties` crate with the exception that//! the necessary Android libc symbols are loaded dynamically instead of linked//! statically. In practice this means that the same binary will work with old and//! new versions of Android, even though the API for reading system properties changed//! around Android L.//!//! ## Example//!//! ```rust//! use android_system_properties::AndroidSystemProperties;//!//! let properties = AndroidSystemProperties::new();//!//! if let Some(value) = properties.get("persist.sys.timezone") {//!    println!("{}", value);//! }//! ```//!//! ## Listing and setting properties//!//! For the sake of simplicity this crate currently only contains what's needed by wgpu.//! The implementations for listing and setting properties can be added back if anyone needs//! them (let me know by filing an issue).//!//! ## License//!//! Licensed under either of//!//!  * Apache License, Version 2.0 ([LICENSE-APACHE] or <http://www.apache.org/licenses/LICENSE-2.0>)//!  * MIT license ([LICENSE-MIT] or <http://opensource.org/licenses/MIT>)//!//! at your option.//!//! [LICENSE-APACHE]: https://github.com/nical/android_system_properties/blob/804681c5c1c93d4fab29c1a2f47b7d808dc70fd3/LICENSE-APACHE//! [LICENSE-MIT]: https://github.com/nical/android_system_properties/blob/804681c5c1c93d4fab29c1a2f47b7d808dc70fd3/LICENSE-MITuse std::{    ffi::{CStr, CString},    os::raw::{c_char, c_int, c_void},};#[cfg(target_os = "android")]use std::mem;
unsafe fn property_callback(payload: *mut String, _name: *const c_char, value: *const c_char, _serial: u32) {    let cvalue = CStr::from_ptr(value);    (*payload) = cvalue.to_str().unwrap().to_string();}
Line 47–50

C-callback writes a String into a caller-provided payload pointer. The to_str().unwrap() panics on invalid UTF-8 across an FFI boundary; see FINDING-1.

type Callback = unsafe fn(*mut String, *const c_char, *const c_char, u32);type SystemPropertyGetFn = unsafe extern "C" fn(*const c_char, *mut c_char) -> c_int;type SystemPropertyFindFn = unsafe extern "C" fn(*const c_char) -> *const c_void;type SystemPropertyReadCallbackFn = unsafe extern "C" fn(*const c_void, Callback, *mut String) -> *const c_void;#[derive(Debug)]/// An object that can retrieve android system properties.////// ## Example////// ```/// use android_system_properties::AndroidSystemProperties;////// let properties = AndroidSystemProperties::new();////// if let Some(value) = properties.get("persist.sys.timezone") {///    println!("{}", value);/// }/// ```pub struct AndroidSystemProperties {    libc_so: *mut c_void,    get_fn: Option<SystemPropertyGetFn>,    find_fn: Option<SystemPropertyFindFn>,    read_callback_fn: Option<SystemPropertyReadCallbackFn>,}
unsafe impl Send for AndroidSystemProperties {}unsafe impl Sync for AndroidSystemProperties {}
Line 79–80

unsafe impl Send for AndroidSystemProperties / unsafe impl Sync for .... The type contains a *mut c_void (dlopen handle) and three Option<extern "C" fn> slots. After new() the struct is immutable and reads from libc's property store, which is thread-safe on Android. Send/Sync is sound; the missing safety comment is part of FINDING-2.

impl AndroidSystemProperties {    #[cfg(not(target_os = "android"))]    /// Create an entry point for accessing Android properties.    pub fn new() -> Self {        AndroidSystemProperties {            libc_so: std::ptr::null_mut(),            find_fn: None,            read_callback_fn: None,            get_fn: None,        }    }
    #[cfg(target_os = "android")]    /// Create an entry point for accessing Android properties.    pub fn new() -> Self {        let libc_so = unsafe { libc::dlopen(b"libc.so\0".as_ptr().cast(), libc::RTLD_NOLOAD) };        let mut properties = AndroidSystemProperties {            libc_so,            find_fn: None,            read_callback_fn: None,            get_fn: None,        };        if libc_so.is_null() {            return properties;        }        unsafe fn load_fn(libc_so: *mut c_void, name: &[u8]) -> Option<*const c_void> {            let fn_ptr = libc::dlsym(libc_so, name.as_ptr().cast());            if fn_ptr.is_null() {                return None;            }            Some(fn_ptr)        }        unsafe {            properties.read_callback_fn = load_fn(libc_so, b"__system_property_read_callback\0")                .map(|raw| mem::transmute::<*const c_void, SystemPropertyReadCallbackFn>(raw));            properties.find_fn = load_fn(libc_so, b"__system_property_find\0")                .map(|raw| mem::transmute::<*const c_void, SystemPropertyFindFn>(raw));            // Fallback for old versions of Android.            if properties.read_callback_fn.is_none() || properties.find_fn.is_none() {                properties.get_fn = load_fn(libc_so, b"__system_property_get\0")                    .map(|raw| mem::transmute::<*const c_void, SystemPropertyGetFn>(raw));            }        }        properties    }
Line 94–136

new() on Android: dlopen("libc.so", RTLD_NOLOAD)RTLD_NOLOAD returns a handle only if libc.so is already loaded (which it always is in any Android process), avoiding the cost and complications of a fresh load. Then dlsym for the three property functions; if the new callback-based pair (__system_property_read_callback + __system_property_find) is missing, falls back to the legacy __system_property_get. The function pointer slots are Option<extern "C" fn> so a missing symbol just leaves None.

    /// Retrieve a system property.    ///    /// Returns None if the operation fails.    ///    /// # Example    ///    /// ```    /// # use android_system_properties::AndroidSystemProperties;    /// let properties = AndroidSystemProperties::new();    ///    /// if let Some(value) = properties.get("persist.sys.timezone") {    ///     println!("{}", value);    /// }    /// ```    pub fn get(&self, name: &str) -> Option<String> {        let cname = CString::new(name).ok()?;        self.get_from_cstr(&cname)    }    /// Retrieve a system property using a [`CStr`] key.    ///    /// Returns None if the operation fails.    ///    /// # Example    ///    /// ```    /// # use android_system_properties::AndroidSystemProperties;    /// # use std::ffi::CStr;    /// let properties = AndroidSystemProperties::new();    ///    /// let key = unsafe { CStr::from_bytes_with_nul_unchecked(b"persist.sys.timezone\0") };    /// if let Some(value) = properties.get_from_cstr(key) {    ///     println!("{}", value);    /// }    /// ```
    pub fn get_from_cstr(&self, cname: &std::ffi::CStr) -> Option<String> {        // If available, use the recommended approach to accessing properties (Android L and onward).        if let (Some(find_fn), Some(read_callback_fn)) = (self.find_fn, self.read_callback_fn) {            let info = unsafe { (find_fn)(cname.as_ptr()) };            if info.is_null() {                return None;            }            let mut result = String::new();            unsafe {                (read_callback_fn)(info, property_callback, &mut result);            }            return Some(result);        }        // Fall back to the older approach.        if let Some(get_fn) = self.get_fn {            // The constant is PROP_VALUE_MAX in Android's libc/include/sys/system_properties.h            const PROPERTY_VALUE_MAX: usize = 92;            let mut buffer: Vec<u8> = Vec::with_capacity(PROPERTY_VALUE_MAX);            let raw = buffer.as_mut_ptr() as *mut c_char;            let len = unsafe { (get_fn)(cname.as_ptr(), raw) };            if len > 0 {                assert!(len as usize <= buffer.capacity());                unsafe { buffer.set_len(len as usize); }                String::from_utf8(buffer).ok()            } else {                None            }        } else {            None        }    }
Line 173–210

get_from_cstr: prefers the modern callback-based API. The legacy path (lines 192-209) uses Vec::with_capacity(PROPERTY_VALUE_MAX) (92 bytes, per Android's libc/include/sys/system_properties.h), hands a raw pointer to the C function, then assert!(len as usize <= buffer.capacity()) before set_len. The assert is defensive — if the C side overflowed the buffer, UB would already have happened. Correctness depends on Android's documented contract that __system_property_get writes at most PROP_VALUE_MAX bytes. String::from_utf8(buffer).ok() returns None on invalid UTF-8 in this path, unlike the callback path (FINDING-1).

}
impl Drop for AndroidSystemProperties {    fn drop(&mut self) {        if !self.libc_so.is_null() {            unsafe {                libc::dlclose(self.libc_so);            }        }    }}
Line 213–221

Drop calls dlclose. Since the function pointers are stored on the same struct as the handle, they cannot outlive the dlclose. Sound.