use super::CowBranch;
use crate::error::BranchError;
use std::fs;
use std::path::{Path, PathBuf};
use uuid::Uuid;
use walkdir::WalkDir;
pub(crate) struct OverlayBranch {
base_dir: PathBuf,
storage: PathBuf,
upper: PathBuf,
merged: PathBuf,
}
impl OverlayBranch {
pub fn create(base: &Path, storage: &Path) -> Result<Self, BranchError> {
let id = Uuid::new_v4().to_string();
let branch_dir = storage.join(&id);
let upper = branch_dir.join("upper");
let work = branch_dir.join("work");
let merged = branch_dir.join("merged");
fs::create_dir_all(&upper).map_err(|e| BranchError::Operation(format!("create upper: {}", e)))?;
fs::create_dir_all(&work).map_err(|e| BranchError::Operation(format!("create work: {}", e)))?;
fs::create_dir_all(&merged).map_err(|e| BranchError::Operation(format!("create merged: {}", e)))?;
Ok(Self {
base_dir: base.to_path_buf(),
storage: branch_dir,
upper,
merged,
})
}
}
impl CowBranch for OverlayBranch {
fn child_mount_config(&self) -> Option<super::ChildMountConfig> {
Some(super::ChildMountConfig {
mount_point: self.base_dir.clone(),
upper: self.upper.clone(),
work: self.storage.join("work"),
lowers: vec![self.base_dir.clone()],
})
}
fn commit(&self) -> Result<(), BranchError> {
for entry in WalkDir::new(&self.upper).min_depth(1) {
let entry = entry.map_err(|e| BranchError::Operation(format!("walk: {}", e)))?;
let rel = entry.path().strip_prefix(&self.upper).unwrap();
let target = self.base_dir.join(rel);
if is_whiteout(entry.path())? {
let _ = fs::remove_file(&target);
let _ = fs::remove_dir_all(&target);
} else if entry.file_type().is_dir() {
fs::create_dir_all(&target)
.map_err(|e| BranchError::Operation(format!("mkdir {}: {}", target.display(), e)))?;
} else {
if let Some(parent) = target.parent() {
let _ = fs::create_dir_all(parent);
}
fs::copy(entry.path(), &target)
.map_err(|e| BranchError::Operation(format!("copy to {}: {}", target.display(), e)))?;
}
}
self.cleanup()
}
fn abort(&self) -> Result<(), BranchError> {
self.cleanup()
}
fn cleanup(&self) -> Result<(), BranchError> {
let merged_cstr = std::ffi::CString::new(self.merged.to_str().unwrap_or("")).unwrap();
unsafe { libc::umount2(merged_cstr.as_ptr(), libc::MNT_DETACH); }
let _ = fs::remove_dir_all(&self.storage);
Ok(())
}
fn changes(&self) -> Result<Vec<crate::dry_run::Change>, BranchError> {
use crate::dry_run::{Change, ChangeKind};
let mut result = Vec::new();
for entry in WalkDir::new(&self.upper).min_depth(1) {
let entry = entry.map_err(|e| BranchError::Operation(format!("walk: {}", e)))?;
let rel = entry.path().strip_prefix(&self.upper).unwrap();
let target = self.base_dir.join(rel);
if is_whiteout(entry.path())? {
result.push(Change {
kind: ChangeKind::Deleted,
path: rel.to_path_buf(),
});
} else if !entry.file_type().is_dir() {
let kind = if target.exists() {
ChangeKind::Modified
} else {
ChangeKind::Added
};
result.push(Change { kind, path: rel.to_path_buf() });
}
}
Ok(result)
}
}
fn is_whiteout(path: &Path) -> Result<bool, BranchError> {
use std::os::unix::fs::{FileTypeExt, MetadataExt};
let meta = match fs::symlink_metadata(path) {
Ok(m) => m,
Err(_) => return Ok(false),
};
Ok(meta.file_type().is_char_device() && meta.rdev() == 0)
}