use crate::models::dirs;
#[cfg(not(target_os = "linux"))]
use std::path::{Path, PathBuf};
pub fn build_vm_mount_policy(run_options: &crate::run::RunOptions) -> Vec<String> {
let mut specs = Vec::new();
add_epkg_mount_specs(&mut specs);
#[cfg(target_os = "linux")]
if std::path::Path::new("/lib/modules").exists() {
specs.push("/lib/modules:ro,try".to_string());
}
specs.extend(run_options.effective_sandbox.mount_specs.iter().cloned());
if !run_options.chdir_to_env_root {
if let Ok(cwd) = std::env::current_dir() {
let cwd_str = cwd.to_string_lossy();
if cwd.is_absolute() && cwd.exists() {
log::trace!("VM mount policy: adding cwd {}", cwd_str);
specs.push(format!("{}:@{}", cwd_str, cwd_str));
}
}
}
#[cfg(target_os = "linux")]
specs.extend(windows_drive_mount_specs());
let epkg_src_path = crate::dirs::get_epkg_src_path();
if epkg_src_path.exists() {
specs.push(format!("{}:try", epkg_src_path.display()));
}
specs
}
#[cfg(not(target_os = "linux"))]
pub fn build_virtiofs_mounts(
env_root: &Path,
run_options: &crate::run::RunOptions,
) -> Vec<(String, String, String, bool)> {
let resolved_specs = resolve_and_filter_mount_specs(
&build_vm_mount_policy(run_options),
env_root,
);
resolved_specs
.into_iter()
.map(|(host_path, guest_path, read_only, _try_only)| {
let tag = generate_virtiofs_tag(&host_path);
let guest = guest_path.to_string_lossy().to_string();
(tag, host_path.to_string_lossy().to_string(), guest, read_only)
})
.collect()
}
#[cfg(not(target_os = "linux"))]
pub fn generate_virtiofs_tag(path: &Path) -> String {
let tag = path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("root");
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
path.hash(&mut hasher);
let hash = hasher.finish();
format!("{}_{}", tag, hash % 10000)
}
#[cfg(not(target_os = "linux"))]
pub fn resolve_and_filter_mount_specs(
specs: &[String],
env_root: &Path,
) -> Vec<(PathBuf, PathBuf, bool, bool)> {
let mut filtered = Vec::new();
let mut mounted_canonicals: Vec<PathBuf> = Vec::new();
for spec_str in specs {
if let Some((host_path, guest_path, read_only, try_only)) = parse_mount_spec(spec_str, env_root) {
if !host_path.exists() || !host_path.is_dir() {
log::trace!("Mount spec skipped (not a directory): {}", host_path.display());
continue;
}
let canonical = match host_path.canonicalize() {
Ok(c) => c,
Err(e) => {
log::warn!("Cannot canonicalize {}: {}", host_path.display(), e);
continue;
}
};
if is_path_covered_by(&canonical, &mounted_canonicals) {
log::trace!("Mount spec skipped (covered by existing): {}", host_path.display());
continue;
}
filtered.push((canonical.clone(), guest_path, read_only, try_only));
mounted_canonicals.push(canonical);
}
}
filtered
}
#[cfg(not(target_os = "linux"))]
fn parse_mount_spec(spec_str: &str, env_root: &Path) -> Option<(PathBuf, PathBuf, bool, bool)> {
let parts: Vec<&str> = spec_str.split(':').collect();
#[cfg(target_os = "linux")]
if parts.len() >= 2 {
if crate::mount::PSEUDO_FS_TYPES.contains(&parts[0]) {
return None;
}
}
let (source, target, options) = if parts.len() == 1 {
(parts[0], parts[0], "")
} else if parts.len() == 2 {
if parts[1].contains(',') || parts[1] == "ro" || parts[1] == "rw" || parts[1].starts_with("ro") || parts[1].starts_with("try") {
(parts[0], parts[0], parts[1])
} else {
(parts[0], parts[1], "")
}
} else if parts.len() >= 3 {
(parts[0], parts[1], parts[2])
} else {
return None;
};
let host_path = if source.starts_with('@') {
env_root.join(&source[1..])
} else {
PathBuf::from(source)
};
let guest_path = if target.starts_with('@') {
env_root.join(&target[1..])
} else if target.starts_with("//") {
PathBuf::from(&target[2..])
} else {
PathBuf::from(target)
};
let read_only = options.contains("ro");
let try_only = options.contains("try");
Some((host_path, guest_path, read_only, try_only))
}
#[cfg(not(target_os = "linux"))]
fn is_path_covered_by(path: &Path, mounted: &[PathBuf]) -> bool {
mounted.iter().any(|m| path == m || path.starts_with(m))
}
pub fn add_epkg_mount_specs(specs: &mut Vec<String>) {
let dirs = dirs();
specs.push(format!("{}:try", dirs.home_epkg.display()));
specs.push(format!("{}:try", dirs.home_cache.display()));
specs.push(format!("{}:ro,try", dirs.opt_epkg.display()));
add_epkg_bin_dir_mount(specs);
}
pub fn add_epkg_bin_dir_mount(specs: &mut Vec<String>) {
let Ok(epkg_exe) = std::env::current_exe() else { return };
let Some(epkg_bin_dir) = epkg_exe.parent() else { return };
let dirs = dirs();
if epkg_bin_dir.starts_with(&dirs.home_epkg) || epkg_bin_dir.starts_with(&dirs.opt_epkg) {
return;
}
specs.push(format!("{}:ro", epkg_bin_dir.display()));
}
#[cfg(target_os = "linux")]
pub fn windows_drive_mount_specs() -> Vec<String> {
let mut specs = Vec::new();
let mnt = std::path::Path::new("/mnt");
if !mnt.exists() {
return specs;
}
let Ok(entries) = std::fs::read_dir(mnt) else { return specs };
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.len() == 1 && name_str.chars().next().unwrap().is_ascii_alphabetic() {
if entry.metadata().map(|m| m.is_dir()).unwrap_or(false) {
specs.push(format!("/mnt/{}:try", name_str));
}
}
}
specs
}