159 lines
4.6 KiB
Rust
159 lines
4.6 KiB
Rust
|
|
use anyhow::{bail, Context, Result};
|
||
|
|
use log::LevelFilter;
|
||
|
|
use simplelog::{Config, WriteLogger};
|
||
|
|
use std::fs::{OpenOptions, write, remove_file, create_dir_all, remove_dir_all, read_to_string};
|
||
|
|
use std::path::{Path, PathBuf};
|
||
|
|
|
||
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||
|
|
use extattr::{lsetxattr, Flags as XattrFlags};
|
||
|
|
|
||
|
|
const SELINUX_XATTR: &str = "security.selinux";
|
||
|
|
|
||
|
|
const TEMP_DIR_SUFFIX: &str = ".magic_mount";
|
||
|
|
|
||
|
|
const TMPFS_CANDIDATES: &[&str] = &["/mnt/vendor", "/mnt", "/debug_ramdisk"];
|
||
|
|
|
||
|
|
pub fn lsetfilecon<P: AsRef<Path>>(path: P, con: &str) -> Result<()> {
|
||
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||
|
|
lsetxattr(&path, SELINUX_XATTR, con, XattrFlags::empty()).with_context(|| {
|
||
|
|
format!(
|
||
|
|
"Failed to change SELinux context for {}",
|
||
|
|
path.as_ref().display()
|
||
|
|
)
|
||
|
|
})?;
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||
|
|
pub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {
|
||
|
|
let con = extattr::lgetxattr(&path, SELINUX_XATTR).with_context(|| {
|
||
|
|
format!(
|
||
|
|
"Failed to get SELinux context for {}",
|
||
|
|
path.as_ref().display()
|
||
|
|
)
|
||
|
|
})?;
|
||
|
|
let con = String::from_utf8_lossy(&con);
|
||
|
|
Ok(con.to_string())
|
||
|
|
}
|
||
|
|
|
||
|
|
#[cfg(not(any(target_os = "linux", target_os = "android")))]
|
||
|
|
pub fn lgetfilecon<P: AsRef<Path>>(path: P) -> Result<String> {
|
||
|
|
unimplemented!()
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn ensure_dir_exists<T: AsRef<Path>>(dir: T) -> Result<()> {
|
||
|
|
let result = create_dir_all(&dir);
|
||
|
|
if dir.as_ref().is_dir() && result.is_ok() {
|
||
|
|
Ok(())
|
||
|
|
} else {
|
||
|
|
bail!("{} is not a regular directory", dir.as_ref().display())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn init_logger(logfile: &Path, verbose: bool) -> Result<()> {
|
||
|
|
let level = if verbose {
|
||
|
|
LevelFilter::Debug
|
||
|
|
} else if cfg!(debug_assertions) {
|
||
|
|
LevelFilter::Debug
|
||
|
|
} else {
|
||
|
|
LevelFilter::Info
|
||
|
|
};
|
||
|
|
|
||
|
|
let file = OpenOptions::new()
|
||
|
|
.create(true)
|
||
|
|
.append(true)
|
||
|
|
.open(logfile)
|
||
|
|
.with_context(|| format!("failed to open log file {logfile:?}"))?;
|
||
|
|
|
||
|
|
WriteLogger::init(level, Config::default(), file)
|
||
|
|
.context("failed to init file logger")?;
|
||
|
|
|
||
|
|
log::info!("log level: {:?}", level);
|
||
|
|
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
fn is_writable_tmpfs(path: &Path) -> bool {
|
||
|
|
if !path.is_dir() {
|
||
|
|
log::debug!(" {} is not a directory", path.display());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if let Ok(mounts) = read_to_string("/proc/mounts") {
|
||
|
|
let path_str = path.to_string_lossy();
|
||
|
|
let is_tmpfs = mounts.lines().any(|line| {
|
||
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||
|
|
parts.len() >= 3 && parts[1] == path_str && parts[2] == "tmpfs"
|
||
|
|
});
|
||
|
|
|
||
|
|
if !is_tmpfs {
|
||
|
|
log::debug!(" {} is not a tmpfs", path.display());
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
log::debug!(" {} is a tmpfs", path.display());
|
||
|
|
} else {
|
||
|
|
log::debug!(" failed to read /proc/mounts");
|
||
|
|
}
|
||
|
|
|
||
|
|
let test_file = path.join(format!(".mm_test_{}", std::process::id()));
|
||
|
|
let writable = write(&test_file, b"test").is_ok();
|
||
|
|
|
||
|
|
if writable {
|
||
|
|
let _ = remove_file(&test_file);
|
||
|
|
log::debug!(" {} is writable", path.display());
|
||
|
|
} else {
|
||
|
|
log::debug!(" {} is not writable", path.display());
|
||
|
|
}
|
||
|
|
|
||
|
|
writable
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn select_temp_dir() -> Result<PathBuf> {
|
||
|
|
log::debug!("searching for suitable tmpfs mount point...");
|
||
|
|
|
||
|
|
for candidate in TMPFS_CANDIDATES {
|
||
|
|
let path = Path::new(candidate);
|
||
|
|
log::debug!("checking tmpfs candidate: {}", path.display());
|
||
|
|
|
||
|
|
if is_writable_tmpfs(path) {
|
||
|
|
let temp_dir = path.join(TEMP_DIR_SUFFIX);
|
||
|
|
log::info!(
|
||
|
|
"selected tmpfs: {} -> {}",
|
||
|
|
path.display(),
|
||
|
|
temp_dir.display()
|
||
|
|
);
|
||
|
|
return Ok(temp_dir);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bail!(
|
||
|
|
"no writable tmpfs found in candidates: {}",
|
||
|
|
TMPFS_CANDIDATES.join(", ")
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn ensure_temp_dir(temp_dir: &Path) -> Result<()> {
|
||
|
|
if temp_dir.exists() {
|
||
|
|
log::debug!("cleaning existing temp dir: {}", temp_dir.display());
|
||
|
|
remove_dir_all(temp_dir)
|
||
|
|
.with_context(|| format!("failed to clean temp dir {temp_dir:?}"))?;
|
||
|
|
}
|
||
|
|
|
||
|
|
create_dir_all(temp_dir)
|
||
|
|
.with_context(|| format!("failed to create temp dir {temp_dir:?}"))?;
|
||
|
|
|
||
|
|
log::debug!("temp dir ready: {}", temp_dir.display());
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
pub fn cleanup_temp_dir(temp_dir: &Path) {
|
||
|
|
if let Err(e) = remove_dir_all(temp_dir) {
|
||
|
|
log::warn!(
|
||
|
|
"failed to clean up temp dir {}: {:#}",
|
||
|
|
temp_dir.display(),
|
||
|
|
e
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
log::debug!("cleaned up temp dir: {}", temp_dir.display());
|
||
|
|
}
|
||
|
|
}
|