Cleanup of EtcTree.
This commit is contained in:
parent
0e5a2c4bd7
commit
95f9897766
3 changed files with 146 additions and 121 deletions
|
|
@ -13,7 +13,7 @@ use std::{fs, io};
|
|||
|
||||
use crate::{
|
||||
create_link, create_store_link, remove_dir, remove_file, remove_link, StorePath,
|
||||
ETC_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR,
|
||||
ETC_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR, SYSTEM_MANAGER_STATIC_NAME,
|
||||
};
|
||||
use etc_tree::EtcTree;
|
||||
|
||||
|
|
@ -57,16 +57,16 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
|||
DirBuilder::new().recursive(true).create(&etc_dir)?;
|
||||
|
||||
let old_state = read_created_files()?;
|
||||
let initial_state = EtcTree::root_node();
|
||||
|
||||
// TODO: constant?
|
||||
let static_dir_name = ".system-manager-static";
|
||||
let (state, status) = create_etc_static_link(
|
||||
static_dir_name,
|
||||
SYSTEM_MANAGER_STATIC_NAME,
|
||||
&config.static_env,
|
||||
&etc_dir,
|
||||
EtcTree::new(PathBuf::new()),
|
||||
initial_state,
|
||||
);
|
||||
status?;
|
||||
|
||||
let new_state = create_etc_links(config.entries.values(), &etc_dir, state).update_state(
|
||||
old_state,
|
||||
&|path| {
|
||||
|
|
@ -75,7 +75,7 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
|||
},
|
||||
);
|
||||
|
||||
serialise_state(&new_state.unwrap_or_else(|| EtcTree::new(PathBuf::from("/"))))?;
|
||||
serialise_state(new_state)?;
|
||||
|
||||
log::info!("Done");
|
||||
Ok(())
|
||||
|
|
@ -85,17 +85,15 @@ pub fn deactivate() -> Result<()> {
|
|||
let state = read_created_files()?;
|
||||
log::debug!("{:?}", state);
|
||||
|
||||
serialise_state(&state.deactivate_managed_entry(
|
||||
Path::new("/"),
|
||||
&|path| match try_delete_path(path) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
serialise_state(state.deactivate(&|path| {
|
||||
try_delete_path(path)
|
||||
.map_err(|e| {
|
||||
log::error!("Error deleting path: {}", path.display());
|
||||
log::error!("{e}");
|
||||
false
|
||||
}
|
||||
},
|
||||
))?;
|
||||
e
|
||||
})
|
||||
.is_ok()
|
||||
}))?;
|
||||
|
||||
log::info!("Done");
|
||||
Ok(())
|
||||
|
|
@ -154,7 +152,7 @@ fn create_etc_link(link_target: &OsStr, etc_dir: &Path, state: EtcTree) -> (EtcT
|
|||
match status.and_then(|_| {
|
||||
create_link(
|
||||
Path::new(".")
|
||||
.join(".system-manager-static")
|
||||
.join(SYSTEM_MANAGER_STATIC_NAME)
|
||||
.join("etc")
|
||||
.join(link_target)
|
||||
.as_path(),
|
||||
|
|
@ -207,7 +205,7 @@ fn create_dir_recursively(dir: &Path, state: EtcTree) -> (EtcTree, Result<()>) {
|
|||
let (new_state, _, status) = dir
|
||||
.components()
|
||||
.fold_while(
|
||||
(state, PathBuf::from("/"), Ok(())),
|
||||
(state, PathBuf::from(path::MAIN_SEPARATOR_STR), Ok(())),
|
||||
|(state, path, _), component| match component {
|
||||
Component::RootDir => Continue((state, path, Ok(()))),
|
||||
Component::Normal(dir) => {
|
||||
|
|
@ -240,10 +238,15 @@ fn create_dir_recursively(dir: &Path, state: EtcTree) -> (EtcTree, Result<()>) {
|
|||
}
|
||||
|
||||
fn copy_file(source: &Path, target: &Path, mode: &str) -> Result<()> {
|
||||
let exists = target.try_exists()?;
|
||||
if !exists {
|
||||
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)?;
|
||||
fs::set_permissions(target, Permissions::from_mode(mode_int))?;
|
||||
Ok(())
|
||||
} else {
|
||||
anyhow::bail!("File {} already exists, ignoring.", target.display());
|
||||
}
|
||||
}
|
||||
|
||||
fn etc_dir(ephemeral: bool) -> PathBuf {
|
||||
|
|
@ -254,7 +257,10 @@ fn etc_dir(ephemeral: bool) -> PathBuf {
|
|||
}
|
||||
}
|
||||
|
||||
fn serialise_state(created_files: &EtcTree) -> Result<()> {
|
||||
fn serialise_state<E>(created_files: Option<E>) -> Result<()>
|
||||
where
|
||||
E: AsRef<EtcTree>,
|
||||
{
|
||||
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_STATE_FILE_NAME);
|
||||
DirBuilder::new()
|
||||
.recursive(true)
|
||||
|
|
@ -262,7 +268,12 @@ fn serialise_state(created_files: &EtcTree) -> Result<()> {
|
|||
|
||||
log::info!("Writing state info into file: {}", state_file.display());
|
||||
let writer = io::BufWriter::new(fs::File::create(state_file)?);
|
||||
serde_json::to_writer(writer, created_files)?;
|
||||
|
||||
if let Some(e) = created_files {
|
||||
serde_json::to_writer(writer, e.as_ref())?;
|
||||
} else {
|
||||
serde_json::to_writer(writer, &EtcTree::default())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -283,5 +294,5 @@ fn read_created_files() -> Result<EtcTree> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Ok(EtcTree::new(PathBuf::from("/")))
|
||||
Ok(EtcTree::default())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,10 @@
|
|||
use im::HashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Eq;
|
||||
use std::ffi::OsString;
|
||||
use std::iter::Peekable;
|
||||
use std::path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EtcTree {
|
||||
status: EtcFileStatus,
|
||||
path: PathBuf,
|
||||
// TODO directories and files are now both represented as a string associated with a nested
|
||||
// map. For files the nested map is simple empty.
|
||||
// We could potentially optimise this.
|
||||
nested: HashMap<OsString, EtcTree>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum EtcFileStatus {
|
||||
|
|
@ -35,22 +23,45 @@ impl EtcFileStatus {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EtcTree {
|
||||
status: EtcFileStatus,
|
||||
path: PathBuf,
|
||||
// TODO directories and files are now both represented as a string associated with a nested
|
||||
// map. For files the nested map is simple empty.
|
||||
// We could potentially optimise this.
|
||||
nested: HashMap<String, EtcTree>,
|
||||
}
|
||||
|
||||
impl AsRef<EtcTree> for EtcTree {
|
||||
fn as_ref(&self) -> &EtcTree {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EtcTree {
|
||||
fn default() -> Self {
|
||||
Self::root_node()
|
||||
}
|
||||
}
|
||||
|
||||
/// Data structure to represent files that are managed by system-manager.
|
||||
///
|
||||
/// This data will be serialised to disk and read on the next run.
|
||||
///
|
||||
/// We need these basic operations:
|
||||
/// 1. Create a new, empty structure
|
||||
/// 1. Create a new root 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 {
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
Self::with_status(path, EtcFileStatus::Unmanaged)
|
||||
}
|
||||
|
||||
pub fn with_status(path: PathBuf, status: EtcFileStatus) -> Self {
|
||||
fn with_status(path: PathBuf, status: EtcFileStatus) -> Self {
|
||||
Self {
|
||||
status,
|
||||
path,
|
||||
|
|
@ -58,6 +69,10 @@ impl EtcTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn root_node() -> Self {
|
||||
Self::new(PathBuf::from(path::MAIN_SEPARATOR_STR))
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
|
@ -75,26 +90,24 @@ impl EtcTree {
|
|||
maybe_subtree.unwrap_or_else(|| {
|
||||
EtcTree::with_status(
|
||||
new_path.to_owned(),
|
||||
if components.peek().is_some() {
|
||||
components
|
||||
.peek()
|
||||
.map_or(EtcFileStatus::Managed, |_| {
|
||||
EtcFileStatus::Unmanaged
|
||||
} else {
|
||||
EtcFileStatus::Managed
|
||||
},
|
||||
}),
|
||||
)
|
||||
}),
|
||||
components,
|
||||
new_path,
|
||||
))
|
||||
},
|
||||
name.to_owned(),
|
||||
name.to_string_lossy().to_string(),
|
||||
);
|
||||
tree
|
||||
}
|
||||
path::Component::RootDir => go(
|
||||
tree,
|
||||
components,
|
||||
path.join(path::MAIN_SEPARATOR.to_string()),
|
||||
),
|
||||
path::Component::RootDir => {
|
||||
go(tree, components, path.join(path::MAIN_SEPARATOR_STR))
|
||||
}
|
||||
_ => panic!(
|
||||
"Unsupported path provided! At path component: {:?}",
|
||||
component
|
||||
|
|
@ -131,63 +144,6 @@ impl EtcTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub 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,
|
||||
),
|
||||
_ => panic!(
|
||||
"Unsupported path provided! At path component: {:?}",
|
||||
component
|
||||
),
|
||||
}
|
||||
} else {
|
||||
tree
|
||||
}
|
||||
}
|
||||
go(
|
||||
self,
|
||||
PathBuf::new(),
|
||||
path.components().peekable(),
|
||||
delete_action,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn update_state<F>(self, other: Self, delete_action: &F) -> Option<Self>
|
||||
where
|
||||
F: Fn(&Path) -> bool,
|
||||
|
|
@ -237,29 +193,86 @@ impl EtcTree {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use itertools::Itertools;
|
||||
use std::ffi::OsStr;
|
||||
|
||||
impl EtcTree {
|
||||
pub 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_string_lossy().to_string(),
|
||||
);
|
||||
tree
|
||||
}
|
||||
path::Component::RootDir => go(
|
||||
tree,
|
||||
path.join(path::MAIN_SEPARATOR.to_string()),
|
||||
components,
|
||||
delete_action,
|
||||
),
|
||||
_ => panic!(
|
||||
"Unsupported path provided! At path component: {:?}",
|
||||
component
|
||||
),
|
||||
}
|
||||
} else {
|
||||
tree
|
||||
}
|
||||
}
|
||||
go(
|
||||
self,
|
||||
PathBuf::new(),
|
||||
path.components().peekable(),
|
||||
delete_action,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn etc_tree_register() {
|
||||
let tree1 = EtcTree::new(PathBuf::from("/"))
|
||||
let tree = EtcTree::root_node()
|
||||
.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);
|
||||
dbg!(&tree);
|
||||
assert_eq!(
|
||||
tree1.nested.keys().sorted().collect::<Vec<_>>(),
|
||||
tree.nested.keys().sorted().collect::<Vec<_>>(),
|
||||
["foo", "foo2"]
|
||||
);
|
||||
assert_eq!(
|
||||
tree1
|
||||
.nested
|
||||
.get(OsStr::new("foo2"))
|
||||
tree.nested
|
||||
.get("foo2")
|
||||
.unwrap()
|
||||
.nested
|
||||
.get(OsStr::new("baz"))
|
||||
.get("baz")
|
||||
.unwrap()
|
||||
.nested
|
||||
.get(OsStr::new("bar"))
|
||||
.get("bar")
|
||||
.unwrap()
|
||||
.path,
|
||||
PathBuf::from("/").join("foo2").join("baz").join("bar")
|
||||
|
|
@ -268,7 +281,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn etc_tree_deactivate() {
|
||||
let tree1 = EtcTree::new(PathBuf::from("/"))
|
||||
let tree1 = EtcTree::root_node()
|
||||
.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"))
|
||||
|
|
@ -301,10 +314,10 @@ mod tests {
|
|||
);
|
||||
assert!(tree2
|
||||
.nested
|
||||
.get(OsStr::new("foo3"))
|
||||
.get("foo3")
|
||||
.unwrap()
|
||||
.nested
|
||||
.get(OsStr::new("baz2"))
|
||||
.get("baz2")
|
||||
.unwrap()
|
||||
.nested
|
||||
.keys()
|
||||
|
|
@ -319,7 +332,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn etc_tree_update_state() {
|
||||
let tree1 = EtcTree::new(PathBuf::from("/"))
|
||||
let tree1 = EtcTree::root_node()
|
||||
.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"))
|
||||
|
|
@ -327,7 +340,7 @@ mod tests {
|
|||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo2").join("baz2").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo3").join("baz2").join("bar"));
|
||||
let tree2 = EtcTree::new(PathBuf::from("/"))
|
||||
let tree2 = EtcTree::root_node()
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo3").join("bar"))
|
||||
.register_managed_entry(&PathBuf::from("/").join("foo4"))
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ const GCROOT_PATH: &str = "/nix/var/nix/gcroots/system-manager-current";
|
|||
const SYSTEM_MANAGER_STATE_DIR: &str = "/var/lib/system-manager/state";
|
||||
const SERVICES_STATE_FILE_NAME: &str = "services.json";
|
||||
const ETC_STATE_FILE_NAME: &str = "etc-files.json";
|
||||
const SYSTEM_MANAGER_STATIC_NAME: &str = ".system-manager-static";
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(from = "String", into = "String", rename_all = "camelCase")]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue