357 lines
12 KiB
Rust
357 lines
12 KiB
Rust
// Copyright (C) 2021 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! This crate provides the PropertyWatcher type, which watches for changes
|
|
//! in Android system properties.
|
|
|
|
use anyhow::Context;
|
|
use std::os::raw::c_char;
|
|
use std::ptr::null;
|
|
use std::{
|
|
ffi::{c_void, CStr, CString},
|
|
str::Utf8Error,
|
|
};
|
|
use system_properties_bindgen::prop_info as PropInfo;
|
|
use thiserror::Error;
|
|
|
|
/// Errors this crate can generate
|
|
#[derive(Error, Debug)]
|
|
pub enum PropertyWatcherError {
|
|
/// We can't watch for a property whose name contains a NUL character.
|
|
#[error("Cannot convert name to C string")]
|
|
BadNameError(#[from] std::ffi::NulError),
|
|
/// We can only watch for properties that exist when the watcher is created.
|
|
#[error("System property is absent")]
|
|
SystemPropertyAbsent,
|
|
/// System properties are not initialized
|
|
#[error("System properties are not initialized.")]
|
|
Uninitialized,
|
|
/// __system_property_wait timed out despite being given no timeout.
|
|
#[error("Wait failed")]
|
|
WaitFailed,
|
|
/// read callback was not called
|
|
#[error("__system_property_read_callback did not call callback")]
|
|
ReadCallbackNotCalled,
|
|
/// read callback gave us a NULL pointer
|
|
#[error("__system_property_read_callback gave us a NULL pointer instead of a string")]
|
|
MissingCString,
|
|
/// read callback gave us a bad C string
|
|
#[error("__system_property_read_callback gave us a non-UTF8 C string")]
|
|
BadCString(#[from] Utf8Error),
|
|
/// read callback returned an error
|
|
#[error("Callback failed")]
|
|
CallbackError(#[from] anyhow::Error),
|
|
/// Failure in setting the system property
|
|
#[error("__system_property_set failed.")]
|
|
SetPropertyFailed,
|
|
}
|
|
|
|
/// Result type specific for this crate.
|
|
pub type Result<T> = std::result::Result<T, PropertyWatcherError>;
|
|
|
|
/// PropertyWatcher takes the name of an Android system property such
|
|
/// as `keystore.boot_level`; it can report the current value of this
|
|
/// property, or wait for it to change.
|
|
pub struct PropertyWatcher {
|
|
prop_name: CString,
|
|
prop_info: *const PropInfo,
|
|
serial: system_properties_bindgen::__uint32_t,
|
|
}
|
|
|
|
impl PropertyWatcher {
|
|
/// Create a PropertyWatcher for the named system property.
|
|
pub fn new(name: &str) -> Result<Self> {
|
|
Ok(Self {
|
|
prop_name: CString::new(name)?,
|
|
prop_info: null(),
|
|
serial: 0,
|
|
})
|
|
}
|
|
|
|
// Lazy-initializing accessor for self.prop_info.
|
|
fn get_prop_info(&mut self) -> Option<*const PropInfo> {
|
|
if self.prop_info.is_null() {
|
|
// Unsafe required for FFI call. Input and output are both const.
|
|
// The returned pointer is valid for the lifetime of the program.
|
|
self.prop_info = unsafe {
|
|
system_properties_bindgen::__system_property_find(self.prop_name.as_ptr())
|
|
};
|
|
}
|
|
if self.prop_info.is_null() {
|
|
None
|
|
} else {
|
|
Some(self.prop_info)
|
|
}
|
|
}
|
|
|
|
fn read_raw(prop_info: *const PropInfo, mut f: impl FnMut(Option<&CStr>, Option<&CStr>)) {
|
|
// Unsafe function converts values passed to us by
|
|
// __system_property_read_callback to Rust form
|
|
// and pass them to inner callback.
|
|
unsafe extern "C" fn callback(
|
|
res_p: *mut c_void,
|
|
name: *const c_char,
|
|
value: *const c_char,
|
|
_: system_properties_bindgen::__uint32_t,
|
|
) {
|
|
let name = if name.is_null() {
|
|
None
|
|
} else {
|
|
Some(CStr::from_ptr(name))
|
|
};
|
|
let value = if value.is_null() {
|
|
None
|
|
} else {
|
|
Some(CStr::from_ptr(value))
|
|
};
|
|
let f = &mut *res_p.cast::<&mut dyn FnMut(Option<&CStr>, Option<&CStr>)>();
|
|
f(name, value);
|
|
}
|
|
|
|
let mut f: &mut dyn FnMut(Option<&CStr>, Option<&CStr>) = &mut f;
|
|
|
|
// Unsafe block for FFI call. We convert the FnMut
|
|
// to a void pointer, and unwrap it in our callback.
|
|
unsafe {
|
|
system_properties_bindgen::__system_property_read_callback(
|
|
prop_info,
|
|
Some(callback),
|
|
&mut f as *mut _ as *mut c_void,
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Call the passed function, passing it the name and current value
|
|
/// of this system property. See documentation for
|
|
/// `__system_property_read_callback` for details.
|
|
/// Returns an error if the property is empty or doesn't exist.
|
|
pub fn read<T, F>(&mut self, mut f: F) -> Result<T>
|
|
where
|
|
F: FnMut(&str, &str) -> anyhow::Result<T>,
|
|
{
|
|
let prop_info = self
|
|
.get_prop_info()
|
|
.ok_or(PropertyWatcherError::SystemPropertyAbsent)?;
|
|
let mut result = Err(PropertyWatcherError::ReadCallbackNotCalled);
|
|
Self::read_raw(prop_info, |name, value| {
|
|
// use a wrapping closure as an erzatz try block.
|
|
result = (|| {
|
|
let name = name.ok_or(PropertyWatcherError::MissingCString)?.to_str()?;
|
|
let value = value
|
|
.ok_or(PropertyWatcherError::MissingCString)?
|
|
.to_str()?;
|
|
f(name, value).map_err(PropertyWatcherError::CallbackError)
|
|
})()
|
|
});
|
|
result
|
|
}
|
|
|
|
// Waits for the property that self is watching to be created. Returns immediately if the
|
|
// property already exists.
|
|
fn wait_for_property_creation(&mut self) -> Result<()> {
|
|
let mut global_serial = 0;
|
|
loop {
|
|
match self.get_prop_info() {
|
|
Some(_) => return Ok(()),
|
|
None => {
|
|
// Unsafe call for FFI. The function modifies only global_serial, and has
|
|
// no side-effects.
|
|
if !unsafe {
|
|
// Wait for a global serial number change, then try again. On success,
|
|
// the function will update global_serial with the last version seen.
|
|
system_properties_bindgen::__system_property_wait(
|
|
null(),
|
|
global_serial,
|
|
&mut global_serial,
|
|
null(),
|
|
)
|
|
} {
|
|
return Err(PropertyWatcherError::WaitFailed);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Wait for the system property to change. This
|
|
/// records the serial number of the last change, so
|
|
/// race conditions are avoided.
|
|
pub fn wait(&mut self) -> Result<()> {
|
|
// If the property is null, then wait for it to be created. Subsequent waits will
|
|
// skip this step and wait for our specific property to change.
|
|
if self.prop_info.is_null() {
|
|
return self.wait_for_property_creation();
|
|
}
|
|
|
|
let mut new_serial = self.serial;
|
|
// Unsafe block to call __system_property_wait.
|
|
// All arguments are private to PropertyWatcher so we
|
|
// can be confident they are valid.
|
|
if !unsafe {
|
|
system_properties_bindgen::__system_property_wait(
|
|
self.prop_info,
|
|
self.serial,
|
|
&mut new_serial,
|
|
null(),
|
|
)
|
|
} {
|
|
return Err(PropertyWatcherError::WaitFailed);
|
|
}
|
|
self.serial = new_serial;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Reads a system property.
|
|
///
|
|
/// Returns `Ok(None)` if the property doesn't exist.
|
|
pub fn read(name: &str) -> Result<Option<String>> {
|
|
match PropertyWatcher::new(name)?.read(|_name, value| Ok(value.to_owned())) {
|
|
Ok(value) => Ok(Some(value)),
|
|
Err(PropertyWatcherError::SystemPropertyAbsent) => Ok(None),
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
fn parse_bool(value: &str) -> Option<bool> {
|
|
if ["1", "y", "yes", "on", "true"].contains(&value) {
|
|
Some(true)
|
|
} else if ["0", "n", "no", "off", "false"].contains(&value) {
|
|
Some(false)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Returns true if the system property `name` has the value "1", "y", "yes", "on", or "true",
|
|
/// false for "0", "n", "no", "off", or "false", or `default_value` otherwise.
|
|
pub fn read_bool(name: &str, default_value: bool) -> Result<bool> {
|
|
Ok(read(name)?
|
|
.as_deref()
|
|
.and_then(parse_bool)
|
|
.unwrap_or(default_value))
|
|
}
|
|
|
|
/// Writes a system property.
|
|
pub fn write(name: &str, value: &str) -> Result<()> {
|
|
if
|
|
// Unsafe required for FFI call. Input and output are both const and valid strings.
|
|
unsafe {
|
|
// If successful, __system_property_set returns 0, otherwise, returns -1.
|
|
system_properties_bindgen::__system_property_set(
|
|
CString::new(name)
|
|
.context("Failed to construct CString from name.")?
|
|
.as_ptr(),
|
|
CString::new(value)
|
|
.context("Failed to construct CString from value.")?
|
|
.as_ptr(),
|
|
)
|
|
} == 0
|
|
{
|
|
Ok(())
|
|
} else {
|
|
Err(PropertyWatcherError::SetPropertyFailed)
|
|
}
|
|
}
|
|
|
|
/// Iterates through the properties (that the current process is allowed to access).
|
|
pub fn foreach<F>(mut f: F) -> Result<()>
|
|
where
|
|
F: FnMut(&str, &str),
|
|
{
|
|
extern "C" fn read_callback<F: FnMut(&str, &str)>(
|
|
res_p: *mut c_void,
|
|
name: *const c_char,
|
|
value: *const c_char,
|
|
_: system_properties_bindgen::__uint32_t,
|
|
) {
|
|
// SAFETY: system properties are null-terminated C string in UTF-8. See IsLegalPropertyName
|
|
// and IsLegalPropertyValue in system/core/init/util.cpp.
|
|
let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
|
|
let value = unsafe { CStr::from_ptr(value) }.to_str().unwrap();
|
|
|
|
let ptr = res_p as *mut F;
|
|
// SAFETY: ptr points to the API user's callback, which was cast to `*mut c_void` below.
|
|
// Here we're casting it back.
|
|
let f = unsafe { ptr.as_mut() }.unwrap();
|
|
f(name, value);
|
|
}
|
|
|
|
extern "C" fn foreach_callback<F: FnMut(&str, &str)>(
|
|
prop_info: *const PropInfo,
|
|
res_p: *mut c_void,
|
|
) {
|
|
// SAFETY: FFI call with an internal callback function in Rust, with other parameters
|
|
// passed through.
|
|
unsafe {
|
|
system_properties_bindgen::__system_property_read_callback(
|
|
prop_info,
|
|
Some(read_callback::<F>),
|
|
res_p,
|
|
)
|
|
}
|
|
}
|
|
|
|
// SAFETY: FFI call with an internal callback function in Rust, and another client's callback
|
|
// that's cast only for our own use right above.
|
|
let retval = unsafe {
|
|
system_properties_bindgen::__system_property_foreach(
|
|
Some(foreach_callback::<F>),
|
|
&mut f as *mut _ as *mut c_void,
|
|
)
|
|
};
|
|
if retval < 0 {
|
|
Err(PropertyWatcherError::Uninitialized)
|
|
} else {
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parse_bool_test() {
|
|
for s in ["1", "y", "yes", "on", "true"] {
|
|
assert_eq!(parse_bool(s), Some(true), "testing with {}", s);
|
|
}
|
|
for s in ["0", "n", "no", "off", "false"] {
|
|
assert_eq!(parse_bool(s), Some(false), "testing with {}", s);
|
|
}
|
|
for s in ["random", "00", "of course", "no way", "YES", "Off"] {
|
|
assert_eq!(parse_bool(s), None, "testing with {}", s);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn read_absent_bool_test() {
|
|
let prop = "certainly.does.not.exist";
|
|
assert!(matches!(read(prop), Ok(None)));
|
|
assert!(read_bool(prop, true).unwrap_or(false));
|
|
assert!(!read_bool(prop, false).unwrap_or(true));
|
|
}
|
|
|
|
#[test]
|
|
fn foreach_test() {
|
|
let mut properties = Vec::new();
|
|
assert!(foreach(|name, value| {
|
|
properties.push((name.to_owned(), value.to_owned()));
|
|
})
|
|
.is_ok());
|
|
// Assuming the test runs on Android, any process can at least see some system properties.
|
|
assert!(!properties.is_empty());
|
|
}
|
|
}
|