Skip to main content

coreshift_core/
android_property.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5use std::ffi::CString;
6use std::io;
7use std::time::Duration;
8
9const ANDROID_PROP_VALUE_MAX: usize = 92;
10const ANDROID_PROP_SERIAL_ERROR: u32 = u32::MAX;
11
12#[repr(C)]
13pub struct AndroidPropertyInfoOpaque {
14    _private: [u8; 0],
15}
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct AndroidPropertyInfo {
19    raw: *const AndroidPropertyInfoOpaque,
20}
21
22impl AndroidPropertyInfo {
23    pub fn as_ptr(self) -> *const AndroidPropertyInfoOpaque {
24        self.raw
25    }
26}
27
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct AndroidPropertyValue {
30    pub name: String,
31    pub value: String,
32    pub serial: u32,
33}
34
35pub trait AndroidPropertyStore {
36    fn get(&self, key: &str) -> Option<String>;
37    fn set(&self, key: &str, value: &str) -> io::Result<()>;
38}
39
40#[derive(Clone, Copy, Debug, Default)]
41pub struct SystemAndroidPropertyStore;
42
43pub fn android_property_get(key: &str) -> Option<String> {
44    SystemAndroidPropertyStore.get(key)
45}
46
47/// Set an Android system property.
48///
49/// ### Errors
50/// - `EINVAL`: Key or value contains a NUL byte or exceeds the length limit.
51/// - `EACCES`: Permission denied.
52pub fn android_property_set(key: &str, value: &str) -> io::Result<()> {
53    SystemAndroidPropertyStore.set(key, value)
54}
55
56pub fn android_property_find(key: &str) -> Option<AndroidPropertyInfo> {
57    system_android_property_find(key)
58}
59
60/// Read the current value of an Android system property.
61///
62/// ### Errors
63/// - `EINVAL`: The property info is invalid.
64pub fn android_property_read(property: AndroidPropertyInfo) -> io::Result<AndroidPropertyValue> {
65    system_android_property_read(property)
66}
67
68/// Return the current serial number of an Android system property.
69///
70/// ### Errors
71/// - `EINVAL`: The property info is invalid.
72pub fn android_property_serial(property: AndroidPropertyInfo) -> io::Result<u32> {
73    system_android_property_serial(property)
74}
75
76/// Wait for an Android system property to change from its last known serial.
77///
78/// ### Errors
79/// - `EINVAL`: The property info or timeout is invalid.
80pub fn android_property_wait(
81    property: AndroidPropertyInfo,
82    old_serial: u32,
83    timeout: Option<Duration>,
84) -> io::Result<Option<u32>> {
85    system_android_property_wait(property, old_serial, timeout)
86}
87
88impl AndroidPropertyStore for SystemAndroidPropertyStore {
89    fn get(&self, key: &str) -> Option<String> {
90        system_android_property_get(key)
91    }
92
93    fn set(&self, key: &str, value: &str) -> io::Result<()> {
94        validate_property_c_string(key)?;
95        validate_property_c_string(value)?;
96        system_android_property_set(key, value)
97    }
98}
99
100fn validate_property_c_string(value: &str) -> io::Result<CString> {
101    CString::new(value).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "embedded NUL"))
102}
103
104fn android_properties_unsupported() -> io::Error {
105    io::Error::new(
106        io::ErrorKind::Unsupported,
107        "android system properties unavailable on this platform",
108    )
109}
110
111#[cfg(target_os = "android")]
112fn property_info_invalid() -> io::Error {
113    io::Error::new(io::ErrorKind::InvalidInput, "invalid android property info")
114}
115
116#[cfg(target_os = "android")]
117fn serial_result(serial: u32) -> io::Result<u32> {
118    if serial == ANDROID_PROP_SERIAL_ERROR {
119        Err(io::Error::last_os_error())
120    } else {
121        Ok(serial)
122    }
123}
124
125fn duration_to_timespec(duration: Duration) -> io::Result<libc::timespec> {
126    let tv_sec = duration
127        .as_secs()
128        .try_into()
129        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "timeout too large"))?;
130    let tv_nsec = duration.subsec_nanos() as _;
131    Ok(libc::timespec { tv_sec, tv_nsec })
132}
133
134#[cfg(target_os = "android")]
135fn system_android_property_get(key: &str) -> Option<String> {
136    use std::ffi::CStr;
137
138    let key = CString::new(key).ok()?;
139    let mut value = [0 as libc::c_char; ANDROID_PROP_VALUE_MAX + 1];
140    let len = unsafe { __system_property_get(key.as_ptr(), value.as_mut_ptr()) };
141    if len <= 0 {
142        return None;
143    }
144    Some(
145        unsafe { CStr::from_ptr(value.as_ptr()) }
146            .to_string_lossy()
147            .into_owned(),
148    )
149}
150
151#[cfg(target_os = "android")]
152fn system_android_property_find(key: &str) -> Option<AndroidPropertyInfo> {
153    let key = CString::new(key).ok()?;
154    let raw = unsafe { __system_property_find(key.as_ptr()) };
155    if raw.is_null() {
156        None
157    } else {
158        Some(AndroidPropertyInfo { raw })
159    }
160}
161
162#[cfg(target_os = "android")]
163fn system_android_property_read(property: AndroidPropertyInfo) -> io::Result<AndroidPropertyValue> {
164    if property.raw.is_null() {
165        return Err(property_info_invalid());
166    }
167    let mut value = AndroidPropertyReadCookie::default();
168    unsafe {
169        __system_property_read_callback(
170            property.raw,
171            android_property_read_callback,
172            (&mut value as *mut AndroidPropertyReadCookie).cast(),
173        );
174    }
175    value
176        .value
177        .ok_or_else(|| io::Error::other("android property read callback did not run"))
178}
179
180#[cfg(target_os = "android")]
181fn system_android_property_serial(property: AndroidPropertyInfo) -> io::Result<u32> {
182    if property.raw.is_null() {
183        return Err(property_info_invalid());
184    }
185    serial_result(unsafe { __system_property_serial(property.raw) })
186}
187
188#[cfg(target_os = "android")]
189fn system_android_property_wait(
190    property: AndroidPropertyInfo,
191    old_serial: u32,
192    timeout: Option<Duration>,
193) -> io::Result<Option<u32>> {
194    if property.raw.is_null() {
195        return Err(property_info_invalid());
196    }
197    let timeout = timeout.map(duration_to_timespec).transpose()?;
198    let timeout_ptr = timeout
199        .as_ref()
200        .map_or(std::ptr::null(), |value| value as *const libc::timespec);
201    let mut new_serial = 0;
202    let changed =
203        unsafe { __system_property_wait(property.raw, old_serial, &mut new_serial, timeout_ptr) };
204    if changed {
205        Ok(Some(new_serial))
206    } else {
207        Ok(None)
208    }
209}
210
211#[cfg(target_os = "android")]
212fn system_android_property_set(key: &str, value: &str) -> io::Result<()> {
213    let key = validate_property_c_string(key)?;
214    let value = validate_property_c_string(value)?;
215    let status = unsafe { __system_property_set(key.as_ptr(), value.as_ptr()) };
216    if status == 0 {
217        Ok(())
218    } else {
219        Err(io::Error::from_raw_os_error(status))
220    }
221}
222
223#[cfg(not(target_os = "android"))]
224fn system_android_property_get(_key: &str) -> Option<String> {
225    let _ = ANDROID_PROP_VALUE_MAX;
226    None
227}
228
229#[cfg(not(target_os = "android"))]
230fn system_android_property_find(_key: &str) -> Option<AndroidPropertyInfo> {
231    None
232}
233
234#[cfg(not(target_os = "android"))]
235fn system_android_property_read(
236    _property: AndroidPropertyInfo,
237) -> io::Result<AndroidPropertyValue> {
238    Err(android_properties_unsupported())
239}
240
241#[cfg(not(target_os = "android"))]
242fn system_android_property_serial(_property: AndroidPropertyInfo) -> io::Result<u32> {
243    let _ = ANDROID_PROP_SERIAL_ERROR;
244    Err(android_properties_unsupported())
245}
246
247#[cfg(not(target_os = "android"))]
248fn system_android_property_wait(
249    _property: AndroidPropertyInfo,
250    _old_serial: u32,
251    timeout: Option<Duration>,
252) -> io::Result<Option<u32>> {
253    if let Some(timeout) = timeout {
254        let _ = duration_to_timespec(timeout)?;
255    }
256    Err(android_properties_unsupported())
257}
258
259#[cfg(not(target_os = "android"))]
260fn system_android_property_set(_key: &str, _value: &str) -> io::Result<()> {
261    Err(android_properties_unsupported())
262}
263
264#[cfg(target_os = "android")]
265unsafe extern "C" {
266    fn __system_property_get(name: *const libc::c_char, value: *mut libc::c_char) -> libc::c_int;
267    fn __system_property_set(name: *const libc::c_char, value: *const libc::c_char) -> libc::c_int;
268    fn __system_property_find(name: *const libc::c_char) -> *const AndroidPropertyInfoOpaque;
269    fn __system_property_read_callback(
270        pi: *const AndroidPropertyInfoOpaque,
271        callback: unsafe extern "C" fn(
272            *mut libc::c_void,
273            *const libc::c_char,
274            *const libc::c_char,
275            u32,
276        ),
277        cookie: *mut libc::c_void,
278    );
279    fn __system_property_serial(pi: *const AndroidPropertyInfoOpaque) -> u32;
280    fn __system_property_wait(
281        pi: *const AndroidPropertyInfoOpaque,
282        old_serial: u32,
283        new_serial_ptr: *mut u32,
284        relative_timeout: *const libc::timespec,
285    ) -> bool;
286}
287
288#[cfg(target_os = "android")]
289#[derive(Default)]
290struct AndroidPropertyReadCookie {
291    value: Option<AndroidPropertyValue>,
292}
293
294#[cfg(target_os = "android")]
295unsafe extern "C" fn android_property_read_callback(
296    cookie: *mut libc::c_void,
297    name: *const libc::c_char,
298    value: *const libc::c_char,
299    serial: u32,
300) {
301    use std::ffi::CStr;
302
303    let cookie = unsafe { &mut *(cookie.cast::<AndroidPropertyReadCookie>()) };
304    let name = unsafe { CStr::from_ptr(name) }
305        .to_string_lossy()
306        .into_owned();
307    let value = unsafe { CStr::from_ptr(value) }
308        .to_string_lossy()
309        .into_owned();
310    cookie.value = Some(AndroidPropertyValue {
311        name,
312        value,
313        serial,
314    });
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use std::cell::RefCell;
321    use std::collections::BTreeMap;
322
323    #[derive(Default)]
324    struct FakeStore {
325        entries: RefCell<BTreeMap<String, String>>,
326    }
327
328    impl AndroidPropertyStore for FakeStore {
329        fn get(&self, key: &str) -> Option<String> {
330            self.entries.borrow().get(key).cloned()
331        }
332
333        fn set(&self, key: &str, value: &str) -> io::Result<()> {
334            self.entries
335                .borrow_mut()
336                .insert(key.to_string(), value.to_string());
337            Ok(())
338        }
339    }
340
341    #[test]
342    fn fake_store_round_trips_properties() {
343        let store = FakeStore::default();
344        assert_eq!(store.get("debug.hwui.renderer"), None);
345        store.set("debug.hwui.renderer", "skiagl").unwrap();
346        assert_eq!(store.get("debug.hwui.renderer").as_deref(), Some("skiagl"));
347    }
348
349    #[test]
350    fn system_store_rejects_embedded_nul() {
351        let err = SystemAndroidPropertyStore
352            .set("debug.hwui.renderer", "skia\0gl")
353            .unwrap_err();
354        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
355    }
356
357    #[test]
358    fn duration_to_timespec_rejects_large_timeout() {
359        let err = duration_to_timespec(Duration::from_secs(u64::MAX)).unwrap_err();
360        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
361    }
362
363    #[test]
364    #[cfg(target_os = "android")]
365    fn serial_error_maps_to_io_error() {
366        assert!(serial_result(1).is_ok());
367        assert!(serial_result(ANDROID_PROP_SERIAL_ERROR).is_err());
368    }
369}