use std::path::{Path, PathBuf};
use std::os::unix::fs::{DirBuilderExt, MetadataExt, PermissionsExt};
fn uid() -> u32 {
rustix::process::getuid().as_raw()
}
fn is_safe_owned_dir(path: &Path) -> bool {
let Ok(md) = std::fs::symlink_metadata(path) else {
return false;
};
md.is_dir() && md.uid() == uid() && (md.mode() & 0o077) == 0
}
pub(crate) fn ensure_safe_dir(dir: &Path) -> 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)
}
pub fn private_dir() -> Option<PathBuf> {
if let Some(x) = std::env::var_os("XDG_RUNTIME_DIR") {
let p = PathBuf::from(x);
if is_safe_owned_dir(&p) {
return Some(p);
}
}
let p = PathBuf::from(format!("/tmp/onelf-{}", uid()));
match std::fs::DirBuilder::new().mode(0o700).create(&p) {
Ok(()) => {}
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
Err(_) => return None,
}
is_safe_owned_dir(&p).then_some(p)
}
pub fn create_mountpoint(package_name: &str, package_id: &[u8; 32]) -> Option<PathBuf> {
let name_prefix: String = package_name.chars().take(6).collect();
let hash_suffix = crate::cache::hex(&package_id[0..4]);
let dir_name = format!("onelf-{name_prefix}-{hash_suffix}");
let path = private_dir()?.join(dir_name);
if let Err(e) = std::fs::create_dir_all(&path) {
eprintln!("onelf-rt: cannot create {}: {e}", path.display());
return None;
}
Some(path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn prefers_safe_xdg_runtime_dir() {
let dir = std::env::temp_dir().join(format!("onelf-priv-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir(&dir).unwrap();
std::fs::set_permissions(&dir, PermissionsExt::from_mode(0o700)).unwrap();
assert!(is_safe_owned_dir(&dir));
std::fs::set_permissions(&dir, PermissionsExt::from_mode(0o755)).unwrap();
assert!(!is_safe_owned_dir(&dir));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn rejects_non_directory_and_missing() {
assert!(!is_safe_owned_dir(std::path::Path::new(
"/nonexistent/onelf/private/xyz"
)));
}
}