Rework error handling and merge the two state files.
This commit is contained in:
parent
0f0eeec627
commit
d3c8c6923f
7 changed files with 361 additions and 292 deletions
|
|
@ -29,3 +29,4 @@ log = "0.4.17"
|
||||||
nix = "0.26.2"
|
nix = "0.26.2"
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.91"
|
serde_json = "1.0.91"
|
||||||
|
thiserror = "1.0.40"
|
||||||
|
|
|
||||||
127
src/activate.rs
127
src/activate.rs
|
|
@ -2,9 +2,65 @@ mod etc_files;
|
||||||
mod services;
|
mod services;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use std::process;
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs::DirBuilder;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{fs, io, process};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::StorePath;
|
use crate::activate::etc_files::EtcTree;
|
||||||
|
use crate::{StorePath, STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR};
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ActivationError<R> {
|
||||||
|
#[error("")]
|
||||||
|
WithPartialResult { result: R, source: anyhow::Error },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> ActivationError<R> {
|
||||||
|
fn with_partial_result<E>(result: R, source: E) -> Self
|
||||||
|
where
|
||||||
|
E: Into<anyhow::Error>,
|
||||||
|
{
|
||||||
|
Self::WithPartialResult {
|
||||||
|
result,
|
||||||
|
source: source.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ActivationResult<R> = Result<R, ActivationError<R>>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct State {
|
||||||
|
etc_tree: EtcTree,
|
||||||
|
services: services::Services,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn from_file(state_file: &Path) -> Result<Self> {
|
||||||
|
if state_file.is_file() {
|
||||||
|
log::info!("Reading state info from {}", state_file.display());
|
||||||
|
let reader = io::BufReader::new(fs::File::open(state_file)?);
|
||||||
|
serde_json::from_reader(reader).or_else(|e| {
|
||||||
|
log::error!("Error reading the state file, ignoring.");
|
||||||
|
log::error!("{e:?}");
|
||||||
|
Ok(Self::default())
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Self::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_to_file(&self, state_file: &Path) -> 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, self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
||||||
log::info!("Activating system-manager profile: {store_path}");
|
log::info!("Activating system-manager profile: {store_path}");
|
||||||
|
|
@ -17,20 +73,67 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
||||||
anyhow::bail!("Failure in pre-activation assertions.");
|
anyhow::bail!("Failure in pre-activation assertions.");
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Activating etc files...");
|
let state_file = &get_state_file()?;
|
||||||
etc_files::activate(store_path, ephemeral)?;
|
let old_state = State::from_file(state_file)?;
|
||||||
|
|
||||||
|
log::info!("Activating etc files...");
|
||||||
|
|
||||||
|
match etc_files::activate(store_path, old_state.etc_tree, ephemeral) {
|
||||||
|
Ok(etc_tree) => {
|
||||||
log::info!("Activating systemd services...");
|
log::info!("Activating systemd services...");
|
||||||
services::activate(store_path, ephemeral)?;
|
match services::activate(store_path, old_state.services, ephemeral) {
|
||||||
|
Ok(services) => State { etc_tree, services },
|
||||||
|
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||||
|
log::error!("Error during activation: {source:?}");
|
||||||
|
State {
|
||||||
|
etc_tree,
|
||||||
|
services: result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||||
|
log::error!("Error during activation: {source:?}");
|
||||||
|
State {
|
||||||
|
etc_tree: result,
|
||||||
|
..old_state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.write_to_file(state_file)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO should we also remove the GC root for the profile if it exists?
|
|
||||||
pub fn deactivate() -> Result<()> {
|
pub fn deactivate() -> Result<()> {
|
||||||
log::info!("Deactivating system-manager");
|
log::info!("Deactivating system-manager");
|
||||||
etc_files::deactivate()?;
|
let state_file = &get_state_file()?;
|
||||||
services::deactivate()?;
|
let old_state = State::from_file(state_file)?;
|
||||||
|
log::debug!("{old_state:?}");
|
||||||
|
|
||||||
|
match etc_files::deactivate(old_state.etc_tree) {
|
||||||
|
Ok(etc_tree) => {
|
||||||
|
log::info!("Deactivating systemd services...");
|
||||||
|
match services::deactivate(old_state.services) {
|
||||||
|
Ok(services) => State { etc_tree, services },
|
||||||
|
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||||
|
log::error!("Error during deactivation: {source:?}");
|
||||||
|
State {
|
||||||
|
etc_tree,
|
||||||
|
services: result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||||
|
log::error!("Error during deactivation: {source:?}");
|
||||||
|
State {
|
||||||
|
etc_tree: result,
|
||||||
|
..old_state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.write_to_file(state_file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,3 +149,11 @@ fn run_preactivation_assertions(store_path: &StorePath) -> Result<process::ExitS
|
||||||
.status()?;
|
.status()?;
|
||||||
Ok(status)
|
Ok(status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_state_file() -> Result<PathBuf> {
|
||||||
|
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(STATE_FILE_NAME);
|
||||||
|
DirBuilder::new()
|
||||||
|
.recursive(true)
|
||||||
|
.create(SYSTEM_MANAGER_STATE_DIR)?;
|
||||||
|
Ok(state_file)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
mod etc_tree;
|
mod etc_tree;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
|
||||||
use im::HashMap;
|
use im::HashMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -10,13 +9,17 @@ use std::path;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fs, io};
|
use std::{fs, io};
|
||||||
|
|
||||||
|
use self::etc_tree::EtcFileStatus;
|
||||||
|
use super::ActivationResult;
|
||||||
|
use crate::activate::ActivationError;
|
||||||
use crate::{
|
use crate::{
|
||||||
create_link, create_store_link, etc_dir, remove_dir, remove_file, remove_link, StorePath,
|
create_link, create_store_link, etc_dir, remove_dir, remove_file, remove_link, StorePath,
|
||||||
ETC_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR, SYSTEM_MANAGER_STATIC_NAME,
|
SYSTEM_MANAGER_STATIC_NAME,
|
||||||
};
|
};
|
||||||
use etc_tree::EtcTree;
|
|
||||||
|
|
||||||
use self::etc_tree::EtcFileStatus;
|
pub use etc_tree::EtcTree;
|
||||||
|
|
||||||
|
type EtcActivationResult = ActivationResult<EtcTree>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
@ -63,7 +66,7 @@ struct CreatedEtcFile {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
fn read_config(store_path: &StorePath) -> anyhow::Result<EtcFilesConfig> {
|
||||||
log::info!("Reading etc file definitions...");
|
log::info!("Reading etc file definitions...");
|
||||||
let file = fs::File::open(
|
let file = fs::File::open(
|
||||||
Path::new(&store_path.store_path)
|
Path::new(&store_path.store_path)
|
||||||
|
|
@ -73,48 +76,47 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
||||||
let reader = io::BufReader::new(file);
|
let reader = io::BufReader::new(file);
|
||||||
let config: EtcFilesConfig = serde_json::from_reader(reader)?;
|
let config: EtcFilesConfig = serde_json::from_reader(reader)?;
|
||||||
log::debug!("{config}");
|
log::debug!("{config}");
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn activate(
|
||||||
|
store_path: &StorePath,
|
||||||
|
old_state: EtcTree,
|
||||||
|
ephemeral: bool,
|
||||||
|
) -> EtcActivationResult {
|
||||||
|
let config = read_config(store_path)
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(old_state.clone(), e))?;
|
||||||
|
|
||||||
let etc_dir = etc_dir(ephemeral);
|
let etc_dir = etc_dir(ephemeral);
|
||||||
log::info!("Creating /etc entries in {}", etc_dir.display());
|
log::info!("Creating /etc entries in {}", etc_dir.display());
|
||||||
|
|
||||||
DirBuilder::new().recursive(true).create(&etc_dir)?;
|
|
||||||
|
|
||||||
let old_state = EtcTree::from_file(&get_state_file()?)?;
|
|
||||||
let initial_state = EtcTree::root_node();
|
let initial_state = EtcTree::root_node();
|
||||||
|
|
||||||
let (state, status) = create_etc_static_link(
|
let state = create_etc_static_link(
|
||||||
SYSTEM_MANAGER_STATIC_NAME,
|
SYSTEM_MANAGER_STATIC_NAME,
|
||||||
&config.static_env,
|
&config.static_env,
|
||||||
&etc_dir,
|
&etc_dir,
|
||||||
initial_state,
|
initial_state,
|
||||||
);
|
)?;
|
||||||
status?;
|
|
||||||
|
|
||||||
// Create the rest of the links and serialise the resulting state
|
// Create the rest of the links
|
||||||
create_etc_links(config.entries.values(), &etc_dir, state, &old_state)
|
let final_state = create_etc_links(config.entries.values(), &etc_dir, state, &old_state)
|
||||||
.update_state(old_state, &try_delete_path)
|
.update_state(old_state, &try_delete_path)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default();
|
||||||
.write_to_file(&get_state_file()?)?;
|
|
||||||
|
|
||||||
log::info!("Done");
|
log::info!("Done");
|
||||||
Ok(())
|
Ok(final_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deactivate() -> Result<()> {
|
pub fn deactivate(old_state: EtcTree) -> EtcActivationResult {
|
||||||
let state = EtcTree::from_file(&get_state_file()?)?;
|
let final_state = old_state.deactivate(&try_delete_path).unwrap_or_default();
|
||||||
log::debug!("{:?}", state);
|
|
||||||
|
|
||||||
state
|
|
||||||
.deactivate(&try_delete_path)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.write_to_file(&get_state_file()?)?;
|
|
||||||
|
|
||||||
log::info!("Done");
|
log::info!("Done");
|
||||||
Ok(())
|
Ok(final_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_delete_path(path: &Path, status: &EtcFileStatus) -> bool {
|
fn try_delete_path(path: &Path, status: &EtcFileStatus) -> bool {
|
||||||
fn do_try_delete(path: &Path, status: &EtcFileStatus) -> Result<()> {
|
fn do_try_delete(path: &Path, status: &EtcFileStatus) -> anyhow::Result<()> {
|
||||||
// exists() returns false for broken symlinks
|
// exists() returns false for broken symlinks
|
||||||
if path.exists() || path.is_symlink() {
|
if path.exists() || path.is_symlink() {
|
||||||
if path.is_symlink() {
|
if path.is_symlink() {
|
||||||
|
|
@ -158,12 +160,15 @@ 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_entry(entry, etc_dir, state, old_state);
|
let new_state = create_etc_entry(entry, etc_dir, state, old_state);
|
||||||
match status {
|
match new_state {
|
||||||
Ok(_) => new_state,
|
Ok(new_state) => new_state,
|
||||||
Err(e) => {
|
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||||
log::error!("Error while creating file in {}: {e}", etc_dir.display());
|
log::error!(
|
||||||
new_state
|
"Error while creating file in {}: {source:?}",
|
||||||
|
etc_dir.display()
|
||||||
|
);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -174,13 +179,15 @@ fn create_etc_static_link(
|
||||||
store_path: &StorePath,
|
store_path: &StorePath,
|
||||||
etc_dir: &Path,
|
etc_dir: &Path,
|
||||||
state: EtcTree,
|
state: EtcTree,
|
||||||
) -> (EtcTree, Result<()>) {
|
) -> EtcActivationResult {
|
||||||
let static_path = etc_dir.join(static_dir_name);
|
let static_path = etc_dir.join(static_dir_name);
|
||||||
let (new_state, status) = create_dir_recursively(static_path.parent().unwrap(), state);
|
let new_state = create_dir_recursively(static_path.parent().unwrap(), state);
|
||||||
match status.and_then(|_| create_store_link(store_path, &static_path)) {
|
new_state.and_then(|new_state| {
|
||||||
Ok(_) => (new_state.register_managed_entry(&static_path), Ok(())),
|
create_store_link(store_path, &static_path).map_or_else(
|
||||||
e => (new_state, e),
|
|e| Err(ActivationError::with_partial_result(new_state.clone(), e)),
|
||||||
}
|
|_| Ok(new_state.clone().register_managed_entry(&static_path)),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_etc_link<P>(
|
fn create_etc_link<P>(
|
||||||
|
|
@ -188,7 +195,7 @@ fn create_etc_link<P>(
|
||||||
etc_dir: &Path,
|
etc_dir: &Path,
|
||||||
state: EtcTree,
|
state: EtcTree,
|
||||||
old_state: &EtcTree,
|
old_state: &EtcTree,
|
||||||
) -> (EtcTree, Result<()>)
|
) -> EtcActivationResult
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
|
@ -199,16 +206,15 @@ where
|
||||||
state: EtcTree,
|
state: EtcTree,
|
||||||
old_state: &EtcTree,
|
old_state: &EtcTree,
|
||||||
upwards_path: &Path,
|
upwards_path: &Path,
|
||||||
) -> (EtcTree, Result<()>) {
|
) -> EtcActivationResult {
|
||||||
let link_path = etc_dir.join(link_target);
|
let link_path = etc_dir.join(link_target);
|
||||||
if link_path.is_dir() && absolute_target.is_dir() {
|
if link_path.is_dir() && absolute_target.is_dir() {
|
||||||
log::info!("Entering into directory...");
|
log::info!("Entering into directory...");
|
||||||
(
|
Ok(absolute_target
|
||||||
absolute_target
|
|
||||||
.read_dir()
|
.read_dir()
|
||||||
.expect("Error reading the directory.")
|
.expect("Error reading the directory.")
|
||||||
.fold(state, |state, entry| {
|
.fold(state, |state, entry| {
|
||||||
let (new_state, status) = go(
|
let new_state = go(
|
||||||
&link_target.join(
|
&link_target.join(
|
||||||
entry
|
entry
|
||||||
.expect("Error reading the directory entry.")
|
.expect("Error reading the directory entry.")
|
||||||
|
|
@ -219,26 +225,25 @@ where
|
||||||
old_state,
|
old_state,
|
||||||
&upwards_path.join(".."),
|
&upwards_path.join(".."),
|
||||||
);
|
);
|
||||||
if let Err(e) = status {
|
match new_state {
|
||||||
|
Ok(new_state) => new_state,
|
||||||
|
Err(ActivationError::WithPartialResult { result, source }) => {
|
||||||
log::error!(
|
log::error!(
|
||||||
"Error while trying to link directory {}: {:?}",
|
"Error while trying to link directory {}: {source:?}",
|
||||||
absolute_target.display(),
|
absolute_target.display()
|
||||||
e
|
|
||||||
);
|
);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
new_state
|
}
|
||||||
}),
|
}))
|
||||||
// TODO better error handling
|
|
||||||
Ok(()),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(
|
Err(ActivationError::with_partial_result(
|
||||||
state,
|
state,
|
||||||
Err(anyhow!(
|
anyhow::anyhow!(
|
||||||
"Unmanaged file or directory {} already exists, ignoring...",
|
"Unmanaged file or directory {} already exists, ignoring...",
|
||||||
link_path.display()
|
link_path.display()
|
||||||
)),
|
),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -248,10 +253,9 @@ where
|
||||||
state: EtcTree,
|
state: EtcTree,
|
||||||
old_state: &EtcTree,
|
old_state: &EtcTree,
|
||||||
upwards_path: &Path,
|
upwards_path: &Path,
|
||||||
) -> (EtcTree, Result<()>) {
|
) -> EtcActivationResult {
|
||||||
let link_path = etc_dir.join(link_target);
|
let link_path = etc_dir.join(link_target);
|
||||||
match create_dir_recursively(link_path.parent().unwrap(), state) {
|
let dir_state = create_dir_recursively(link_path.parent().unwrap(), state)?;
|
||||||
(dir_state, Ok(_)) => {
|
|
||||||
let target = upwards_path
|
let target = upwards_path
|
||||||
.join(SYSTEM_MANAGER_STATIC_NAME)
|
.join(SYSTEM_MANAGER_STATIC_NAME)
|
||||||
.join(link_target);
|
.join(link_target);
|
||||||
|
|
@ -268,28 +272,25 @@ where
|
||||||
} else if link_path.is_symlink()
|
} else if link_path.is_symlink()
|
||||||
&& link_path.read_link().expect("Error reading link.") == target
|
&& link_path.read_link().expect("Error reading link.") == target
|
||||||
{
|
{
|
||||||
(dir_state.register_managed_entry(&link_path), Ok(()))
|
Ok(dir_state.register_managed_entry(&link_path))
|
||||||
} else {
|
} else {
|
||||||
let result = if link_path.exists() {
|
let result = if link_path.exists() {
|
||||||
assert!(old_state.is_managed(&link_path));
|
assert!(old_state.is_managed(&link_path));
|
||||||
fs::remove_file(&link_path).map_err(anyhow::Error::from)
|
fs::remove_file(&link_path)
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(dir_state.clone(), e))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
|
|
||||||
match result.and_then(|_| create_link(&target, &link_path)) {
|
match result.and_then(|_| {
|
||||||
Ok(_) => (dir_state.register_managed_entry(&link_path), Ok(())),
|
create_link(&target, &link_path)
|
||||||
Err(e) => (
|
.map_err(|e| ActivationError::with_partial_result(dir_state.clone(), e))
|
||||||
dir_state,
|
}) {
|
||||||
Err(anyhow!(e)
|
Ok(_) => Ok(dir_state.register_managed_entry(&link_path)),
|
||||||
.context(format!("Error creating link: {}", link_path.display()))),
|
Err(e) => Err(e),
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(new_state, e) => (new_state, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go(
|
go(
|
||||||
link_target.as_ref(),
|
link_target.as_ref(),
|
||||||
|
|
@ -305,74 +306,83 @@ fn create_etc_entry(
|
||||||
etc_dir: &Path,
|
etc_dir: &Path,
|
||||||
state: EtcTree,
|
state: EtcTree,
|
||||||
old_state: &EtcTree,
|
old_state: &EtcTree,
|
||||||
) -> (EtcTree, Result<()>) {
|
) -> EtcActivationResult {
|
||||||
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 {
|
||||||
(
|
Err(ActivationError::with_partial_result(
|
||||||
state,
|
state,
|
||||||
Err(anyhow!("Cannot create link: {}", entry.target.display(),)),
|
anyhow::anyhow!("Cannot create link: {}", entry.target.display()),
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let target_path = etc_dir.join(&entry.target);
|
let target_path = etc_dir.join(&entry.target);
|
||||||
let (new_state, status) = create_dir_recursively(target_path.parent().unwrap(), state);
|
let new_state = create_dir_recursively(target_path.parent().unwrap(), state)?;
|
||||||
match status.and_then(|_| {
|
match copy_file(
|
||||||
copy_file(
|
|
||||||
&entry.source.store_path.join(&entry.target),
|
&entry.source.store_path.join(&entry.target),
|
||||||
&target_path,
|
&target_path,
|
||||||
&entry.mode,
|
&entry.mode,
|
||||||
old_state,
|
old_state,
|
||||||
)
|
) {
|
||||||
}) {
|
Ok(_) => Ok(new_state.register_managed_entry(&target_path)),
|
||||||
Ok(_) => (new_state.register_managed_entry(&target_path), Ok(())),
|
Err(e) => Err(ActivationError::with_partial_result(new_state, e)),
|
||||||
e => (new_state, e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_dir_recursively(dir: &Path, state: EtcTree) -> (EtcTree, Result<()>) {
|
fn create_dir_recursively(dir: &Path, state: EtcTree) -> EtcActivationResult {
|
||||||
use itertools::FoldWhile::{Continue, Done};
|
use itertools::FoldWhile::{Continue, Done};
|
||||||
use path::Component;
|
use path::Component;
|
||||||
|
|
||||||
let dirbuilder = DirBuilder::new();
|
let dirbuilder = DirBuilder::new();
|
||||||
let (new_state, _, status) = dir
|
let (new_state, _) = dir
|
||||||
.components()
|
.components()
|
||||||
.fold_while(
|
.fold_while(
|
||||||
(state, PathBuf::from(path::MAIN_SEPARATOR_STR), Ok(())),
|
(Ok(state), PathBuf::from(path::MAIN_SEPARATOR_STR)),
|
||||||
|(state, path, _), component| match component {
|
|(state, path), component| match (state, component) {
|
||||||
Component::RootDir => Continue((state, path, Ok(()))),
|
(Ok(state), Component::RootDir) => Continue((Ok(state), path)),
|
||||||
Component::Normal(dir) => {
|
(Ok(state), Component::Normal(dir)) => {
|
||||||
let new_path = path.join(dir);
|
let new_path = path.join(dir);
|
||||||
if !new_path.exists() {
|
if !new_path.exists() {
|
||||||
log::debug!("Creating path: {}", new_path.display());
|
log::debug!("Creating path: {}", new_path.display());
|
||||||
match dirbuilder.create(&new_path) {
|
match dirbuilder.create(&new_path) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let new_state = state.register_managed_entry(&new_path);
|
let new_state = state.register_managed_entry(&new_path);
|
||||||
Continue((new_state, new_path, Ok(())))
|
Continue((Ok(new_state), new_path))
|
||||||
}
|
}
|
||||||
Err(e) => Done((state, path, Err(anyhow!(e)))),
|
Err(e) => Done((
|
||||||
|
Err(ActivationError::with_partial_result(
|
||||||
|
state,
|
||||||
|
anyhow::anyhow!(e).context(format!(
|
||||||
|
"Error creating directory {}",
|
||||||
|
new_path.display()
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
path,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Continue((state, new_path, Ok(())))
|
Continue((Ok(state), new_path))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
otherwise => Done((
|
(Ok(state), otherwise) => Done((
|
||||||
|
Err(ActivationError::with_partial_result(
|
||||||
state,
|
state,
|
||||||
|
anyhow::anyhow!("Unexpected path component encountered: {:?}", otherwise),
|
||||||
|
)),
|
||||||
path,
|
path,
|
||||||
Err(anyhow!(
|
|
||||||
"Unexpected path component encountered: {:?}",
|
|
||||||
otherwise
|
|
||||||
)),
|
|
||||||
)),
|
)),
|
||||||
|
(Err(e), _) => {
|
||||||
|
panic!("Something went horribly wrong! We should not get here: {e:?}.")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.into_inner();
|
.into_inner();
|
||||||
(new_state, status)
|
new_state
|
||||||
}
|
}
|
||||||
|
|
||||||
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) -> anyhow::Result<()> {
|
||||||
let exists = target.try_exists()?;
|
let exists = target.try_exists()?;
|
||||||
if !exists || old_state.is_managed(target) {
|
if !exists || old_state.is_managed(target) {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
|
|
@ -388,11 +398,3 @@ fn copy_file(source: &Path, target: &Path, mode: &str, old_state: &EtcTree) -> R
|
||||||
anyhow::bail!("File {} already exists, ignoring.", target.display());
|
anyhow::bail!("File {} already exists, ignoring.", target.display());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_state_file() -> Result<PathBuf> {
|
|
||||||
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_STATE_FILE_NAME);
|
|
||||||
DirBuilder::new()
|
|
||||||
.recursive(true)
|
|
||||||
.create(SYSTEM_MANAGER_STATE_DIR)?;
|
|
||||||
Ok(state_file)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use anyhow::Result;
|
|
||||||
use im::HashMap;
|
use im::HashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Eq;
|
use std::cmp::Eq;
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
use std::path;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fs, io, path};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
@ -230,29 +229,6 @@ impl EtcTree {
|
||||||
|
|
||||||
Some(merged)
|
Some(merged)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to_file(&self, state_file: &Path) -> 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, self)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_file(state_file: &Path) -> Result<Self> {
|
|
||||||
if state_file.is_file() {
|
|
||||||
log::info!("Reading state info from {}", state_file.display());
|
|
||||||
let reader = io::BufReader::new(fs::File::open(state_file)?);
|
|
||||||
match serde_json::from_reader(reader) {
|
|
||||||
Ok(created_files) => return Ok(created_files),
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Error reading the state file, ignoring.");
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Self::default())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,25 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Context;
|
||||||
use im::{HashMap, HashSet};
|
use im::{HashMap, HashSet};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fs::DirBuilder;
|
|
||||||
use std::path::{self, Path, PathBuf};
|
use std::path::{self, Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::{fs, io, str};
|
use std::{fs, io, str};
|
||||||
|
|
||||||
use crate::{
|
use super::ActivationResult;
|
||||||
create_link, etc_dir, systemd, StorePath, SERVICES_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR,
|
use crate::activate::ActivationError;
|
||||||
};
|
use crate::{create_link, etc_dir, systemd, StorePath};
|
||||||
|
|
||||||
|
type ServiceActivationResult = ActivationResult<Services>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct ServiceConfig {
|
pub struct ServiceConfig {
|
||||||
store_path: StorePath,
|
store_path: StorePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
type Services = HashMap<String, ServiceConfig>;
|
pub type Services = HashMap<String, ServiceConfig>;
|
||||||
|
|
||||||
fn print_services(services: &Services) -> Result<String> {
|
fn print_services(services: &Services) -> String {
|
||||||
let out = itertools::intersperse(
|
let out = itertools::intersperse(
|
||||||
services
|
services
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -26,30 +27,39 @@ fn print_services(services: &Services) -> Result<String> {
|
||||||
"\n".to_owned(),
|
"\n".to_owned(),
|
||||||
)
|
)
|
||||||
.collect();
|
.collect();
|
||||||
Ok(out)
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
pub fn activate(
|
||||||
verify_systemd_dir(ephemeral)?;
|
store_path: &StorePath,
|
||||||
|
old_services: Services,
|
||||||
let old_services = read_saved_services()?;
|
ephemeral: bool,
|
||||||
|
) -> ServiceActivationResult {
|
||||||
|
verify_systemd_dir(ephemeral)
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
|
|
||||||
log::info!("Reading new service definitions...");
|
log::info!("Reading new service definitions...");
|
||||||
let file = fs::File::open(
|
let file = fs::File::open(
|
||||||
Path::new(&store_path.store_path)
|
Path::new(&store_path.store_path)
|
||||||
.join("services")
|
.join("services")
|
||||||
.join("services.json"),
|
.join("services.json"),
|
||||||
)?;
|
)
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
let reader = io::BufReader::new(file);
|
let reader = io::BufReader::new(file);
|
||||||
let services: Services = serde_json::from_reader(reader)?;
|
let services: Services = serde_json::from_reader(reader)
|
||||||
log::debug!("{}", print_services(&services)?);
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
|
log::debug!("{}", print_services(&services));
|
||||||
|
|
||||||
serialise_saved_services(&services)?;
|
//serialise_saved_services(&services)?;
|
||||||
|
|
||||||
let services_to_stop = old_services.clone().relative_complement(services.clone());
|
let services_to_stop = old_services.clone().relative_complement(services.clone());
|
||||||
|
let services_to_reload = get_services_to_reload(services.clone(), old_services.clone());
|
||||||
|
|
||||||
let service_manager = systemd::ServiceManager::new_session()?;
|
let service_manager = systemd::ServiceManager::new_session()
|
||||||
let job_monitor = service_manager.monitor_jobs_init()?;
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
|
let job_monitor = service_manager
|
||||||
|
.monitor_jobs_init()
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
let timeout = Some(Duration::from_secs(30));
|
let timeout = Some(Duration::from_secs(30));
|
||||||
|
|
||||||
// We need to do this before we reload the systemd daemon, so that the daemon
|
// We need to do this before we reload the systemd daemon, so that the daemon
|
||||||
|
|
@ -58,33 +68,37 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
|
||||||
wait_for_jobs(
|
wait_for_jobs(
|
||||||
&service_manager,
|
&service_manager,
|
||||||
&job_monitor,
|
&job_monitor,
|
||||||
stop_services(&service_manager, &services_to_stop)?,
|
stop_services(&service_manager, &services_to_stop),
|
||||||
&timeout,
|
&timeout,
|
||||||
)?;
|
)
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(services.clone(), e))?;
|
||||||
|
|
||||||
// We added all new services and removed old ones, so let's reload the units
|
// We added all new services and removed old ones, so let's reload the units
|
||||||
// to tell systemd about them.
|
// to tell systemd about them.
|
||||||
log::info!("Reloading the systemd daemon...");
|
log::info!("Reloading the systemd daemon...");
|
||||||
service_manager.daemon_reload()?;
|
service_manager
|
||||||
|
.daemon_reload()
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(services.clone(), e))?;
|
||||||
|
|
||||||
let active_targets = get_active_targets(&service_manager);
|
let active_targets = get_active_targets(&service_manager)
|
||||||
let services_to_reload = get_services_to_reload(services, old_services);
|
.map_err(|e| ActivationError::with_partial_result(services.clone(), e))?;
|
||||||
|
|
||||||
wait_for_jobs(
|
wait_for_jobs(
|
||||||
&service_manager,
|
&service_manager,
|
||||||
&job_monitor,
|
&job_monitor,
|
||||||
reload_services(&service_manager, &services_to_reload)?
|
reload_services(&service_manager, &services_to_reload)
|
||||||
+ start_units(&service_manager, &active_targets?)?,
|
+ start_units(&service_manager, &active_targets),
|
||||||
&timeout,
|
&timeout,
|
||||||
)?;
|
)
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(services.clone(), e))?;
|
||||||
|
|
||||||
log::info!("Done");
|
log::info!("Done");
|
||||||
Ok(())
|
Ok(services)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_active_targets(
|
fn get_active_targets(
|
||||||
service_manager: &systemd::ServiceManager,
|
service_manager: &systemd::ServiceManager,
|
||||||
) -> Result<Vec<systemd::UnitStatus>> {
|
) -> anyhow::Result<Vec<systemd::UnitStatus>> {
|
||||||
// We exclude some targets that we do not want to start
|
// We exclude some targets that we do not want to start
|
||||||
let excluded_targets: HashSet<String> =
|
let excluded_targets: HashSet<String> =
|
||||||
["suspend.target", "hibernate.target", "hybrid-sleep.target"]
|
["suspend.target", "hibernate.target", "hybrid-sleep.target"]
|
||||||
|
|
@ -135,7 +149,7 @@ fn systemd_system_dir(ephemeral: bool) -> PathBuf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_systemd_dir(ephemeral: bool) -> Result<()> {
|
fn verify_systemd_dir(ephemeral: bool) -> anyhow::Result<()> {
|
||||||
if ephemeral {
|
if ephemeral {
|
||||||
let system_dir = systemd_system_dir(ephemeral);
|
let system_dir = systemd_system_dir(ephemeral);
|
||||||
if system_dir.exists()
|
if system_dir.exists()
|
||||||
|
|
@ -177,15 +191,18 @@ fn verify_systemd_dir(ephemeral: bool) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deactivate() -> Result<()> {
|
pub fn deactivate(old_services: Services) -> ServiceActivationResult {
|
||||||
restore_ephemeral_system_dir()?;
|
|
||||||
|
|
||||||
let old_services = read_saved_services()?;
|
|
||||||
log::debug!("{:?}", old_services);
|
log::debug!("{:?}", old_services);
|
||||||
|
|
||||||
let service_manager = systemd::ServiceManager::new_session()?;
|
restore_ephemeral_system_dir()
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
|
|
||||||
|
let service_manager = systemd::ServiceManager::new_session()
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
if !old_services.is_empty() {
|
if !old_services.is_empty() {
|
||||||
let job_monitor = service_manager.monitor_jobs_init()?;
|
let job_monitor = service_manager
|
||||||
|
.monitor_jobs_init()
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
|
||||||
let timeout = Some(Duration::from_secs(30));
|
let timeout = Some(Duration::from_secs(30));
|
||||||
|
|
||||||
// We need to do this before we reload the systemd daemon, so that the daemon
|
// We need to do this before we reload the systemd daemon, so that the daemon
|
||||||
|
|
@ -193,19 +210,21 @@ pub fn deactivate() -> Result<()> {
|
||||||
wait_for_jobs(
|
wait_for_jobs(
|
||||||
&service_manager,
|
&service_manager,
|
||||||
&job_monitor,
|
&job_monitor,
|
||||||
stop_services(&service_manager, &old_services)?,
|
stop_services(&service_manager, &old_services),
|
||||||
&timeout,
|
&timeout,
|
||||||
)?;
|
)
|
||||||
|
// We consider all jobs stopped now..
|
||||||
|
.map_err(|e| ActivationError::with_partial_result(im::HashMap::new(), e))?;
|
||||||
} else {
|
} else {
|
||||||
log::info!("No services to deactivate.");
|
log::info!("No services to deactivate.");
|
||||||
}
|
}
|
||||||
log::info!("Reloading the systemd daemon...");
|
log::info!("Reloading the systemd daemon...");
|
||||||
service_manager.daemon_reload()?;
|
service_manager
|
||||||
|
.daemon_reload()
|
||||||
serialise_saved_services(&HashMap::new())?;
|
.map_err(|e| ActivationError::with_partial_result(im::HashMap::new(), e))?;
|
||||||
|
|
||||||
log::info!("Done");
|
log::info!("Done");
|
||||||
Ok(())
|
Ok(im::HashMap::new())
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we turned the ephemeral systemd system dir under /run into a symlink,
|
// If we turned the ephemeral systemd system dir under /run into a symlink,
|
||||||
|
|
@ -213,7 +232,7 @@ pub fn deactivate() -> Result<()> {
|
||||||
// To avoid this, we always check whether this directory exists and is correct,
|
// To avoid this, we always check whether this directory exists and is correct,
|
||||||
// and we recreate it if needed.
|
// and we recreate it if needed.
|
||||||
// NOTE: We rely on the fact that the etc files get cleaned up first, before this runs!
|
// NOTE: We rely on the fact that the etc files get cleaned up first, before this runs!
|
||||||
fn restore_ephemeral_system_dir() -> Result<()> {
|
fn restore_ephemeral_system_dir() -> anyhow::Result<()> {
|
||||||
let ephemeral_systemd_system_dir = systemd_system_dir(true);
|
let ephemeral_systemd_system_dir = systemd_system_dir(true);
|
||||||
if !ephemeral_systemd_system_dir.exists() {
|
if !ephemeral_systemd_system_dir.exists() {
|
||||||
if ephemeral_systemd_system_dir.is_symlink() {
|
if ephemeral_systemd_system_dir.is_symlink() {
|
||||||
|
|
@ -224,43 +243,7 @@ fn restore_ephemeral_system_dir() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we should probably lock this file to avoid concurrent writes
|
fn stop_services(service_manager: &systemd::ServiceManager, services: &Services) -> HashSet<JobId> {
|
||||||
fn serialise_saved_services(services: &Services) -> Result<()> {
|
|
||||||
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(SERVICES_STATE_FILE_NAME);
|
|
||||||
DirBuilder::new()
|
|
||||||
.recursive(true)
|
|
||||||
.create(SYSTEM_MANAGER_STATE_DIR)?;
|
|
||||||
|
|
||||||
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, services)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_saved_services() -> Result<Services> {
|
|
||||||
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(SERVICES_STATE_FILE_NAME);
|
|
||||||
DirBuilder::new()
|
|
||||||
.recursive(true)
|
|
||||||
.create(SYSTEM_MANAGER_STATE_DIR)?;
|
|
||||||
|
|
||||||
if Path::new(&state_file).is_file() {
|
|
||||||
log::info!("Reading state info from {}", state_file.display());
|
|
||||||
let reader = io::BufReader::new(fs::File::open(state_file)?);
|
|
||||||
match serde_json::from_reader(reader) {
|
|
||||||
Ok(linked_services) => return Ok(linked_services),
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("Error reading the state file, ignoring.");
|
|
||||||
log::error!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(HashMap::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop_services(
|
|
||||||
service_manager: &systemd::ServiceManager,
|
|
||||||
services: &Services,
|
|
||||||
) -> Result<HashSet<JobId>> {
|
|
||||||
for_each_unit(
|
for_each_unit(
|
||||||
|s| service_manager.stop_unit(s),
|
|s| service_manager.stop_unit(s),
|
||||||
convert_services(services),
|
convert_services(services),
|
||||||
|
|
@ -271,7 +254,7 @@ fn stop_services(
|
||||||
fn reload_services(
|
fn reload_services(
|
||||||
service_manager: &systemd::ServiceManager,
|
service_manager: &systemd::ServiceManager,
|
||||||
services: &Services,
|
services: &Services,
|
||||||
) -> Result<HashSet<JobId>> {
|
) -> HashSet<JobId> {
|
||||||
for_each_unit(
|
for_each_unit(
|
||||||
|s| service_manager.reload_unit(s),
|
|s| service_manager.reload_unit(s),
|
||||||
convert_services(services),
|
convert_services(services),
|
||||||
|
|
@ -282,7 +265,7 @@ fn reload_services(
|
||||||
fn start_units(
|
fn start_units(
|
||||||
service_manager: &systemd::ServiceManager,
|
service_manager: &systemd::ServiceManager,
|
||||||
units: &[systemd::UnitStatus],
|
units: &[systemd::UnitStatus],
|
||||||
) -> Result<HashSet<JobId>> {
|
) -> HashSet<JobId> {
|
||||||
for_each_unit(
|
for_each_unit(
|
||||||
|unit| service_manager.start_unit(unit),
|
|unit| service_manager.start_unit(unit),
|
||||||
convert_units(units),
|
convert_units(units),
|
||||||
|
|
@ -301,12 +284,12 @@ fn convert_units(units: &[systemd::UnitStatus]) -> Vec<&str> {
|
||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn for_each_unit<'a, F, R, S>(action: F, units: S, log_action: &str) -> Result<HashSet<JobId>>
|
fn for_each_unit<'a, F, R, S>(action: F, units: S, log_action: &str) -> HashSet<JobId>
|
||||||
where
|
where
|
||||||
F: Fn(&str) -> Result<R>,
|
F: Fn(&str) -> anyhow::Result<R>,
|
||||||
S: AsRef<[&'a str]>,
|
S: AsRef<[&'a str]>,
|
||||||
{
|
{
|
||||||
let successful_services: HashSet<JobId> =
|
// TODO: do we want to propagate unit failures here in some way?
|
||||||
units
|
units
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -326,10 +309,7 @@ where
|
||||||
log::error!("{e}");
|
log::error!("{e}");
|
||||||
set
|
set
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// TODO: do we want to propagate unit failures here in some way?
|
|
||||||
Ok(successful_services)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_for_jobs(
|
fn wait_for_jobs(
|
||||||
|
|
@ -337,7 +317,7 @@ fn wait_for_jobs(
|
||||||
job_monitor: &systemd::JobMonitor,
|
job_monitor: &systemd::JobMonitor,
|
||||||
jobs: HashSet<JobId>,
|
jobs: HashSet<JobId>,
|
||||||
timeout: &Option<Duration>,
|
timeout: &Option<Duration>,
|
||||||
) -> Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if !service_manager.monitor_jobs_finish(job_monitor, timeout, jobs)? {
|
if !service_manager.monitor_jobs_finish(job_monitor, timeout, jobs)? {
|
||||||
anyhow::bail!("Timeout waiting for systemd jobs");
|
anyhow::bail!("Timeout waiting for systemd jobs");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,7 @@ pub const PROFILE_DIR: &str = "/nix/var/nix/profiles/system-manager-profiles";
|
||||||
pub const PROFILE_NAME: &str = "system-manager";
|
pub const PROFILE_NAME: &str = "system-manager";
|
||||||
pub const GCROOT_PATH: &str = "/nix/var/nix/gcroots/system-manager-current";
|
pub const GCROOT_PATH: &str = "/nix/var/nix/gcroots/system-manager-current";
|
||||||
pub const SYSTEM_MANAGER_STATE_DIR: &str = "/var/lib/system-manager/state";
|
pub const SYSTEM_MANAGER_STATE_DIR: &str = "/var/lib/system-manager/state";
|
||||||
pub const SERVICES_STATE_FILE_NAME: &str = "services.json";
|
pub const STATE_FILE_NAME: &str = "system-manager-state.json";
|
||||||
pub const ETC_STATE_FILE_NAME: &str = "etc-files.json";
|
|
||||||
pub const SYSTEM_MANAGER_STATIC_NAME: &str = ".system-manager-static";
|
pub const SYSTEM_MANAGER_STATIC_NAME: &str = ".system-manager-static";
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -294,8 +294,8 @@ fn check_root() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_toplevel_error<T>(r: Result<T>) -> ExitCode {
|
fn handle_toplevel_error<T>(r: Result<T>) -> ExitCode {
|
||||||
if r.is_err() {
|
if let Err(e) = r {
|
||||||
log::error!("{:?}", r);
|
log::error!("{:?}", e);
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
ExitCode::SUCCESS
|
ExitCode::SUCCESS
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue