use std::path::{Path, PathBuf};
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
pub fn is_safe_owned_dir(path: &Path, uid: u32) -> bool {
let Ok(md) = std::fs::symlink_metadata(path) else {
return false;
};
md.is_dir() && md.uid() == uid && (md.mode() & 0o077) == 0
}
pub fn ensure_safe_dir(dir: &Path, uid: u32) -> bool {
if let Some(parent) = dir.parent() {
let _ = std::fs::create_dir_all(parent);
}
match std::fs::DirBuilder::new().mode(0o700).create(dir) {
Ok(()) => return true,
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(_) => return false,
}
if let Ok(md) = std::fs::symlink_metadata(dir)
&& md.is_dir()
&& md.uid() == uid
&& md.mode() & 0o077 != 0
{
let _ = std::fs::set_permissions(dir, PermissionsExt::from_mode(0o700));
}
is_safe_owned_dir(dir, uid)
}
pub fn privileged_base() -> Option<PathBuf> {
privileged_base_in(0, Path::new("/run"), Path::new("/tmp"))
}
pub fn privileged_base_in(uid: u32, run: &Path, tmp: &Path) -> Option<PathBuf> {
let run_is_private = std::fs::symlink_metadata(run)
.is_ok_and(|md| md.is_dir() && md.uid() == uid && md.mode() & 0o022 == 0);
if run_is_private {
let dir = run.join("onelf");
if create_private_dir(&dir, uid) {
return Some(dir);
}
}
let dir = tmp.join(format!("onelf-{uid}"));
create_private_dir(&dir, uid).then_some(dir)
}
fn persistent_privileged_base() -> Option<PathBuf> {
persistent_base_in(0, Path::new("/var/cache"))
}
pub fn persistent_privileged_root() -> Option<PathBuf> {
persistent_privileged_base().map(|base| base.join("onelf"))
}
fn persistent_base_in(uid: u32, var_cache: &Path) -> Option<PathBuf> {
std::fs::symlink_metadata(var_cache)
.is_ok_and(|md| md.is_dir() && md.uid() == uid && md.mode() & 0o022 == 0)
.then(|| var_cache.to_path_buf())
}
pub fn create_private_dir(dir: &Path, uid: u32) -> bool {
match std::fs::DirBuilder::new().mode(0o700).create(dir) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(_) => return false,
}
is_safe_owned_dir(dir, uid)
}
fn env_base(uid: u32, var: impl Fn(&str) -> Option<std::ffi::OsString>) -> Option<PathBuf> {
if uid == 0 {
return None;
}
let abs = |v: std::ffi::OsString| -> Option<PathBuf> {
let p = PathBuf::from(v);
p.is_absolute().then_some(p)
};
var("XDG_CACHE_HOME")
.and_then(abs)
.or_else(|| var("HOME").and_then(abs).map(|h| h.join(".cache")))
}
pub fn resolve_root(uid: u32, fallback: Option<PathBuf>) -> Option<PathBuf> {
resolve_root_with(uid, |name| std::env::var_os(name), fallback)
}
fn resolve_root_with(
uid: u32,
var: impl Fn(&str) -> Option<std::ffi::OsString>,
fallback: Option<PathBuf>,
) -> Option<PathBuf> {
let base = env_base(uid, var).or(fallback).or_else(|| {
(uid == 0)
.then(|| persistent_privileged_base().or_else(privileged_base))
.flatten()
})?;
let onelf = base.join("onelf");
ensure_safe_dir(&onelf, uid).then_some(onelf)
}
pub fn proposed_root(uid: u32, fallback: Option<PathBuf>) -> Option<PathBuf> {
proposed_root_with(uid, |name| std::env::var_os(name), fallback)
}
fn proposed_root_with(
uid: u32,
var: impl Fn(&str) -> Option<std::ffi::OsString>,
fallback: Option<PathBuf>,
) -> Option<PathBuf> {
env_base(uid, var)
.or(fallback)
.map(|base| base.join("onelf"))
}
pub fn pkg_dir(root: &Path, package_id: &str) -> PathBuf {
root.join("pkg").join(package_id)
}
pub fn ready_marker(root: &Path, package_id: &str) -> PathBuf {
root.join("pkg").join(format!(".{package_id}.ready"))
}
pub fn extract_tmp_dir(root: &Path, package_id: &str) -> PathBuf {
root.join("pkg").join(format!(".{package_id}.tmp"))
}
pub fn lock_path(root: &Path, package_id: &str) -> PathBuf {
root.join("lock").join(package_id)
}
pub fn extract_lock_path(root: &Path, package_id: &str) -> PathBuf {
root.join("lock").join(format!("{package_id}.extract"))
}
pub fn meta_path(root: &Path, package_id: &str) -> PathBuf {
root.join("meta").join(package_id)
}
pub fn platform_dir(root: &Path) -> PathBuf {
root.join("platform")
}
pub fn cas_lock_path(root: &Path) -> PathBuf {
root.join("lock").join("cas")
}
#[cfg(test)]
mod tests {
use super::*;
fn unique(tag: &str) -> std::path::PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static SEQ: AtomicU64 = AtomicU64::new(0);
std::env::temp_dir().join(format!(
"onelf-{tag}-{}-{}",
std::process::id(),
SEQ.fetch_add(1, Ordering::Relaxed)
))
}
fn uid() -> u32 {
let p = unique("uidprobe");
std::fs::write(&p, b"").unwrap();
let u = std::fs::symlink_metadata(&p).unwrap().uid();
let _ = std::fs::remove_file(&p);
u
}
#[test]
fn safe_dir_rules() {
let me = uid();
let dir = unique("layout");
let _ = std::fs::remove_dir_all(&dir);
assert!(ensure_safe_dir(&dir, me), "fresh dir is created 0700");
assert!(is_safe_owned_dir(&dir, me));
std::fs::set_permissions(&dir, PermissionsExt::from_mode(0o755)).unwrap();
assert!(!is_safe_owned_dir(&dir, me), "0755 is not owner-private");
assert!(ensure_safe_dir(&dir, me), "our own dir is tightened back");
assert!(is_safe_owned_dir(&dir, me));
assert!(
!is_safe_owned_dir(&dir, me.wrapping_add(1)),
"another uid's directory is refused, never chmod'd"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn missing_and_non_directory_are_unsafe() {
let me = uid();
assert!(!is_safe_owned_dir(Path::new("/nonexistent/onelf/xyz"), me));
let f = unique("layout-file");
std::fs::write(&f, b"x").unwrap();
assert!(!is_safe_owned_dir(&f, me), "a regular file is not a dir");
let _ = std::fs::remove_file(&f);
}
#[test]
fn relative_env_roots_are_not_trusted() {
let me = uid();
let fallback = unique("fb");
let _ = std::fs::remove_dir_all(&fallback);
std::fs::create_dir_all(&fallback).unwrap();
let prev_xdg = std::env::var_os("XDG_CACHE_HOME");
let prev_home = std::env::var_os("HOME");
unsafe {
std::env::set_var("XDG_CACHE_HOME", "relative/path");
std::env::remove_var("HOME");
}
let root = resolve_root(me, Some(fallback.clone()));
unsafe {
match prev_xdg {
Some(v) => std::env::set_var("XDG_CACHE_HOME", v),
None => std::env::remove_var("XDG_CACHE_HOME"),
}
if let Some(v) = prev_home {
std::env::set_var("HOME", v);
}
}
assert_eq!(root, Some(fallback.join("onelf")));
let _ = std::fs::remove_dir_all(&fallback);
}
fn env_of(pairs: &[(&str, &Path)]) -> impl Fn(&str) -> Option<std::ffi::OsString> + use<> {
let pairs: Vec<(String, PathBuf)> = pairs
.iter()
.map(|(k, v)| (k.to_string(), v.to_path_buf()))
.collect();
move |name| {
pairs
.iter()
.find(|(k, _)| k == name)
.map(|(_, v)| v.clone().into_os_string())
}
}
#[test]
fn root_ignores_the_environment_cache_base() {
let xdg = unique("root-xdg");
let home = unique("root-home");
let fallback = unique("root-fb");
for dir in [&xdg, &home, &fallback] {
std::fs::create_dir_all(dir).unwrap();
}
let both = env_of(&[("XDG_CACHE_HOME", &xdg), ("HOME", &home)]);
let home_only = env_of(&[("HOME", &home)]);
assert_eq!(
proposed_root_with(0, &both, Some(fallback.clone())),
Some(fallback.join("onelf"))
);
assert_eq!(proposed_root_with(0, &both, None), None);
assert_eq!(
proposed_root_with(1000, &both, Some(fallback.clone())),
Some(xdg.join("onelf"))
);
assert_eq!(
proposed_root_with(1000, &home_only, None),
Some(home.join(".cache").join("onelf"))
);
let _ = resolve_root_with(0, &both, Some(fallback.clone()));
assert_eq!(std::fs::read_dir(&xdg).unwrap().count(), 0);
assert_eq!(std::fs::read_dir(&home).unwrap().count(), 0);
for dir in [&xdg, &home, &fallback] {
let _ = std::fs::remove_dir_all(dir);
}
}
#[test]
fn the_privileged_base_prefers_a_private_run_directory() {
let me = uid();
let top = unique("pbase");
let run = top.join("run");
let tmp = top.join("tmp");
std::fs::create_dir_all(&run).unwrap();
std::fs::create_dir_all(&tmp).unwrap();
std::fs::set_permissions(&run, PermissionsExt::from_mode(0o755)).unwrap();
let got = privileged_base_in(me, &run, &tmp).unwrap();
assert_eq!(got, run.join("onelf"));
assert_eq!(
std::fs::symlink_metadata(&got).unwrap().mode() & 0o777,
0o700
);
std::fs::remove_dir(&got).unwrap();
std::fs::set_permissions(&run, PermissionsExt::from_mode(0o777)).unwrap();
assert_eq!(
privileged_base_in(me, &run, &tmp),
Some(tmp.join(format!("onelf-{me}"))),
"a run directory others may write is never used"
);
assert!(!run.join("onelf").exists());
std::fs::remove_dir(tmp.join(format!("onelf-{me}"))).unwrap();
std::os::unix::fs::symlink(&top, tmp.join(format!("onelf-{me}"))).unwrap();
assert_eq!(
privileged_base_in(me, &top.join("absent"), &tmp),
None,
"a planted fallback name is refused"
);
let _ = std::fs::remove_dir_all(&top);
}
#[test]
fn the_persistent_base_requires_a_private_owned_directory() {
let me = uid();
let top = unique("vbase");
let cache = top.join("cache");
std::fs::create_dir_all(&cache).unwrap();
std::fs::set_permissions(&cache, PermissionsExt::from_mode(0o755)).unwrap();
assert_eq!(persistent_base_in(me, &cache), Some(cache.clone()));
assert_eq!(persistent_base_in(me.wrapping_add(1), &cache), None);
std::fs::set_permissions(&cache, PermissionsExt::from_mode(0o777)).unwrap();
assert_eq!(persistent_base_in(me, &cache), None, "others may write it");
let link = top.join("link");
std::os::unix::fs::symlink(&cache, &link).unwrap();
std::fs::set_permissions(&cache, PermissionsExt::from_mode(0o755)).unwrap();
assert_eq!(persistent_base_in(me, &link), None, "a symlink is refused");
assert_eq!(persistent_base_in(me, &top.join("absent")), None);
let _ = std::fs::remove_dir_all(&top);
}
#[test]
fn layout_paths_are_stable() {
let root = Path::new("/c/onelf");
assert_eq!(pkg_dir(root, "ab"), Path::new("/c/onelf/pkg/ab"));
assert_eq!(
ready_marker(root, "ab"),
Path::new("/c/onelf/pkg/.ab.ready")
);
assert_eq!(lock_path(root, "ab"), Path::new("/c/onelf/lock/ab"));
assert_eq!(
extract_lock_path(root, "ab"),
Path::new("/c/onelf/lock/ab.extract")
);
assert_eq!(meta_path(root, "ab"), Path::new("/c/onelf/meta/ab"));
}
}