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:
parent
336db40ad7
commit
360b0ce3c5
2 changed files with 119 additions and 42 deletions
|
|
@ -4,7 +4,6 @@ use anyhow::{anyhow, Result};
|
||||||
use im::HashMap;
|
use im::HashMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ffi::OsStr;
|
|
||||||
use std::fs::{DirBuilder, Permissions};
|
use std::fs::{DirBuilder, Permissions};
|
||||||
use std::os::unix::prelude::PermissionsExt;
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
use std::path;
|
use std::path;
|
||||||
|
|
@ -184,33 +183,121 @@ fn create_etc_static_link(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_etc_link(
|
fn create_etc_link<P>(
|
||||||
link_target: &OsStr,
|
link_target: &P,
|
||||||
etc_dir: &Path,
|
etc_dir: &Path,
|
||||||
state: EtcTree,
|
state: EtcTree,
|
||||||
old_state: &EtcTree,
|
old_state: &EtcTree,
|
||||||
) -> (EtcTree, Result<()>) {
|
) -> (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);
|
let link_path = etc_dir.join(link_target);
|
||||||
let (new_state, status) = create_dir_recursively(link_path.parent().unwrap(), state);
|
if link_path.is_dir() && absolute_target.is_dir() {
|
||||||
match status.and_then(|_| {
|
log::info!("Entering into directory...");
|
||||||
if *old_state.get_status(&link_path) == EtcFileStatus::Unmanaged
|
(
|
||||||
&& (link_path.exists() || link_path.is_symlink())
|
absolute_target
|
||||||
{
|
.read_dir()
|
||||||
anyhow::bail!(
|
.expect("Error reading the directory.")
|
||||||
"Unmanaged file {} already exists, ignoring...",
|
.fold(state, |state, entry| {
|
||||||
link_path.display()
|
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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
create_link(
|
new_state
|
||||||
&Path::new(".")
|
}),
|
||||||
.join(SYSTEM_MANAGER_STATIC_NAME)
|
// TODO better error handling
|
||||||
.join(link_target),
|
Ok(()),
|
||||||
&link_path,
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
state,
|
||||||
|
Err(anyhow!(
|
||||||
|
"Unmanaged file or directory {} already exists, ignoring...",
|
||||||
|
link_path.display()
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
}) {
|
|
||||||
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(
|
fn create_etc_entry(
|
||||||
|
|
@ -221,7 +308,7 @@ fn create_etc_entry(
|
||||||
) -> (EtcTree, Result<()>) {
|
) -> (EtcTree, Result<()>) {
|
||||||
if entry.mode == "symlink" {
|
if entry.mode == "symlink" {
|
||||||
if let Some(path::Component::Normal(link_target)) = entry.target.components().next() {
|
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 {
|
} else {
|
||||||
(
|
(
|
||||||
state,
|
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<()> {
|
fn copy_file(source: &Path, target: &Path, mode: &str, old_state: &EtcTree) -> Result<()> {
|
||||||
let exists = target.try_exists()?;
|
let exists = target.try_exists()?;
|
||||||
let old_status = old_state.get_status(target);
|
if !exists || old_state.is_managed(target) {
|
||||||
log::debug!(
|
|
||||||
"Status for target {} before copy: {old_status:?}",
|
|
||||||
target.display()
|
|
||||||
);
|
|
||||||
if !exists || *old_status == EtcFileStatus::Managed {
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Copying file {} to {}...",
|
"Copying file {} to {}...",
|
||||||
source.display(),
|
source.display(),
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,10 @@ impl EtcTree {
|
||||||
go(self, path.components(), path)
|
go(self, path.components(), path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_managed(&self, path: &Path) -> bool {
|
||||||
|
*self.get_status(path) == EtcFileStatus::Managed
|
||||||
|
}
|
||||||
|
|
||||||
// TODO is recursion OK here?
|
// TODO is recursion OK here?
|
||||||
// Should we convert to CPS and use a crate like tramp to TCO this?
|
// Should we convert to CPS and use a crate like tramp to TCO this?
|
||||||
pub fn register_managed_entry(self, path: &Path) -> Self {
|
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("baz2"))
|
||||||
.register_managed_entry(&PathBuf::from("/").join("foo5").join("baz").join("bar"));
|
.register_managed_entry(&PathBuf::from("/").join("foo5").join("baz").join("bar"));
|
||||||
|
|
||||||
assert_eq!(
|
assert!(tree1.is_managed(&PathBuf::from("/").join("foo5").join("baz").join("bar")));
|
||||||
tree1.get_status(&PathBuf::from("/").join("foo5").join("baz").join("bar")),
|
assert!(!tree1.is_managed(&PathBuf::from("/").join("foo")));
|
||||||
&EtcFileStatus::Managed
|
assert!(!tree1.is_managed(&PathBuf::from("/").join("foo").join("nonexistent")));
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
tree1.get_status(&PathBuf::from("/").join("foo")),
|
|
||||||
&EtcFileStatus::Unmanaged
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
tree1.get_status(&PathBuf::from("/").join("foo").join("nonexistent")),
|
|
||||||
&EtcFileStatus::Unmanaged
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue