use crate::files::OverlayFiles;
use crate::OverlayFS;
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
pub const WH_PREFIX: &str = ".wh.";
pub struct LayerManager {
pub backend: OverlayFiles,
}
impl LayerManager {
pub fn new(backend: OverlayFiles) -> Self {
Self { backend }
}
pub fn resolve(&self, rel: &Path) -> Option<(PathBuf, bool)> {
if self.is_hidden(rel) {
return None;
}
let upper = self.backend.upper.join(rel);
if fs::symlink_metadata(&upper).is_ok() {
return Some((upper, true));
}
let lower = self.backend.lower.join(rel);
if fs::symlink_metadata(&lower).is_ok() {
return Some((lower, false));
}
None
}
pub fn is_hidden(&self, rel: &Path) -> bool {
if let (Some(parent), Some(name)) = (rel.parent(), rel.file_name()) {
let wh = self.backend.upper.join(parent).join(format!(
"{}{}",
WH_PREFIX,
name.to_string_lossy()
));
return fs::symlink_metadata(&wh).is_ok();
}
false
}
pub fn create_whiteout(&self, rel: &Path) -> std::io::Result<()> {
if let (Some(parent), Some(name)) = (rel.parent(), rel.file_name()) {
let wh = self.backend.upper.join(parent).join(format!(
"{}{}",
WH_PREFIX,
name.to_string_lossy()
));
if let Some(p) = wh.parent() {
fs::create_dir_all(p)?;
}
fs::File::create(wh)?;
}
Ok(())
}
pub fn clear_whiteout(&self, rel: &Path) {
if let (Some(parent), Some(name)) = (rel.parent(), rel.file_name()) {
let wh = self.backend.upper.join(parent).join(format!(
"{}{}",
WH_PREFIX,
name.to_string_lossy()
));
let _ = fs::remove_file(wh);
}
}
pub fn copy_on_write(&self, rel: &Path) -> std::io::Result<PathBuf> {
let Some((path, is_upper)) = self.resolve(rel) else {
return Err(std::io::Error::from_raw_os_error(libc::ENOENT));
};
if is_upper {
return Ok(path);
}
let upper_path = self.backend.upper.join(rel);
if let Some(p) = upper_path.parent() {
fs::create_dir_all(p)?;
}
let meta = fs::symlink_metadata(&path)?;
if meta.file_type().is_symlink() {
let target = fs::read_link(&path)?;
std::os::unix::fs::symlink(target, &upper_path)?;
} else if meta.is_dir() {
fs::create_dir_all(&upper_path)?;
} else {
fs::copy(&path, &upper_path)?;
}
fs::set_permissions(&upper_path, meta.permissions())?;
let times = [
libc::timespec {
tv_sec: meta.atime() as _, tv_nsec: meta.atime_nsec() as libc::c_long,
},
libc::timespec {
tv_sec: meta.mtime() as _, tv_nsec: meta.mtime_nsec() as libc::c_long,
},
];
let path_c = std::ffi::CString::new(upper_path.as_os_str().as_bytes())?;
unsafe {
libc::utimensat(libc::AT_FDCWD, path_c.as_ptr(), times.as_ptr(), libc::AT_SYMLINK_NOFOLLOW);
}
let uid = meta.uid();
let gid = meta.gid();
use std::os::unix::ffi::OsStrExt;
let path_c = std::ffi::CString::new(upper_path.as_os_str().as_bytes())?;
unsafe {
libc::lchown(path_c.as_ptr(), uid, gid);
}
OverlayFS::copy_xattrs(&path, &upper_path)?;
Ok(upper_path)
}
}