use std::{
path::{Path, PathBuf},
sync::OnceLock,
};
#[derive(Debug, Clone, Default)]
pub struct PropertyConfig {
pub properties_dir: Option<PathBuf>,
pub socket_dir: Option<PathBuf>,
}
impl From<PathBuf> for PropertyConfig {
fn from(path: PathBuf) -> Self {
Self {
properties_dir: Some(path),
socket_dir: None,
}
}
}
impl From<String> for PropertyConfig {
fn from(path: String) -> Self {
Self {
properties_dir: Some(PathBuf::from(path)),
socket_dir: None,
}
}
}
impl From<&str> for PropertyConfig {
fn from(path: &str) -> Self {
Self {
properties_dir: Some(PathBuf::from(path)),
socket_dir: None,
}
}
}
impl PropertyConfig {
pub fn from_optional_path(path: Option<PathBuf>) -> Self {
match path {
Some(path) => Self::from(path),
None => Self::default(),
}
}
pub fn with_properties_dir<P: Into<PathBuf>>(dir: P) -> Self {
Self {
properties_dir: Some(dir.into()),
socket_dir: None,
}
}
pub fn with_socket_dir<P: Into<PathBuf>>(dir: P) -> Self {
Self {
properties_dir: None,
socket_dir: Some(dir.into()),
}
}
pub fn with_both_dirs<P1: Into<PathBuf>, P2: Into<PathBuf>>(
properties_dir: P1,
socket_dir: P2,
) -> Self {
Self {
properties_dir: Some(properties_dir.into()),
socket_dir: Some(socket_dir.into()),
}
}
pub fn builder() -> PropertyConfigBuilder {
PropertyConfigBuilder::default()
}
}
#[derive(Debug, Clone, Default)]
pub struct PropertyConfigBuilder {
properties_dir: Option<PathBuf>,
socket_dir: Option<PathBuf>,
}
impl PropertyConfigBuilder {
pub fn properties_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
self.properties_dir = Some(dir.into());
self
}
pub fn socket_dir<P: Into<PathBuf>>(mut self, dir: P) -> Self {
self.socket_dir = Some(dir.into());
self
}
pub fn build(self) -> PropertyConfig {
PropertyConfig {
properties_dir: self.properties_dir,
socket_dir: self.socket_dir,
}
}
}
pub mod errors;
pub use errors::{ContextWithLocation, Error, Result};
#[cfg(feature = "builder")]
mod build_property_parser;
mod context_node;
mod contexts_serialized;
mod property_area;
mod property_info;
mod property_info_parser;
#[cfg(feature = "builder")]
mod property_info_serializer;
mod system_properties;
mod system_property_set;
#[cfg(feature = "builder")]
mod trie_builder;
#[cfg(feature = "builder")]
mod trie_node_arena;
#[cfg(feature = "builder")]
mod trie_serializer;
#[cfg(feature = "builder")]
pub use build_property_parser::*;
#[cfg(feature = "builder")]
pub use property_info_serializer::*;
pub use system_properties::SystemProperties;
pub use system_property_set::socket_dir;
pub use system_property_set::{
PROPERTY_SERVICE_FOR_SYSTEM_SOCKET_NAME, PROPERTY_SERVICE_SOCKET_NAME,
};
pub const PROP_VALUE_MAX: usize = 92;
pub const PROP_DIRNAME: &str = "/dev/__properties__";
static SYSTEM_PROPERTIES_DIR: OnceLock<PathBuf> = OnceLock::new();
static SYSTEM_PROPERTIES: OnceLock<system_properties::SystemProperties> = OnceLock::new();
pub fn init(config: PropertyConfig) {
let props_dir = config.properties_dir.unwrap_or_else(|| {
log::info!("Using default properties directory: {PROP_DIRNAME}");
PathBuf::from(PROP_DIRNAME)
});
match SYSTEM_PROPERTIES_DIR.set(props_dir.clone()) {
Ok(_) => {
log::info!("Successfully set system properties directory to: {props_dir:?}");
}
Err(_) => {
log::warn!("System properties directory already set, ignoring new value");
}
}
if let Some(socket_dir) = config.socket_dir {
let success = system_property_set::set_socket_dir(&socket_dir);
if success {
log::info!("Successfully set socket directory to: {socket_dir:?}");
} else {
log::warn!("Socket directory already set, ignoring new value");
}
}
}
pub fn properties_dir() -> &'static Path {
let path = SYSTEM_PROPERTIES_DIR
.get_or_init(|| {
log::info!("Using default properties directory: {PROP_DIRNAME}");
PathBuf::from(PROP_DIRNAME)
})
.as_path();
path
}
pub fn system_properties() -> &'static system_properties::SystemProperties {
SYSTEM_PROPERTIES.get_or_init(|| {
let dir = properties_dir();
log::debug!("Initializing global SystemProperties instance from: {dir:?}");
match system_properties::SystemProperties::new(dir) {
Ok(props) => {
log::debug!("Successfully initialized global SystemProperties instance");
props
}
Err(e) => {
log::error!("Failed to initialize SystemProperties from {dir:?}: {e}");
panic!("Failed to initialize SystemProperties from {dir:?}: {e}");
}
}
})
}
pub(crate) fn bionic_align(value: usize, alignment: usize) -> usize {
assert!(
alignment.is_power_of_two(),
"Alignment must be a power of 2"
);
value.saturating_add(alignment - 1) & !(alignment - 1)
}
pub fn get<T>(name: &str) -> Result<T>
where
T: std::str::FromStr,
T::Err: std::fmt::Display,
{
let value = system_properties().get_with_result(name)?;
value.parse().map_err(|e| {
Error::Parse(format!(
"Failed to parse '{value}' for property '{name}': {e}"
))
})
}
pub fn get_or<T>(name: &str, default: T) -> T
where
T: std::str::FromStr + Clone,
{
match system_properties().get_with_result(name) {
Ok(value) if !value.is_empty() => value.parse().unwrap_or(default),
_ => default,
}
}
pub fn set<T: std::fmt::Display + ?Sized>(name: &str, value: &T) -> Result<()> {
system_property_set::set(name, &value.to_string())
}
#[cfg(test)]
mod tests {
#![allow(unused_imports)]
use super::*;
#[cfg(target_os = "android")]
use android_system_properties::AndroidSystemProperties;
use std::collections::HashMap;
use std::fs::{create_dir, remove_dir_all, File};
use std::io::Write;
use std::path::Path;
use std::sync::{Mutex, MutexGuard};
#[cfg(all(feature = "builder", not(target_os = "android")))]
const TEST_PROPERTY_DIR: &str = "__properties__";
#[cfg(any(feature = "builder", target_os = "android"))]
fn enable_logger() {
let _ = env_logger::builder().is_test(true).try_init();
}
#[cfg(target_os = "android")]
#[test]
fn test_get() {
const PROPERTIES: [&str; 40] = [
"ro.build.version.sdk",
"ro.build.version.release",
"ro.product.model",
"ro.product.manufacturer",
"ro.product.name",
"ro.serialno",
"ro.bootloader",
"ro.hardware",
"ro.revision",
"ro.kernel.qemu",
"dalvik.vm.heapsize",
"dalvik.vm.heapgrowthlimit",
"dalvik.vm.heapstartsize",
"dalvik.vm.heaptargetutilization",
"dalvik.vm.heapminfree",
"dalvik.vm.heapmaxfree",
"net.bt.name",
"net.change",
"net.dns1",
"net.dns2",
"net.hostname",
"net.tcp.default_init_rwnd",
"persist.sys.timezone",
"persist.sys.locale",
"persist.sys.dalvik.vm.lib.2",
"persist.sys.profiler_ms",
"persist.sys.usb.config",
"persist.service.acm.enable",
"ril.ecclist",
"ril.subscription.types",
"service.adb.tcp.port",
"service.bootanim.exit",
"service.camera.running",
"service.media.powersnd",
"sys.boot_completed",
"sys.usb.config",
"sys.usb.state",
"vold.post_fs_data_done",
"wifi.interface",
"wifi.supplicant_scan_interval",
];
enable_logger();
for prop in PROPERTIES.iter() {
let value1: String = get_or(prop, "".to_owned());
let value2 = AndroidSystemProperties::new().get(prop).unwrap_or_default();
println!("{}: [{}], [{}]", prop, value1, value2);
assert_eq!(value1, value2);
}
}
#[cfg(all(feature = "builder", not(target_os = "android")))]
fn load_properties() -> HashMap<String, String> {
let build_prop_files = vec![
"tests/android/product_build.prop",
"tests/android/system_build.prop",
"tests/android/system_dlkm_build.prop",
"tests/android/system_ext_build.prop",
"tests/android/vendor_build.prop",
"tests/android/vendor_dlkm_build.prop",
"tests/android/vendor_odm_build.prop",
"tests/android/vendor_odm_dlkm_build.prop",
];
let mut properties = HashMap::new();
for file in build_prop_files {
load_properties_from_file(Path::new(file), None, "u:r:init:s0", &mut properties)
.unwrap();
}
properties
}
#[cfg(all(feature = "builder", not(target_os = "android")))]
fn system_properties_area() -> MutexGuard<'static, Option<SystemProperties>> {
static SYSTEM_PROPERTIES: Mutex<Option<SystemProperties>> = Mutex::new(None);
let mut system_properties_guard = SYSTEM_PROPERTIES.lock().unwrap();
if system_properties_guard.is_none() {
*system_properties_guard = Some(build_property_dir(TEST_PROPERTY_DIR));
}
system_properties_guard
}
#[cfg(all(feature = "builder", not(target_os = "android")))]
fn build_property_dir(dir: &str) -> SystemProperties {
crate::init(PropertyConfig::from(PathBuf::from(dir)));
let property_contexts_files = vec![
"tests/android/plat_property_contexts",
"tests/android/system_ext_property_contexts",
"tests/android/vendor_property_contexts",
];
let mut property_infos = Vec::new();
for file in property_contexts_files {
let (mut property_info, errors) =
PropertyInfoEntry::parse_from_file(Path::new(file), false).unwrap();
if !errors.is_empty() {
log::error!("{errors:?}");
}
property_infos.append(&mut property_info);
}
let data: Vec<u8> =
build_trie(&property_infos, "u:object_r:build_prop:s0", "string").unwrap();
let dir = properties_dir();
remove_dir_all(dir).unwrap_or_default();
create_dir(dir).unwrap_or_default();
File::create(dir.join("property_info"))
.unwrap()
.write_all(&data)
.unwrap();
let properties = load_properties();
let dir = properties_dir();
let mut system_properties = SystemProperties::new_area(dir).unwrap_or_else(|e| {
panic!("Cannot create system properties: {e}. Please check if {dir:?} exists.")
});
for (key, value) in properties.iter() {
match system_properties.find(key.as_str()).unwrap() {
Some(prop_ref) => {
system_properties.update(&prop_ref, value.as_str()).unwrap();
}
None => {
system_properties.add(key.as_str(), value.as_str()).unwrap();
}
}
}
system_properties
}
#[cfg(all(feature = "builder", not(target_os = "android")))]
#[test]
fn test_property_info() {
enable_logger();
let _guard = system_properties_area();
let system_properties = system_properties();
let properties = load_properties();
for (key, value) in properties.iter() {
let prop_value = system_properties
.get_with_result(key.as_str())
.unwrap_or_default();
assert_eq!(prop_value, value.as_str());
}
}
#[cfg(all(feature = "builder", not(target_os = "android")))]
#[test]
fn test_wait() {
enable_logger();
let mut guard = system_properties_area();
let system_properties_area = guard.as_mut().unwrap();
let test_prop = "test.property";
let wait_any = || {
std::thread::spawn(move || {
let system_properties = system_properties();
system_properties.wait_any();
})
};
let handle = wait_any();
std::thread::sleep(std::time::Duration::from_millis(100));
system_properties_area.add(test_prop, "true").unwrap();
handle.join().unwrap();
let handle = std::thread::spawn(move || {
let system_properties = system_properties();
let index = system_properties.find(test_prop).unwrap();
system_properties.wait(index.as_ref(), None);
});
let handle_any = wait_any();
std::thread::sleep(std::time::Duration::from_millis(100));
let index = system_properties_area.find(test_prop).unwrap();
system_properties_area
.update(&index.unwrap(), "false")
.unwrap();
handle.join().unwrap();
handle_any.join().unwrap();
}
#[test]
fn test_bionic_align_normal() {
assert_eq!(bionic_align(0, 4), 0);
assert_eq!(bionic_align(1, 4), 4);
assert_eq!(bionic_align(4, 4), 4);
assert_eq!(bionic_align(5, 4), 8);
assert_eq!(bionic_align(7, 4), 8);
assert_eq!(bionic_align(8, 4), 8);
assert_eq!(bionic_align(0, 8), 0);
assert_eq!(bionic_align(1, 8), 8);
assert_eq!(bionic_align(8, 8), 8);
assert_eq!(bionic_align(9, 8), 16);
}
#[test]
fn test_bionic_align_overflow_safety() {
let size = usize::MAX - 10;
let align = 16;
let result = bionic_align(size, align);
assert_eq!(result % align, 0);
}
#[test]
#[should_panic(expected = "Alignment must be a power of 2")]
fn test_bionic_align_invalid_alignment() {
bionic_align(100, 3);
}
#[test]
fn test_bionic_align_edge_cases() {
assert_eq!(bionic_align(5, 1), 5);
assert_eq!(bionic_align(0, 1), 0);
assert_eq!(bionic_align(100, 64), 128);
assert_eq!(bionic_align(64, 64), 64);
assert_eq!(bionic_align(65, 64), 128);
}
}