use log::{error, warn};
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::Path;
use crate::errors::*;
use rustix::process::{Gid, Pid, Uid};
#[cfg(any(target_os = "android", target_os = "linux"))]
use rustix::net::UCred;
#[cfg(target_os = "macos")]
pub struct UCred {
pub pid: Pid,
pub uid: Uid,
pub gid: Gid,
}
const RESTORECON_PROPERTY: &str = "selinux.restorecon_recursive";
pub fn check_permissions(_key: &str, _value: &str, _context: &str, _cr: &UCred) -> Result<()> {
Ok(())
}
pub fn load_properties_from_file(
filename: &Path,
filter: Option<&str>,
context: &str,
properties: &mut HashMap<String, String>,
) -> Result<()> {
let file =
File::open(filename).context_with_location(format!("Failed to open to {filename:?}"))?;
let reader = BufReader::new(file);
let has_filter = match filter {
Some(filter) => !filter.is_empty(),
None => false,
};
let mut line_count = 0;
let mut _processed_properties = 0;
let mut _skipped_lines = 0;
for line in reader.lines() {
line_count += 1;
let line = line.map_err(Error::from)?;
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
_skipped_lines += 1;
continue;
}
if !has_filter && line.starts_with("import ") {
warn!("Line {line_count}: Import statements not implemented: {line}");
unimplemented!("import")
} else {
let (key, value) = match line.find('=') {
Some(pos) => (&line[..pos], line[pos + 1..].trim()),
None => {
_skipped_lines += 1;
continue;
}
};
if has_filter {
let filter = filter.expect("filter must be valid.");
if filter.ends_with('*') {
if let Some(prefix) = filter.strip_suffix('*') {
if !key.starts_with(prefix) {
continue;
}
}
} else if line != filter {
continue;
}
}
if key.starts_with("ctl.") || key == "sys.powerctl" || key == RESTORECON_PROPERTY {
error!("Line {line_count}: Ignoring disallowed property '{key}' with special meaning in prop file '{filename:?}'");
continue;
}
let cr = UCred {
pid: Pid::from_raw(1).expect("Valid PID for init process"),
uid: Uid::from_raw(0),
gid: Gid::from_raw(0),
};
match check_permissions(key, value, context, &cr) {
Ok(_) => {
if let Some(old_value) = properties.insert(key.to_string(), value.to_string()) {
warn!(
"Line {line_count}: Overriding previous property '{key}':'{old_value}' with new value '{value}'"
);
}
_processed_properties += 1;
}
Err(e) => {
error!("Line {line_count}: Failed to check permissions for '{key}': {e}");
continue;
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
#[cfg(not(target_os = "android"))]
use super::*;
#[cfg(not(target_os = "android"))]
#[test]
fn test_load_properties_from_file() {
let mut properties = HashMap::new();
load_properties_from_file(
Path::new("tests/android/system_build.prop"),
None,
"u:r:init:s0",
&mut properties,
)
.unwrap();
assert_eq!(
properties.get("persist.sys.usb.config"),
Some(&"adb".to_string())
);
}
}