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