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
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_android_property_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_android_property_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_android_property_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            android_property_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_android_property_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_android_property_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_android_property_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_android_property_get(_key: &str) -> Option<String> {
226    let _ = ANDROID_PROP_VALUE_MAX;
227    None
228}
229
230#[cfg(not(target_os = "android"))]
231fn system_android_property_find(_key: &str) -> Option<AndroidPropertyInfo> {
232    None
233}
234
235#[cfg(not(target_os = "android"))]
236fn system_android_property_read(
237    _property: AndroidPropertyInfo,
238) -> io::Result<AndroidPropertyValue> {
239    Err(android_properties_unsupported())
240}
241
242#[cfg(not(target_os = "android"))]
243fn system_android_property_serial(_property: AndroidPropertyInfo) -> io::Result<u32> {
244    let _ = ANDROID_PROP_SERIAL_ERROR;
245    Err(android_properties_unsupported())
246}
247
248#[cfg(not(target_os = "android"))]
249fn system_android_property_wait(
250    _property: AndroidPropertyInfo,
251    _old_serial: u32,
252    timeout: Option<Duration>,
253) -> io::Result<Option<u32>> {
254    if let Some(timeout) = timeout {
255        let _ = duration_to_timespec(timeout)?;
256    }
257    Err(android_properties_unsupported())
258}
259
260#[cfg(not(target_os = "android"))]
261fn system_android_property_set(_key: &str, _value: &str) -> io::Result<()> {
262    Err(android_properties_unsupported())
263}
264
265#[cfg(target_os = "android")]
266unsafe extern "C" {
267    fn __system_property_get(name: *const libc::c_char, value: *mut libc::c_char) -> libc::c_int;
268    fn __system_property_set(name: *const libc::c_char, value: *const libc::c_char) -> libc::c_int;
269    fn __system_property_find(name: *const libc::c_char) -> *const AndroidPropertyInfoOpaque;
270    fn __system_property_read_callback(
271        pi: *const AndroidPropertyInfoOpaque,
272        callback: unsafe extern "C" fn(
273            *mut libc::c_void,
274            *const libc::c_char,
275            *const libc::c_char,
276            u32,
277        ),
278        cookie: *mut libc::c_void,
279    );
280    fn __system_property_serial(pi: *const AndroidPropertyInfoOpaque) -> u32;
281    fn __system_property_wait(
282        pi: *const AndroidPropertyInfoOpaque,
283        old_serial: u32,
284        new_serial_ptr: *mut u32,
285        relative_timeout: *const libc::timespec,
286    ) -> bool;
287}
288
289#[cfg(target_os = "android")]
290#[derive(Default)]
291struct AndroidPropertyReadCookie {
292    value: Option<AndroidPropertyValue>,
293}
294
295#[cfg(target_os = "android")]
296unsafe extern "C" fn android_property_read_callback(
297    cookie: *mut libc::c_void,
298    name: *const libc::c_char,
299    value: *const libc::c_char,
300    serial: u32,
301) {
302    use std::ffi::CStr;
303
304    let cookie = unsafe { &mut *(cookie.cast::<AndroidPropertyReadCookie>()) };
305    let name = unsafe { CStr::from_ptr(name) }
306        .to_string_lossy()
307        .into_owned();
308    let value = unsafe { CStr::from_ptr(value) }
309        .to_string_lossy()
310        .into_owned();
311    cookie.value = Some(AndroidPropertyValue {
312        name,
313        value,
314        serial,
315    });
316}
317
318#[cfg(test)]
319mod tests {
320    use super::*;
321    use std::cell::RefCell;
322    use std::collections::BTreeMap;
323
324    #[derive(Default)]
325    struct FakeStore {
326        entries: RefCell<BTreeMap<String, String>>,
327    }
328
329    impl AndroidPropertyStore for FakeStore {
330        fn get(&self, key: &str) -> Option<String> {
331            self.entries.borrow().get(key).cloned()
332        }
333
334        fn set(&self, key: &str, value: &str) -> io::Result<()> {
335            self.entries
336                .borrow_mut()
337                .insert(key.to_string(), value.to_string());
338            Ok(())
339        }
340    }
341
342    #[test]
343    fn fake_store_round_trips_properties() {
344        let store = FakeStore::default();
345        assert_eq!(store.get("debug.hwui.renderer"), None);
346        store.set("debug.hwui.renderer", "skiagl").unwrap();
347        assert_eq!(store.get("debug.hwui.renderer").as_deref(), Some("skiagl"));
348    }
349
350    #[test]
351    fn system_store_rejects_embedded_nul() {
352        let err = SystemAndroidPropertyStore
353            .set("debug.hwui.renderer", "skia\0gl")
354            .unwrap_err();
355        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
356    }
357
358    #[test]
359    fn duration_to_timespec_rejects_large_timeout() {
360        let err = duration_to_timespec(Duration::from_secs(u64::MAX)).unwrap_err();
361        assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
362    }
363
364    #[test]
365    #[cfg(target_os = "android")]
366    fn serial_error_maps_to_io_error() {
367        assert!(serial_result(1).is_ok());
368        assert!(serial_result(ANDROID_PROP_SERIAL_ERROR).is_err());
369    }
370}