use crate::FsError;
use std::io;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
const PRIVATE_DIR_TRUST: std::time::Duration = std::time::Duration::from_secs(300);
type DirIdentity = [u64; 3];
fn private_dir_identity(meta: &std::fs::Metadata) -> Option<DirIdentity> {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
Some([meta.dev(), meta.ino(), u64::from(meta.uid())])
}
#[cfg(windows)]
{
use std::os::windows::fs::MetadataExt;
let created = meta.created().ok()?;
let nanos = created
.duration_since(std::time::UNIX_EPOCH)
.ok()?
.as_nanos() as u64;
Some([nanos, u64::from(meta.file_attributes()), 0])
}
#[cfg(not(any(unix, windows)))]
{
let _ = meta;
None
}
}
type PrivateDirCache =
std::sync::Mutex<std::collections::HashMap<PathBuf, (std::time::Instant, DirIdentity)>>;
fn private_dir_cache() -> &'static PrivateDirCache {
static CACHE: std::sync::OnceLock<PrivateDirCache> = std::sync::OnceLock::new();
CACHE.get_or_init(Default::default)
}
pub fn private_dir_verified(dir: &Path) -> bool {
let Some((at, identity)) = private_dir_cache()
.lock()
.ok()
.and_then(|cache| cache.get(dir).copied())
else {
return false;
};
if at.elapsed() >= PRIVATE_DIR_TRUST {
return false;
}
std::fs::symlink_metadata(dir)
.ok()
.filter(|meta| meta.is_dir() && !meta.file_type().is_symlink())
.and_then(|meta| private_dir_identity(&meta))
.is_some_and(|now| now == identity)
}
pub fn note_private_dir(dir: &Path) {
let Some(identity) = std::fs::symlink_metadata(dir)
.ok()
.and_then(|meta| private_dir_identity(&meta))
else {
return;
};
if let Ok(mut cache) = private_dir_cache().lock() {
if cache.len() > 512 {
cache.clear();
}
cache.insert(dir.to_path_buf(), (std::time::Instant::now(), identity));
}
}
pub fn forget_private_dir(dir: &Path) {
if let Ok(mut cache) = private_dir_cache().lock() {
cache.remove(dir);
}
}
pub fn ensure_dir(dir: &Path) -> io::Result<()> {
if private_dir_verified(dir) {
return Ok(());
}
match crate::private::ensure_private_dir(dir) {
Ok(()) => {
note_private_dir(dir);
Ok(())
}
Err(FsError::NotOwned(_)) if dir.is_dir() => Ok(()),
Err(other) => Err(other.into()),
}
}
pub fn ensure_private_dir_repairing(dir: &Path) -> io::Result<Option<PathBuf>> {
if private_dir_verified(dir) {
return Ok(None);
}
match crate::private::ensure_private_dir(dir) {
Ok(()) => {
note_private_dir(dir);
Ok(None)
}
Err(FsError::NotOwned(_)) if cfg!(windows) => {
let Some(name) = dir.file_name() else {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
format!("{} is not owned by the current user", dir.display()),
));
};
let stamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let mut aside_name = name.to_os_string();
aside_name.push(format!(".admin-owned-{stamp}"));
let aside = dir.with_file_name(aside_name);
std::fs::rename(dir, &aside).map_err(|error| {
io::Error::new(
error.kind(),
format!(
"{} is not owned by the current user (made by an elevated run?) and could not be moved aside: {error}",
dir.display()
),
)
})?;
crate::private::ensure_private_dir(dir)?;
Ok(Some(aside))
}
Err(other) => Err(other.into()),
}
}