1use 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}