use super::fs_beneath::{ObjectId, handle_path, identity_at, identity_of, open_directory_at, remove_at};
use super::subvolume_error::MigrateError;
use super::subvolume_platform::Platform;
use super::subvolume_remove::remove_tree;
use super::worktree_convert_compare::first_difference;
use super::worktree_convert_journal::is_recorded_object;
use std::ffi::OsStr;
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Removal {
Removed,
Absent,
Kept(String),
Differs(String),
}
pub fn remove_recorded_old_tree(platform: &dyn Platform, parent: &std::fs::File, name: &OsStr, old: ObjectId, installed: &Path) -> Result<Removal, MigrateError> {
return remove_recorded_old_tree_seamed(platform, parent, name, old, installed, &mut || {});
}
pub fn remove_recorded_old_tree_seamed(platform: &dyn Platform, parent: &std::fs::File, name: &OsStr, old: ObjectId, installed: &Path, after_pinned: &mut dyn FnMut()) -> Result<Removal, MigrateError> {
let shown: PathBuf = PathBuf::from(name);
let pinned: std::fs::File = match open_directory_at(parent, name) {
Ok(opened) => opened,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Removal::Absent),
Err(error) => return Ok(Removal::Kept(format!("{} cannot be opened as a directory below its parent ({error}), so it is not the old tree this conversion recorded", shown.display()))),
};
let found = identity_of(&pinned).map_err(|error: std::io::Error| { return MigrateError::io("examine", shown.as_path(), &error); })?;
if !is_recorded_object(&old, &found) {
return Ok(Removal::Kept(format!("{} is not the old tree this conversion recorded", shown.display())));
}
let through: PathBuf = handle_path(&pinned);
if platform.is_subvolume_root(through.as_path())? {
return Ok(Removal::Kept(format!("{} is a subvolume, so it is not the old tree this conversion recorded", shown.display())));
}
match first_difference(through.as_path(), installed) {
Ok(None) => {}
Ok(Some(reason)) => return Ok(Removal::Differs(reason)),
Err(error) => return Ok(Removal::Differs(format!("the old tree cannot be compared with the installed worktree ({error})"))),
}
let metadata: std::fs::Metadata = pinned.metadata().map_err(|error: std::io::Error| { return MigrateError::io("examine", shown.as_path(), &error); })?;
if std::os::unix::fs::MetadataExt::mode(&metadata) & 0o700 != 0o700 {
pinned.set_permissions(std::fs::Permissions::from_mode(std::os::unix::fs::MetadataExt::mode(&metadata) | 0o700)).map_err(|error: std::io::Error| { return MigrateError::io("make writable", shown.as_path(), &error); })?;
}
after_pinned();
let listing = std::fs::read_dir(&through).map_err(|error: std::io::Error| { return MigrateError::io("list the old tree", shown.as_path(), &error); })?;
for item in listing {
let entry: std::fs::DirEntry = item.map_err(|error: std::io::Error| { return MigrateError::io("list the old tree", shown.as_path(), &error); })?;
remove_tree(entry.path().as_path()).map_err(|error: std::io::Error| { return MigrateError::io("remove the old tree", entry.path().as_path(), &error); })?;
}
match identity_at(parent, name) {
Ok(still) if is_recorded_object(&old, &still) => {}
Ok(_) => return Ok(Removal::Kept(format!("{} no longer names the old tree, which is empty and was left where it is", shown.display()))),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Removal::Removed),
Err(error) => return Err(MigrateError::io("examine", shown.as_path(), &error)),
}
remove_at(parent, name, true).map_err(|error: std::io::Error| { return MigrateError::io("remove the old tree", shown.as_path(), &error); })?;
return Ok(Removal::Removed);
}