use std::path::{Path, PathBuf};
use super::paths::normalize_for_policy;
use crate::orchestration::CapabilityPolicy;
pub(super) fn normalized_read_only_roots(policy: &CapabilityPolicy) -> Vec<PathBuf> {
let mut roots = normalized_declared_read_only_roots(policy);
if super::declared_path_roots_active() {
return roots;
}
for dir in super::git_scope_extension_for_roots(&super::base_workspace_roots(policy)).read_only
{
if !roots.iter().any(|existing| existing == &dir) {
roots.push(dir);
}
}
roots
}
pub(super) fn normalized_declared_read_only_roots(policy: &CapabilityPolicy) -> Vec<PathBuf> {
let mut roots: Vec<PathBuf> = Vec::new();
for root in &policy.read_only_roots {
let root = normalize_for_policy(&super::resolve_policy_path(root));
if !roots.contains(&root) {
roots.push(root);
}
}
roots
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) fn sandbox_user_home_dir() -> Option<PathBuf> {
crate::user_dirs::home_dir().filter(|path| path.is_absolute())
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
fn home_roots<'a>(home: &Path, entries: impl IntoIterator<Item = &'a str>) -> Vec<PathBuf> {
let mut roots: Vec<_> = entries
.into_iter()
.map(|entry| normalize_for_policy(&home.join(entry)))
.collect();
roots.sort_unstable();
roots.dedup();
roots
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) fn developer_toolchain_read_roots_for_home(home: &Path) -> Vec<PathBuf> {
home_roots(
home,
crate::known_paths::toolchain_read_home_paths(cfg!(target_os = "windows")),
)
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(crate) fn developer_toolchain_cache_write_roots_for_home(home: &Path) -> Vec<PathBuf> {
home_roots(
home,
crate::known_paths::toolchain_cache_home_paths()
.iter()
.map(String::as_str),
)
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
pub(crate) fn package_manager_config_read_roots_for_home(home: &Path) -> Vec<PathBuf> {
home_roots(
home,
crate::known_paths::package_config_home_paths()
.iter()
.map(String::as_str),
)
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub(super) mod path_grants;
#[cfg(all(test, any(target_os = "linux", target_os = "macos")))]
mod tests {
use super::*;
#[test]
fn package_manager_roots_do_not_admit_unknown_xdg_siblings() {
let home = tempfile::tempdir().unwrap();
let roots = package_manager_config_read_roots_for_home(home.path());
for relative in [".config/unlisted/token", ".cache/unlisted/token"] {
let target = normalize_for_policy(&home.path().join(relative));
assert!(
!roots.iter().any(|root| target.starts_with(root)),
"unknown XDG sibling is covered by a preset: {roots:?}"
);
}
for relative in [".config/git", ".config/rustfmt", ".cache/pip"] {
assert!(roots.contains(&normalize_for_policy(&home.path().join(relative))));
}
}
#[test]
fn read_only_xdg_roots_do_not_cancel_writable_toolchain_caches() {
let home = tempfile::tempdir().unwrap();
let reads = package_manager_config_read_roots_for_home(home.path());
let cache = normalize_for_policy(&home.path().join(".cache"));
let mut checked = 0;
for writable in developer_toolchain_cache_write_roots_for_home(home.path()) {
if writable.starts_with(&cache) {
checked += 1;
assert!(
!reads.iter().any(|read| writable.starts_with(read)),
"read-only {reads:?} would cancel writes to {writable:?} on macOS"
);
}
}
assert!(checked >= 2, "must check the Go and Harn writable caches");
}
}