Fix issue with symlinking already existing directories.

We will now link the contents in that case, instead of the whole dir.
This commit is contained in:
r-vdp 2023-03-30 15:01:34 +02:00
parent 336db40ad7
commit 360b0ce3c5
No known key found for this signature in database
2 changed files with 119 additions and 42 deletions

View file

@ -4,7 +4,6 @@ use anyhow::{anyhow, Result};
use im::HashMap;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::ffi::OsStr;
use std::fs::{DirBuilder, Permissions};
use std::os::unix::prelude::PermissionsExt;
use std::path;
@ -184,33 +183,121 @@ fn create_etc_static_link(
}
}
fn create_etc_link(
link_target: &OsStr,
fn create_etc_link<P>(
link_target: &P,
etc_dir: &Path,
state: EtcTree,
old_state: &EtcTree,
) -> (EtcTree, Result<()>) {
let link_path = etc_dir.join(link_target);
let (new_state, status) = create_dir_recursively(link_path.parent().unwrap(), state);
match status.and_then(|_| {
if *old_state.get_status(&link_path) == EtcFileStatus::Unmanaged
&& (link_path.exists() || link_path.is_symlink())
{
anyhow::bail!(
"Unmanaged file {} already exists, ignoring...",
link_path.display()
);
) -> (EtcTree, Result<()>)
where
P: AsRef<Path>,
{
fn link_dir_contents(
link_target: &Path,
absolute_target: &Path,
etc_dir: &Path,
state: EtcTree,
old_state: &EtcTree,
upwards_path: &Path,
) -> (EtcTree, Result<()>) {
let link_path = etc_dir.join(link_target);
if link_path.is_dir() && absolute_target.is_dir() {
log::info!("Entering into directory...");
(
absolute_target
.read_dir()
.expect("Error reading the directory.")
.fold(state, |state, entry| {
let (new_state, status) = go(
&link_target.join(
entry
.expect("Error reading the directory entry.")
.file_name(),
),
etc_dir,
state,
old_state,
&upwards_path.join(".."),
);
if let Err(e) = status {
log::error!(
"Error while trying to link directory {}: {:?}",
absolute_target.display(),
e
);
}
new_state
}),
// TODO better error handling
Ok(()),
)
} else {
(
state,
Err(anyhow!(
"Unmanaged file or directory {} already exists, ignoring...",
link_path.display()
)),
)
}
create_link(
&Path::new(".")
.join(SYSTEM_MANAGER_STATIC_NAME)
.join(link_target),
&link_path,
)
}) {
Ok(_) => (new_state.register_managed_entry(&link_path), Ok(())),
e => (new_state, e),
}
fn go(
link_target: &Path,
etc_dir: &Path,
state: EtcTree,
old_state: &EtcTree,
upwards_path: &Path,
) -> (EtcTree, Result<()>) {
let link_path = etc_dir.join(link_target);
match create_dir_recursively(link_path.parent().unwrap(), state) {
(dir_state, Ok(_)) => {
let target = upwards_path
.join(SYSTEM_MANAGER_STATIC_NAME)
.join(link_target);
let absolute_target = etc_dir.join(SYSTEM_MANAGER_STATIC_NAME).join(link_target);
if link_path.exists() && !old_state.is_managed(&link_path) {
link_dir_contents(
link_target,
&absolute_target,
etc_dir,
dir_state,
old_state,
upwards_path,
)
} else if link_path.is_symlink()
&& link_path.read_link().expect("Error reading link.") == target
{
(dir_state.register_managed_entry(&link_path), Ok(()))
} else {
let result = if link_path.exists() {
assert!(old_state.is_managed(&link_path));
fs::remove_file(&link_path).map_err(anyhow::Error::from)
} else {
Ok(())
};
match result.and_then(|_| create_link(&target, &link_path)) {
Ok(_) => (dir_state.register_managed_entry(&link_path), Ok(())),
Err(e) => (
dir_state,
Err(anyhow!(e)
.context(format!("Error creating link: {}", link_path.display()))),
),
}
}
}
(new_state, e) => (new_state, e),
}
}
go(
link_target.as_ref(),
etc_dir,
state,
old_state,
Path::new("."),
)
}
fn create_etc_entry(
@ -221,7 +308,7 @@ fn create_etc_entry(
) -> (EtcTree, Result<()>) {
if entry.mode == "symlink" {
if let Some(path::Component::Normal(link_target)) = entry.target.components().next() {
create_etc_link(link_target, etc_dir, state, old_state)
create_etc_link(&link_target, etc_dir, state, old_state)
} else {
(
state,
@ -287,12 +374,7 @@ fn create_dir_recursively(dir: &Path, state: EtcTree) -> (EtcTree, Result<()>) {
fn copy_file(source: &Path, target: &Path, mode: &str, old_state: &EtcTree) -> Result<()> {
let exists = target.try_exists()?;
let old_status = old_state.get_status(target);
log::debug!(
"Status for target {} before copy: {old_status:?}",
target.display()
);
if !exists || *old_status == EtcFileStatus::Managed {
if !exists || old_state.is_managed(target) {
log::debug!(
"Copying file {} to {}...",
source.display(),

View file

@ -97,6 +97,10 @@ impl EtcTree {
go(self, path.components(), path)
}
pub fn is_managed(&self, path: &Path) -> bool {
*self.get_status(path) == EtcFileStatus::Managed
}
// TODO is recursion OK here?
// Should we convert to CPS and use a crate like tramp to TCO this?
pub fn register_managed_entry(self, path: &Path) -> Self {
@ -333,18 +337,9 @@ mod tests {
.register_managed_entry(&PathBuf::from("/").join("foo5").join("baz2"))
.register_managed_entry(&PathBuf::from("/").join("foo5").join("baz").join("bar"));
assert_eq!(
tree1.get_status(&PathBuf::from("/").join("foo5").join("baz").join("bar")),
&EtcFileStatus::Managed
);
assert_eq!(
tree1.get_status(&PathBuf::from("/").join("foo")),
&EtcFileStatus::Unmanaged
);
assert_eq!(
tree1.get_status(&PathBuf::from("/").join("foo").join("nonexistent")),
&EtcFileStatus::Unmanaged
);
assert!(tree1.is_managed(&PathBuf::from("/").join("foo5").join("baz").join("bar")));
assert!(!tree1.is_managed(&PathBuf::from("/").join("foo")));
assert!(!tree1.is_managed(&PathBuf::from("/").join("foo").join("nonexistent")));
}
#[test]