WIP, etc tree implementation.
This commit is contained in:
parent
055d11cbdf
commit
c7a66c76ed
1 changed files with 378 additions and 158 deletions
|
|
@ -1,8 +1,11 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use im::HashMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::cmp::Eq;
|
||||||
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs::{DirBuilder, Permissions};
|
use std::fs::{DirBuilder, Permissions};
|
||||||
|
use std::iter::Peekable;
|
||||||
use std::os::unix::prelude::PermissionsExt;
|
use std::os::unix::prelude::PermissionsExt;
|
||||||
use std::path;
|
use std::path;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
@ -13,7 +16,7 @@ use crate::{
|
||||||
ETC_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR,
|
ETC_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct EtcFile {
|
struct EtcFile {
|
||||||
source: StorePath,
|
source: StorePath,
|
||||||
|
|
@ -36,15 +39,16 @@ struct EtcFilesConfig {
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct EtcStateInfo {
|
struct EtcTree {
|
||||||
status: EtcFileStatus,
|
status: EtcFileStatus,
|
||||||
|
path: PathBuf,
|
||||||
// TODO directories and files are now both represented as a string associated with a nested
|
// TODO directories and files are now both represented as a string associated with a nested
|
||||||
// map. For files the nested map is simple empty.
|
// map. For files the nested map is simple empty.
|
||||||
// We could potentially optimise this.
|
// We could potentially optimise this.
|
||||||
nested: HashMap<String, EtcStateInfo>,
|
nested: HashMap<OsString, EtcTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
enum EtcFileStatus {
|
enum EtcFileStatus {
|
||||||
Managed,
|
Managed,
|
||||||
|
|
@ -52,6 +56,8 @@ enum EtcFileStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EtcFileStatus {
|
impl EtcFileStatus {
|
||||||
|
// TODO
|
||||||
|
#[allow(dead_code)]
|
||||||
fn merge(&self, other: &Self) -> Self {
|
fn merge(&self, other: &Self) -> Self {
|
||||||
use EtcFileStatus::*;
|
use EtcFileStatus::*;
|
||||||
|
|
||||||
|
|
@ -62,34 +68,181 @@ impl EtcFileStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EtcStateInfo {
|
/// Data structure to represent files that are managed by system-manager.
|
||||||
fn new() -> Self {
|
///
|
||||||
Self::with_status(EtcFileStatus::Unmanaged)
|
/// This data will be serialised to disk and read on the next run.
|
||||||
|
///
|
||||||
|
/// We need these basic operations:
|
||||||
|
/// 1. Create a new, empty structure
|
||||||
|
/// 2. Persist to a file
|
||||||
|
/// 3. Import from a file
|
||||||
|
/// 4. Add a path to the tree, that will from then on be considered as managed
|
||||||
|
/// 5.
|
||||||
|
impl EtcTree {
|
||||||
|
fn new(path: PathBuf) -> Self {
|
||||||
|
Self::with_status(path, EtcFileStatus::Unmanaged)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_status(status: EtcFileStatus) -> Self {
|
fn with_status(path: PathBuf, status: EtcFileStatus) -> Self {
|
||||||
Self {
|
Self {
|
||||||
status,
|
status,
|
||||||
|
path,
|
||||||
nested: HashMap::new(),
|
nested: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO unit tests
|
// TODO is recursion OK here?
|
||||||
fn register_managed_path(mut self, components: &[String], path: String) -> Self {
|
// Should we convert to CPS and use a crate like tramp to TCO this?
|
||||||
let state = components.iter().fold(&mut self, |state, component| {
|
fn register_managed_entry(self, path: &Path) -> Self {
|
||||||
if !state.nested.contains_key(component) {
|
fn go<'a, C>(mut tree: EtcTree, mut components: Peekable<C>, path: PathBuf) -> EtcTree
|
||||||
let new_state = Self::new();
|
where
|
||||||
state.nested.insert(component.to_owned(), new_state);
|
C: Iterator<Item = path::Component<'a>>,
|
||||||
|
{
|
||||||
|
if let Some(component) = components.next() {
|
||||||
|
match component {
|
||||||
|
path::Component::Normal(name) => {
|
||||||
|
let new_path = path.join(component);
|
||||||
|
tree.nested = tree.nested.alter(
|
||||||
|
|maybe_subtree| {
|
||||||
|
Some(go(
|
||||||
|
maybe_subtree.unwrap_or_else(|| {
|
||||||
|
EtcTree::with_status(
|
||||||
|
new_path.to_owned(),
|
||||||
|
if components.peek().is_some() {
|
||||||
|
EtcFileStatus::Unmanaged
|
||||||
|
} else {
|
||||||
|
EtcFileStatus::Managed
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
components,
|
||||||
|
new_path,
|
||||||
|
))
|
||||||
|
},
|
||||||
|
name.to_owned(),
|
||||||
|
);
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
path::Component::RootDir => go(
|
||||||
|
tree,
|
||||||
|
components,
|
||||||
|
path.join(path::MAIN_SEPARATOR.to_string()),
|
||||||
|
),
|
||||||
|
_ => panic!(
|
||||||
|
"Unsupported path provided! At path component: {:?}",
|
||||||
|
component
|
||||||
|
),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tree
|
||||||
}
|
}
|
||||||
state.nested.get_mut(component).unwrap()
|
}
|
||||||
|
|
||||||
|
go(self, path.components().peekable(), PathBuf::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deactivate<F>(self, delete_action: &F) -> Option<EtcTree>
|
||||||
|
where
|
||||||
|
F: Fn(&Path) -> bool,
|
||||||
|
{
|
||||||
|
let new_tree = self.nested.keys().fold(self.clone(), |mut new_tree, name| {
|
||||||
|
new_tree.nested = new_tree.nested.alter(
|
||||||
|
|subtree| subtree.and_then(|subtree| subtree.deactivate(delete_action)),
|
||||||
|
name.to_owned(),
|
||||||
|
);
|
||||||
|
new_tree
|
||||||
});
|
});
|
||||||
|
|
||||||
state
|
if let EtcFileStatus::Managed = new_tree.status {
|
||||||
.nested
|
if new_tree.nested.is_empty() && delete_action(&new_tree.path) {
|
||||||
.insert(path, Self::with_status(EtcFileStatus::Managed));
|
None
|
||||||
|
} else {
|
||||||
self
|
Some(new_tree)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(new_tree)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn deactivate_managed_entry<F>(self, path: &Path, delete_action: &F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&Path) -> bool,
|
||||||
|
{
|
||||||
|
fn go<'a, C, F>(
|
||||||
|
mut tree: EtcTree,
|
||||||
|
path: PathBuf,
|
||||||
|
mut components: Peekable<C>,
|
||||||
|
delete_action: &F,
|
||||||
|
) -> EtcTree
|
||||||
|
where
|
||||||
|
C: Iterator<Item = path::Component<'a>>,
|
||||||
|
F: Fn(&Path) -> bool,
|
||||||
|
{
|
||||||
|
log::debug!("Deactivating {}", path.display());
|
||||||
|
|
||||||
|
if let Some(component) = components.next() {
|
||||||
|
match component {
|
||||||
|
path::Component::Normal(name) => {
|
||||||
|
let new_path = path.join(name);
|
||||||
|
tree.nested = tree.nested.alter(
|
||||||
|
|maybe_subtree| {
|
||||||
|
maybe_subtree.and_then(|subtree| {
|
||||||
|
if components.peek().is_some() {
|
||||||
|
Some(go(subtree, new_path, components, delete_action))
|
||||||
|
} else {
|
||||||
|
subtree.deactivate(delete_action)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
name.to_owned(),
|
||||||
|
);
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
path::Component::RootDir => go(
|
||||||
|
tree,
|
||||||
|
path.join(path::MAIN_SEPARATOR.to_string()),
|
||||||
|
components,
|
||||||
|
delete_action,
|
||||||
|
),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tree
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go(
|
||||||
|
self,
|
||||||
|
PathBuf::new(),
|
||||||
|
path.components().peekable(),
|
||||||
|
delete_action,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Remove from this tree all subtrees also contained in the given tree
|
||||||
|
// fn diff<F>(&mut self, old: &mut EtcTree, deactivate_action: &F)
|
||||||
|
// where
|
||||||
|
// F: Fn(&str, &mut EtcTree) -> bool,
|
||||||
|
// {
|
||||||
|
// if self.status != old.status {
|
||||||
|
// self.status = self.status.merge(&old.status);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let keys = old.nested.keys().collect::<HashSet<_>>();
|
||||||
|
// keys.iter().for_each(|key| {
|
||||||
|
// if let Some(subtree) = self.nested.get_mut::<str>(key.as_ref()) {
|
||||||
|
// subtree.diff(
|
||||||
|
// old.nested.get_mut::<str>(key.as_ref()).unwrap(),
|
||||||
|
// deactivate_action,
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// let deleted =
|
||||||
|
// deactivate_action(key, old.nested.get_mut::<str>(key.as_ref()).unwrap());
|
||||||
|
// if !deleted {
|
||||||
|
// // TODO what can we do here??
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -110,78 +263,72 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
||||||
|
|
||||||
DirBuilder::new().recursive(true).create(&etc_dir)?;
|
DirBuilder::new().recursive(true).create(&etc_dir)?;
|
||||||
|
|
||||||
// TODO: constant?
|
|
||||||
let static_dir_name = ".system-manager-static";
|
|
||||||
let static_path = etc_dir.join(static_dir_name);
|
|
||||||
create_store_link(&config.static_env, static_path.as_path())?;
|
|
||||||
|
|
||||||
// TODO remove all paths that are in the state file from the previous generation
|
// TODO remove all paths that are in the state file from the previous generation
|
||||||
// and not in the current one.
|
// and not in the current one.
|
||||||
|
let old_state = read_created_files()?;
|
||||||
|
|
||||||
let state = read_created_files()?;
|
// TODO: constant?
|
||||||
|
let static_dir_name = ".system-manager-static";
|
||||||
|
let (state, status) =
|
||||||
|
create_etc_static_link(static_dir_name, &config.static_env, &etc_dir, old_state);
|
||||||
|
status?;
|
||||||
let new_state = create_etc_links(config.entries.values(), &etc_dir, state);
|
let new_state = create_etc_links(config.entries.values(), &etc_dir, state);
|
||||||
serialise_created_files(&new_state.register_managed_path(&[], static_dir_name.to_owned()))?;
|
|
||||||
|
|
||||||
Ok(())
|
// new_state.diff(&mut old_state, &|name, subtree| {
|
||||||
}
|
// log::debug!("Deactivating: {name}");
|
||||||
|
// false
|
||||||
|
// });
|
||||||
|
//log::debug!("{old_state}");
|
||||||
|
|
||||||
pub fn deactivate() -> Result<()> {
|
serialise_state(&new_state)?;
|
||||||
let old_created_files = read_created_files()?;
|
|
||||||
log::debug!("{:?}", old_created_files);
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
//old_created_files
|
|
||||||
// .iter()
|
|
||||||
// .try_for_each(|created_file| remove_created_file(&created_file.path, "etc"))?;
|
|
||||||
|
|
||||||
serialise_created_files(&EtcStateInfo::new())?;
|
|
||||||
|
|
||||||
log::info!("Done");
|
log::info!("Done");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_created_file<P>(created_file: &P, root_dir: &str) -> Result<()>
|
pub fn deactivate() -> Result<()> {
|
||||||
where
|
let state = read_created_files()?;
|
||||||
P: AsRef<Path>,
|
log::debug!("{:?}", state);
|
||||||
{
|
|
||||||
let path = created_file.as_ref();
|
|
||||||
let recurse = if path.is_file() {
|
|
||||||
remove_file(path)?;
|
|
||||||
true
|
|
||||||
} else if path.is_symlink() {
|
|
||||||
remove_link(path)?;
|
|
||||||
true
|
|
||||||
} else if path.is_dir() && fs::read_dir(created_file)?.next().is_none() {
|
|
||||||
log::info!("We will remove the following directory: {}", path.display());
|
|
||||||
remove_dir(path)?;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
log::debug!("Stopped at directory {}.", path.display());
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
if recurse {
|
serialise_state(&state.deactivate_managed_entry(
|
||||||
if let Some(parent) = path.parent() {
|
Path::new("/"),
|
||||||
if parent
|
&|path| match try_delete_path(path) {
|
||||||
.file_name()
|
Ok(()) => true,
|
||||||
.filter(|name| &root_dir != name)
|
Err(e) => {
|
||||||
.is_some()
|
log::error!("Error deleting path: {}", path.display());
|
||||||
{
|
log::error!("{e}");
|
||||||
log::debug!("Recursing up into directory {}...", parent.display());
|
false
|
||||||
return remove_created_file(&parent, root_dir);
|
|
||||||
}
|
}
|
||||||
log::debug!("Reached root dir: {}", parent.display());
|
},
|
||||||
}
|
))?;
|
||||||
}
|
|
||||||
|
log::info!("Done");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_etc_links<'a, E>(entries: E, etc_dir: &Path, state: EtcStateInfo) -> EtcStateInfo
|
fn try_delete_path(path: &Path) -> Result<()> {
|
||||||
|
// exists() returns false for broken symlinks
|
||||||
|
if path.exists() || path.is_symlink() {
|
||||||
|
if path.is_symlink() {
|
||||||
|
remove_link(path)
|
||||||
|
} else if path.is_file() {
|
||||||
|
remove_file(path)
|
||||||
|
} else if path.is_dir() {
|
||||||
|
remove_dir(path)
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("Unsupported file type! {}", path.display()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_etc_links<'a, E>(entries: E, etc_dir: &Path, state: EtcTree) -> EtcTree
|
||||||
where
|
where
|
||||||
E: Iterator<Item = &'a EtcFile>,
|
E: Iterator<Item = &'a EtcFile>,
|
||||||
{
|
{
|
||||||
entries.fold(state, |state, entry| {
|
entries.fold(state, |state, entry| {
|
||||||
let (new_state, status) = create_etc_link(entry, etc_dir, state);
|
let (new_state, status) = create_etc_entry(entry, etc_dir, state);
|
||||||
match status {
|
match status {
|
||||||
Ok(_) => new_state,
|
Ok(_) => new_state,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
|
@ -192,30 +339,44 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_etc_link(
|
fn create_etc_static_link(
|
||||||
entry: &EtcFile,
|
static_dir_name: &str,
|
||||||
|
store_path: &StorePath,
|
||||||
etc_dir: &Path,
|
etc_dir: &Path,
|
||||||
state: EtcStateInfo,
|
state: EtcTree,
|
||||||
) -> (EtcStateInfo, Result<()>) {
|
) -> (EtcTree, Result<()>) {
|
||||||
|
let static_path = etc_dir.join(static_dir_name);
|
||||||
|
let (new_state, status) = create_dir_recursively(static_path.parent().unwrap(), state);
|
||||||
|
match status.and_then(|_| create_store_link(store_path, static_path.as_path())) {
|
||||||
|
Ok(_) => (new_state.register_managed_entry(&static_path), Ok(())),
|
||||||
|
e => (new_state, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_etc_link(link_target: &OsStr, etc_dir: &Path, 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(|_| {
|
||||||
|
create_link(
|
||||||
|
Path::new(".")
|
||||||
|
.join(".system-manager-static")
|
||||||
|
.join("etc")
|
||||||
|
.join(link_target)
|
||||||
|
.as_path(),
|
||||||
|
link_path.as_path(),
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Ok(_) => (new_state.register_managed_entry(&link_path), Ok(())),
|
||||||
|
e => (new_state, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO split up this function, and treat symlinks and copied files the same in the state file (ie
|
||||||
|
// include the root for both).
|
||||||
|
fn create_etc_entry(entry: &EtcFile, etc_dir: &Path, state: EtcTree) -> (EtcTree, Result<()>) {
|
||||||
if entry.mode == "symlink" {
|
if entry.mode == "symlink" {
|
||||||
if let Some(path::Component::Normal(link_target)) =
|
if let Some(path::Component::Normal(link_target)) = entry.target.components().next() {
|
||||||
entry.target.components().into_iter().next()
|
create_etc_link(link_target, etc_dir, state)
|
||||||
{
|
|
||||||
let link_name = etc_dir.join(link_target);
|
|
||||||
match create_link(
|
|
||||||
Path::new(".")
|
|
||||||
.join(".system-manager-static")
|
|
||||||
.join("etc")
|
|
||||||
.join(link_target)
|
|
||||||
.as_path(),
|
|
||||||
link_name.as_path(),
|
|
||||||
) {
|
|
||||||
Ok(_) => (
|
|
||||||
state.register_managed_path(&[], link_target.to_string_lossy().into_owned()),
|
|
||||||
Ok(()),
|
|
||||||
),
|
|
||||||
e => (state, e),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
state,
|
state,
|
||||||
|
|
@ -223,56 +384,8 @@ fn create_etc_link(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let dirbuilder = DirBuilder::new();
|
|
||||||
let target_path = etc_dir.join(entry.target.as_path());
|
let target_path = etc_dir.join(entry.target.as_path());
|
||||||
|
let (new_state, status) = create_dir_recursively(target_path.parent().unwrap(), state);
|
||||||
// Create all parent dirs that do not exist yet
|
|
||||||
let (new_state, created_paths, _, status) = target_path
|
|
||||||
.parent()
|
|
||||||
.unwrap() // TODO
|
|
||||||
.components()
|
|
||||||
.fold_while(
|
|
||||||
(state, Vec::new(), PathBuf::from("/"), Ok(())),
|
|
||||||
|(state, mut created, path, _), component| {
|
|
||||||
use itertools::FoldWhile::{Continue, Done};
|
|
||||||
use path::Component;
|
|
||||||
|
|
||||||
match component {
|
|
||||||
Component::RootDir => Continue((state, created, path, Ok(()))),
|
|
||||||
Component::Normal(dir) => {
|
|
||||||
let new_path = path.join(dir);
|
|
||||||
if !new_path.exists() {
|
|
||||||
log::debug!("Creating path: {}", new_path.display());
|
|
||||||
match dirbuilder.create(new_path.as_path()) {
|
|
||||||
Ok(_) => {
|
|
||||||
let new_state = state.register_managed_path(
|
|
||||||
&created,
|
|
||||||
dir.to_string_lossy().into_owned(),
|
|
||||||
);
|
|
||||||
created.push(dir.to_string_lossy().into_owned());
|
|
||||||
Continue((new_state, created, new_path, Ok(())))
|
|
||||||
}
|
|
||||||
Err(e) => Done((state, created, path, Err(anyhow!(e)))),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
created.push(dir.to_string_lossy().into_owned());
|
|
||||||
Continue((state, created, new_path, Ok(())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
otherwise => Done((
|
|
||||||
state,
|
|
||||||
created,
|
|
||||||
path,
|
|
||||||
Err(anyhow!(
|
|
||||||
"Unexpected path component encountered: {:?}",
|
|
||||||
otherwise
|
|
||||||
)),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.into_inner();
|
|
||||||
|
|
||||||
match status.and_then(|_| {
|
match status.and_then(|_| {
|
||||||
copy_file(
|
copy_file(
|
||||||
entry
|
entry
|
||||||
|
|
@ -285,22 +398,52 @@ fn create_etc_link(
|
||||||
&entry.mode,
|
&entry.mode,
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
Ok(_) => (
|
Ok(_) => (new_state.register_managed_entry(&target_path), Ok(())),
|
||||||
new_state.register_managed_path(
|
|
||||||
&created_paths,
|
|
||||||
target_path
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned(),
|
|
||||||
),
|
|
||||||
Ok(()),
|
|
||||||
),
|
|
||||||
e => (new_state, e),
|
e => (new_state, e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_dir_recursively(dir: &Path, state: EtcTree) -> (EtcTree, Result<()>) {
|
||||||
|
use itertools::FoldWhile::{Continue, Done};
|
||||||
|
use path::Component;
|
||||||
|
|
||||||
|
let dirbuilder = DirBuilder::new();
|
||||||
|
let (new_state, _, status) = dir
|
||||||
|
.components()
|
||||||
|
.fold_while(
|
||||||
|
(state, PathBuf::from("/"), Ok(())),
|
||||||
|
|(state, path, _), component| match component {
|
||||||
|
Component::RootDir => Continue((state, path, Ok(()))),
|
||||||
|
Component::Normal(dir) => {
|
||||||
|
let new_path = path.join(dir);
|
||||||
|
if !new_path.exists() {
|
||||||
|
log::debug!("Creating path: {}", new_path.display());
|
||||||
|
match dirbuilder.create(new_path.as_path()) {
|
||||||
|
Ok(_) => {
|
||||||
|
let new_state = state.register_managed_entry(&new_path);
|
||||||
|
Continue((new_state, new_path, Ok(())))
|
||||||
|
}
|
||||||
|
Err(e) => Done((state, path, Err(anyhow!(e)))),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Continue((state, new_path, Ok(())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
otherwise => Done((
|
||||||
|
state,
|
||||||
|
path,
|
||||||
|
Err(anyhow!(
|
||||||
|
"Unexpected path component encountered: {:?}",
|
||||||
|
otherwise
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.into_inner();
|
||||||
|
(new_state, status)
|
||||||
|
}
|
||||||
|
|
||||||
fn copy_file(source: &Path, target: &Path, mode: &str) -> Result<()> {
|
fn copy_file(source: &Path, target: &Path, mode: &str) -> Result<()> {
|
||||||
fs::copy(source, target)?;
|
fs::copy(source, target)?;
|
||||||
let mode_int = u32::from_str_radix(mode, 8).map_err(anyhow::Error::from)?;
|
let mode_int = u32::from_str_radix(mode, 8).map_err(anyhow::Error::from)?;
|
||||||
|
|
@ -316,7 +459,7 @@ fn etc_dir(ephemeral: bool) -> PathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialise_created_files(created_files: &EtcStateInfo) -> Result<()> {
|
fn serialise_state(created_files: &EtcTree) -> Result<()> {
|
||||||
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_STATE_FILE_NAME);
|
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_STATE_FILE_NAME);
|
||||||
DirBuilder::new()
|
DirBuilder::new()
|
||||||
.recursive(true)
|
.recursive(true)
|
||||||
|
|
@ -328,7 +471,7 @@ fn serialise_created_files(created_files: &EtcStateInfo) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_created_files() -> Result<EtcStateInfo> {
|
fn read_created_files() -> Result<EtcTree> {
|
||||||
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_STATE_FILE_NAME);
|
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_STATE_FILE_NAME);
|
||||||
DirBuilder::new()
|
DirBuilder::new()
|
||||||
.recursive(true)
|
.recursive(true)
|
||||||
|
|
@ -345,5 +488,82 @@ fn read_created_files() -> Result<EtcStateInfo> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(EtcStateInfo::new())
|
Ok(EtcTree::new(PathBuf::from("/")))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn etc_tree_register() {
|
||||||
|
let tree1 = EtcTree::new(PathBuf::from("/"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz").join("bar"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2").join("bar"));
|
||||||
|
dbg!(&tree1);
|
||||||
|
assert_eq!(
|
||||||
|
tree1.nested.keys().sorted().collect::<Vec<_>>(),
|
||||||
|
["foo", "foo2"]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tree1
|
||||||
|
.nested
|
||||||
|
.get(OsStr::new("foo2"))
|
||||||
|
.unwrap()
|
||||||
|
.nested
|
||||||
|
.get(OsStr::new("baz"))
|
||||||
|
.unwrap()
|
||||||
|
.nested
|
||||||
|
.get(OsStr::new("bar"))
|
||||||
|
.unwrap()
|
||||||
|
.path,
|
||||||
|
PathBuf::from("/").join("foo2").join("baz").join("bar")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn etc_tree_deactivate() {
|
||||||
|
let tree1 = EtcTree::new(PathBuf::from("/"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz").join("bar"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2").join("bar"));
|
||||||
|
let tree2 =
|
||||||
|
tree1
|
||||||
|
.clone()
|
||||||
|
.deactivate_managed_entry(&PathBuf::from("/").join("foo2"), &|p| {
|
||||||
|
println!("Deactivating: {}", p.display());
|
||||||
|
true
|
||||||
|
});
|
||||||
|
dbg!(&tree1);
|
||||||
|
assert_eq!(tree2.nested.keys().sorted().collect::<Vec<_>>(), ["foo"]);
|
||||||
|
assert_eq!(
|
||||||
|
tree1.nested.keys().sorted().collect::<Vec<_>>(),
|
||||||
|
["foo", "foo2"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn etc_tree_diff() {
|
||||||
|
let tree1 = EtcTree::new(PathBuf::from("/"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz").join("bar"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2").join("bar"));
|
||||||
|
let tree2 = EtcTree::new(PathBuf::from("/"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||||
|
.register_managed_entry(&PathBuf::from("/").join("foo3").join("bar"));
|
||||||
|
//tree2.diff(&mut tree1, &|name, _subtree| {
|
||||||
|
// println!("Deactivating subtree: {name}");
|
||||||
|
// true
|
||||||
|
//});
|
||||||
|
dbg!(&tree1);
|
||||||
|
assert_eq!(
|
||||||
|
tree1.nested.keys().sorted().collect::<Vec<_>>(),
|
||||||
|
["foo", "foo2"]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue