#![warn(unsafe_op_in_unsafe_fn)]
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 mod wire;
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<Result<system_properties::SystemProperties>> = OnceLock::new();
pub fn init(config: PropertyConfig) {
if let Err(e) = try_init(config) {
log::warn!("init: {e}");
}
}
pub fn try_init(config: PropertyConfig) -> Result<()> {
let props_dir = config.properties_dir.unwrap_or_else(|| {
log::info!("Using default properties directory: {PROP_DIRNAME}");
PathBuf::from(PROP_DIRNAME)
});
if SYSTEM_PROPERTIES_DIR.get().is_some() {
return Err(Error::FileValidation(
"System properties directory already initialized".into(),
));
}
if config.socket_dir.is_some() && system_property_set::socket_dir_is_set() {
return Err(Error::FileValidation(
"Socket directory already initialized".into(),
));
}
log::info!("Setting system properties directory to: {props_dir:?}");
SYSTEM_PROPERTIES_DIR.set(props_dir).map_err(|_| {
Error::FileValidation("System properties directory already initialized".into())
})?;
if let Some(socket_dir) = config.socket_dir {
if !system_property_set::set_socket_dir(&socket_dir) {
return Err(Error::FileValidation(
"Socket directory already initialized (race after pre-check)".into(),
));
}
log::info!("Successfully set socket directory to: {socket_dir:?}");
}
Ok(())
}
pub fn properties_dir() -> &'static Path {
SYSTEM_PROPERTIES_DIR
.get_or_init(|| {
log::info!("Using default properties directory: {PROP_DIRNAME}");
PathBuf::from(PROP_DIRNAME)
})
.as_path()
}
pub fn try_system_properties() -> Result<&'static system_properties::SystemProperties> {
SYSTEM_PROPERTIES
.get_or_init(|| {
let dir = properties_dir();
log::debug!("Initializing global SystemProperties instance from: {dir:?}");
system_properties::SystemProperties::new(dir).inspect_err(|e| {
log::error!("Failed to initialize SystemProperties from {dir:?}: {e}");
})
})
.as_ref()
.map_err(|e| {
Error::FileValidation(format_error_chain("SystemProperties init failed", e))
})
}
fn format_error_chain(prefix: &str, err: &dyn std::error::Error) -> String {
use std::fmt::Write;
let mut out = format!("{prefix}: {err}");
let mut source = err.source();
while let Some(s) = source {
let _ = write!(&mut out, ": {s}");
source = s.source();
}
out
}
pub fn system_properties() -> &'static system_properties::SystemProperties {
match try_system_properties() {
Ok(props) => props,
Err(e) => panic!("Failed to initialize SystemProperties: {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,
{
try_system_properties()?.read_with(name, |value| {
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,
{
let Ok(props) = try_system_properties() else {
return default;
};
match props.read_with(name, |value| {
if value.is_empty() {
return Err(());
}
value.parse::<T>().map_err(|_| ())
}) {
Ok(Ok(v)) => v,
_ => 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);
}
}