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
47pub fn android_property_set(key: &str, value: &str) -> io::Result<()> {
48    SystemAndroidPropertyStore.set(key, value)
49}
50
51pub fn android_property_find(key: &str) -> Option<AndroidPropertyInfo> {
52    system_android_property_find(key)
53}
54
55pub fn android_property_read(property: AndroidPropertyInfo) -> io::Result<AndroidPropertyValue> {
56    system_android_property_read(property)
57}
58
59pub fn android_property_serial(property: AndroidPropertyInfo) -> io::Result<u32> {
60    system_android_property_serial(property)
61}
62
63pub fn android_property_wait(
64    property: AndroidPropertyInfo,
65    old_serial: u32,
66    timeout: Option<Duration>,
67) -> io::Result<Option<u32>> {
68    system_android_property_wait(property, old_serial, timeout)
69}
70
71impl AndroidPropertyStore for SystemAndroidPropertyStore {
72    fn get(&self, key: &str) -> Option<String> {
73        system_android_property_get(key)
74    }
75
76    fn set(&self, key: &str, value: &str) -> io::Result<()> {
77        validate_property_c_string(key)?;
78        validate_property_c_string(value)?;
79        system_android_property_set(key, value)
80    }
81}
82
83fn validate_property_c_string(value: &str) -> io::Result<CString> {
84    CString::new(value).map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "embedded NUL"))
85}
86
87fn android_properties_unsupported() -> io::Error {
88    io::Error::new(
89        io::ErrorKind::Unsupported,
90        "android system properties unavailable on this platform",
91    )
92}
93
94#[cfg(target_os = "android")]
95fn property_info_invalid() -> io::Error {
96    io::Error::new(io::ErrorKind::InvalidInput, "invalid android property info")
97}
98
99#[cfg(target_os = "android")]
100fn serial_result(serial: u32) -> io::Result<u32> {
101    if serial == ANDROID_PROP_SERIAL_ERROR {
102        Err(io::Error::last_os_error())
103    } else {
104        Ok(serial)
105    }
106}
107
108fn duration_to_timespec(duration: Duration) -> io::Result<libc::timespec> {
109    let tv_sec = duration
110        .as_secs()
111        .try_into()
112        .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "timeout too large"))?;
113    let tv_nsec = duration.subsec_nanos() as _;
114    Ok(libc::timespec { tv_sec, tv_nsec })
115}
116
117#[cfg(target_os = "android")]
118fn system_android_property_get(key: &str) -> Option<String> {
119    use std::ffi::CStr;
120
121    let key = CString::new(key).ok()?;
122    let mut value = [0 as libc::c_char; ANDROID_PROP_VALUE_MAX + 1];
123    let len = unsafe { __system_property_get(key.as_ptr(), value.as_mut_ptr()) };
124    if len <= 0 {
125        return None;
126    }
127    Some(
128        unsafe { CStr::from_ptr(value.as_ptr()) }
129            .to_string_lossy()
130            .into_owned(),
131    )
132}
133
134#[cfg(target_os = "android")]
135fn system_android_property_find(key: &str) -> Option<AndroidPropertyInfo> {
136    let key = CString::new(key).ok()?;
137    let raw = unsafe { __system_property_find(key.as_ptr()) };
138    if raw.is_null() {
139        None
140    } else {
141        Some(AndroidPropertyInfo { raw })
142    }
143}
144
145#[cfg(target_os = "android")]
146fn system_android_property_read(property: AndroidPropertyInfo) -> io::Result<AndroidPropertyValue> {
147    if property.raw.is_null() {
148        return Err(property_info_invalid());
149    }
150    let mut value = AndroidPropertyReadCookie::default();
151    unsafe {
152        __system_property_read_callback(
153            property.raw,
154            android_property_read_callback,
155            (&mut value as *mut AndroidPropertyReadCookie).cast(),
156        );
157    }
158    value
159        .value
160        .ok_or_else(|| io::Error::other("android property read callback did not run"))
161}
162
163#[cfg(target_os = "android")]
164fn system_android_property_serial(property: AndroidPropertyInfo) -> io::Result<u32> {
165    if property.raw.is_null() {
166        return Err(property_info_invalid());
167    }
168    serial_result(unsafe { __system_property_serial(property.raw) })
169}
170
171#[cfg(target_os = "android")]
172fn system_android_property_wait(
173    property: AndroidPropertyInfo,
174    old_serial: u32,
175    timeout: Option<Duration>,
176) -> io::Result<Option<u32>> {
177    if property.raw.is_null() {
178        return Err(property_info_invalid());
179    }
180    let timeout = timeout.map(duration_to_timespec).transpose()?;
181    let timeout_ptr = timeout
182        .as_ref()
183        .map_or(std::ptr::null(), |value| value as *const libc::timespec);
184    let mut new_serial = 0;
185    let changed =
186        unsafe { __system_property_wait(property.raw, old_serial, &mut new_serial, timeout_ptr) };
187    if changed {
188        Ok(Some(new_serial))
189    } else {
190        Ok(None)
191    }
192}
193
194#[cfg(target_os = "android")]
195fn system_android_property_set(key: &str, value: &str) -> io::Result<()> {
196    let key = validate_property_c_string(key)?;
197    let value = validate_property_c_string(value)?;
198    let status = unsafe { __system_property_set(key.as_ptr(), value.as_ptr()) };
199    if status == 0 {
200        Ok(())
201    } else {
202        Err(io::Error::from_raw_os_error(status))
203    }
204}
205
206#[cfg(not(target_os = "android"))]
207fn system_android_property_get(_key: &str) -> Option<String> {
208    let _ = ANDROID_PROP_VALUE_MAX;
209    None
210}
211
212#[cfg(not(target_os = "android"))]
213fn system_android_property_find(_key: &str) -> Option<AndroidPropertyInfo> {
214    None
215}
216
217#[cfg(not(target_os = "android"))]
218fn system_android_property_read(
219    _property: AndroidPropertyInfo,
220) -> io::Result<AndroidPropertyValue> {
221    Err(android_properties_unsupported())
222}
223
224#[cfg(not(target_os = "android"))]
225fn system_android_property_serial(_property: AndroidPropertyInfo) -> io::Result<u32> {
226    let _ = ANDROID_PROP_SERIAL_ERROR;
227    Err(android_properties_unsupported())
228}
229
230#[cfg(not(target_os = "android"))]
231fn system_android_property_wait(
232    _property: AndroidPropertyInfo,
233    _old_serial: u32,
234    timeout: Option<Duration>,
235) -> io::Result<Option<u32>> {
236    if let Some(timeout) = timeout {
237        let _ = duration_to_timespec(timeout)?;
238    }
239    Err(android_properties_unsupported())
240}
241
242#[cfg(not(target_os = "android"))]
243fn system_android_property_set(_key: &str, _value: &str) -> io::Result<()> {
244    Err(android_properties_unsupported())
245}
246
247#[cfg(target_os = "android")]
248unsafe extern "C" {
249    fn __system_property_get(name: *const libc::c_char, value: *mut libc::c_char) -> libc::c_int;
250    fn __system_property_set(name: *const libc::c_char, value: *const libc::c_char) -> libc::c_int;
251    fn __system_property_find(name: *const libc::c_char) -> *const AndroidPropertyInfoOpaque;
252    fn __system_property_read_callback(
253        pi: *const AndroidPropertyInfoOpaque,
254        callback: unsafe extern "C" fn(
255            *mut libc::c_void,
256            *const libc::c_char,
257            *const libc::c_char,
258            u32,
259        ),
260        cookie: *mut libc::c_void,
261    );
262    fn __system_property_serial(pi: *const AndroidPropertyInfoOpaque) -> u32;
263    fn __system_property_wait(
264        pi: *const AndroidPropertyInfoOpaque,
265        old_serial: u32,
266        new_serial_ptr: *mut u32,
267        relative_timeout: *const libc::timespec,
268    ) -> bool;
269}
270
271#[cfg(target_os = "android")]
272#[derive(Default)]
273struct AndroidPropertyReadCookie {
274    value: Option<AndroidPropertyValue>,
275}
276
277#[cfg(target_os = "android")]
278unsafe extern "C" fn android_property_read_callback(
279    cookie: *mut libc::c_void,
280    name: *const libc::c_char,
281    value: *const libc::c_char,
282    serial: u32,
283) {
284    use std::ffi::CStr;
285
286    let cookie = unsafe { &mut *(cookie.cast::<AndroidPropertyReadCookie>()) };
287    let name = unsafe { CStr::from_ptr(name) }
288        .to_string_lossy()
289        .into_owned();
290    let value = unsafe { CStr::from_ptr(value) }
291        .to_string_lossy()
292        .into_owned();
293    cookie.value = Some(AndroidPropertyValue {
294        name,
295        value,
296        serial,
297    });
298}
299
300#[cfg(test)]
301mod tests {
302    use super::*;
303    use std::cell::RefCell;
304    use std::collections::BTreeMap;
305
306    #[derive(Default)]
307    struct FakeStore {
308        entries: RefCell<BTreeMap<String, String>>,
309    }
310
311    impl AndroidPropertyStore for FakeStore {
312        fn get(&self, key: &str) -> Option<String> {
313            self.entries.borrow().get(key).cloned()
314        }
315
316        fn set(&self, key: &str, value: &str) -> io::Result<()> {
317            self.entries
318                .borrow_mut()
319                .insert(key.to_string(), value.to_string());
320            Ok(())
321        }
322    }
323
324    #[test]
325    fn fake_store_round_trips_properties() {
326        let store = FakeStore::default();
327        assert_eq!(store.get("debug.hwui.renderer"), None);
328        store.set("debug.hwui.renderer", "skiagl").unwrap();
329        assert_eq!(store.get("debug.hwui.renderer").as_deref(), Some("skiagl"));
330    }
331
332    #[test]
333    fn system_store_rejects_embedded_nul() {
334        let err = SystemAndroidPropertyStore
335            .set("debug.hwui.renderer", "skia\0gl")
336            .unwrap_err();
337        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
338    }
339
340    #[test]
341    fn duration_to_timespec_rejects_large_timeout() {
342        let err = duration_to_timespec(Duration::from_secs(u64::MAX)).unwrap_err();
343        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
344    }
345
346    #[test]
347    #[cfg(target_os = "android")]
348    fn serial_error_maps_to_io_error() {
349        assert!(serial_result(1).is_ok());
350        assert!(serial_result(ANDROID_PROP_SERIAL_ERROR).is_err());
351    }
352}