Rework error handling and merge the two state files.

This commit is contained in:
r-vdp 2023-04-11 21:54:45 +02:00
parent 0f0eeec627
commit d3c8c6923f
No known key found for this signature in database
7 changed files with 361 additions and 292 deletions

View file

@ -29,3 +29,4 @@ log = "0.4.17"
nix = "0.26.2"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.91"
thiserror = "1.0.40"

View file

@ -2,9 +2,65 @@ mod etc_files;
mod services;
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<()> {
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.");
}
log::info!("Activating etc files...");
etc_files::activate(store_path, ephemeral)?;
let state_file = &get_state_file()?;
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...");
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(())
}
// TODO should we also remove the GC root for the profile if it exists?
pub fn deactivate() -> Result<()> {
log::info!("Deactivating system-manager");
etc_files::deactivate()?;
services::deactivate()?;
let state_file = &get_state_file()?;
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(())
}
@ -46,3 +149,11 @@ fn run_preactivation_assertions(store_path: &StorePath) -> Result<process::ExitS
.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)
}

View file

@ -1,6 +1,5 @@
mod etc_tree;
use anyhow::{anyhow, Result};
use im::HashMap;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
@ -10,13 +9,17 @@ use std::path;
use std::path::{Path, PathBuf};
use std::{fs, io};
use self::etc_tree::EtcFileStatus;
use super::ActivationResult;
use crate::activate::ActivationError;
use crate::{
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)]
#[serde(rename_all = "camelCase")]
@ -63,7 +66,7 @@ struct CreatedEtcFile {
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...");
let file = fs::File::open(
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 config: EtcFilesConfig = serde_json::from_reader(reader)?;
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);
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 (state, status) = create_etc_static_link(
let state = create_etc_static_link(
SYSTEM_MANAGER_STATIC_NAME,
&config.static_env,
&etc_dir,
initial_state,
);
status?;
)?;
// Create the rest of the links and serialise the resulting state
create_etc_links(config.entries.values(), &etc_dir, state, &old_state)
// Create the rest of the links
let final_state = create_etc_links(config.entries.values(), &etc_dir, state, &old_state)
.update_state(old_state, &try_delete_path)
.unwrap_or_default()
.write_to_file(&get_state_file()?)?;
.unwrap_or_default();
log::info!("Done");
Ok(())
Ok(final_state)
}
pub fn deactivate() -> Result<()> {
let state = EtcTree::from_file(&get_state_file()?)?;
log::debug!("{:?}", state);
state
.deactivate(&try_delete_path)
.unwrap_or_default()
.write_to_file(&get_state_file()?)?;
pub fn deactivate(old_state: EtcTree) -> EtcActivationResult {
let final_state = old_state.deactivate(&try_delete_path).unwrap_or_default();
log::info!("Done");
Ok(())
Ok(final_state)
}
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
if path.exists() || path.is_symlink() {
if path.is_symlink() {
@ -158,12 +160,15 @@ where
E: Iterator<Item = &'a EtcFile>,
{
entries.fold(state, |state, entry| {
let (new_state, status) = create_etc_entry(entry, etc_dir, state, old_state);
match status {
Ok(_) => new_state,
Err(e) => {
log::error!("Error while creating file in {}: {e}", etc_dir.display());
new_state
let new_state = create_etc_entry(entry, etc_dir, state, old_state);
match new_state {
Ok(new_state) => new_state,
Err(ActivationError::WithPartialResult { result, source }) => {
log::error!(
"Error while creating file in {}: {source:?}",
etc_dir.display()
);
result
}
}
})
@ -174,13 +179,15 @@ fn create_etc_static_link(
store_path: &StorePath,
etc_dir: &Path,
state: EtcTree,
) -> (EtcTree, Result<()>) {
) -> EtcActivationResult {
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)) {
Ok(_) => (new_state.register_managed_entry(&static_path), Ok(())),
e => (new_state, e),
}
let new_state = create_dir_recursively(static_path.parent().unwrap(), state);
new_state.and_then(|new_state| {
create_store_link(store_path, &static_path).map_or_else(
|e| Err(ActivationError::with_partial_result(new_state.clone(), e)),
|_| Ok(new_state.clone().register_managed_entry(&static_path)),
)
})
}
fn create_etc_link<P>(
@ -188,7 +195,7 @@ fn create_etc_link<P>(
etc_dir: &Path,
state: EtcTree,
old_state: &EtcTree,
) -> (EtcTree, Result<()>)
) -> EtcActivationResult
where
P: AsRef<Path>,
{
@ -199,16 +206,15 @@ where
state: EtcTree,
old_state: &EtcTree,
upwards_path: &Path,
) -> (EtcTree, Result<()>) {
) -> EtcActivationResult {
let link_path = etc_dir.join(link_target);
if link_path.is_dir() && absolute_target.is_dir() {
log::info!("Entering into directory...");
(
absolute_target
Ok(absolute_target
.read_dir()
.expect("Error reading the directory.")
.fold(state, |state, entry| {
let (new_state, status) = go(
let new_state = go(
&link_target.join(
entry
.expect("Error reading the directory entry.")
@ -219,26 +225,25 @@ where
old_state,
&upwards_path.join(".."),
);
if let Err(e) = status {
match new_state {
Ok(new_state) => new_state,
Err(ActivationError::WithPartialResult { result, source }) => {
log::error!(
"Error while trying to link directory {}: {:?}",
absolute_target.display(),
e
"Error while trying to link directory {}: {source:?}",
absolute_target.display()
);
result
}
new_state
}),
// TODO better error handling
Ok(()),
)
}
}))
} else {
(
Err(ActivationError::with_partial_result(
state,
Err(anyhow!(
anyhow::anyhow!(
"Unmanaged file or directory {} already exists, ignoring...",
link_path.display()
)),
)
),
))
}
}
@ -248,10 +253,9 @@ where
state: EtcTree,
old_state: &EtcTree,
upwards_path: &Path,
) -> (EtcTree, Result<()>) {
) -> EtcActivationResult {
let link_path = etc_dir.join(link_target);
match create_dir_recursively(link_path.parent().unwrap(), state) {
(dir_state, Ok(_)) => {
let dir_state = create_dir_recursively(link_path.parent().unwrap(), state)?;
let target = upwards_path
.join(SYSTEM_MANAGER_STATIC_NAME)
.join(link_target);
@ -268,28 +272,25 @@ where
} else if link_path.is_symlink()
&& 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 {
let result = if link_path.exists() {
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 {
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()))),
),
match result.and_then(|_| {
create_link(&target, &link_path)
.map_err(|e| ActivationError::with_partial_result(dir_state.clone(), e))
}) {
Ok(_) => Ok(dir_state.register_managed_entry(&link_path)),
Err(e) => Err(e),
}
}
}
(new_state, e) => (new_state, e),
}
}
go(
link_target.as_ref(),
@ -305,74 +306,83 @@ fn create_etc_entry(
etc_dir: &Path,
state: EtcTree,
old_state: &EtcTree,
) -> (EtcTree, Result<()>) {
) -> EtcActivationResult {
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)
} else {
(
Err(ActivationError::with_partial_result(
state,
Err(anyhow!("Cannot create link: {}", entry.target.display(),)),
)
anyhow::anyhow!("Cannot create link: {}", entry.target.display()),
))
}
} else {
let target_path = etc_dir.join(&entry.target);
let (new_state, status) = create_dir_recursively(target_path.parent().unwrap(), state);
match status.and_then(|_| {
copy_file(
let new_state = create_dir_recursively(target_path.parent().unwrap(), state)?;
match copy_file(
&entry.source.store_path.join(&entry.target),
&target_path,
&entry.mode,
old_state,
)
}) {
Ok(_) => (new_state.register_managed_entry(&target_path), Ok(())),
e => (new_state, e),
) {
Ok(_) => Ok(new_state.register_managed_entry(&target_path)),
Err(e) => Err(ActivationError::with_partial_result(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 path::Component;
let dirbuilder = DirBuilder::new();
let (new_state, _, status) = dir
let (new_state, _) = dir
.components()
.fold_while(
(state, PathBuf::from(path::MAIN_SEPARATOR_STR), Ok(())),
|(state, path, _), component| match component {
Component::RootDir => Continue((state, path, Ok(()))),
Component::Normal(dir) => {
(Ok(state), PathBuf::from(path::MAIN_SEPARATOR_STR)),
|(state, path), component| match (state, component) {
(Ok(state), Component::RootDir) => Continue((Ok(state), path)),
(Ok(state), 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) {
Ok(_) => {
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 {
Continue((state, new_path, Ok(())))
Continue((Ok(state), new_path))
}
}
otherwise => Done((
(Ok(state), otherwise) => Done((
Err(ActivationError::with_partial_result(
state,
anyhow::anyhow!("Unexpected path component encountered: {:?}", otherwise),
)),
path,
Err(anyhow!(
"Unexpected path component encountered: {:?}",
otherwise
)),
)),
(Err(e), _) => {
panic!("Something went horribly wrong! We should not get here: {e:?}.")
}
},
)
.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()?;
if !exists || old_state.is_managed(target) {
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());
}
}
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)
}

View file

@ -1,10 +1,9 @@
use anyhow::Result;
use im::HashMap;
use serde::{Deserialize, Serialize};
use std::cmp::Eq;
use std::iter::Peekable;
use std::path;
use std::path::{Path, PathBuf};
use std::{fs, io, path};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@ -230,29 +229,6 @@ impl EtcTree {
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)]

View file

@ -1,24 +1,25 @@
use anyhow::{Context, Result};
use anyhow::Context;
use im::{HashMap, HashSet};
use serde::{Deserialize, Serialize};
use std::fs::DirBuilder;
use std::path::{self, Path, PathBuf};
use std::time::Duration;
use std::{fs, io, str};
use crate::{
create_link, etc_dir, systemd, StorePath, SERVICES_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR,
};
use super::ActivationResult;
use crate::activate::ActivationError;
use crate::{create_link, etc_dir, systemd, StorePath};
type ServiceActivationResult = ActivationResult<Services>;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ServiceConfig {
pub struct ServiceConfig {
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(
services
.iter()
@ -26,30 +27,39 @@ fn print_services(services: &Services) -> Result<String> {
"\n".to_owned(),
)
.collect();
Ok(out)
out
}
pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
verify_systemd_dir(ephemeral)?;
let old_services = read_saved_services()?;
pub fn activate(
store_path: &StorePath,
old_services: 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...");
let file = fs::File::open(
Path::new(&store_path.store_path)
.join("services")
.join("services.json"),
)?;
)
.map_err(|e| ActivationError::with_partial_result(old_services.clone(), e))?;
let reader = io::BufReader::new(file);
let services: Services = serde_json::from_reader(reader)?;
log::debug!("{}", print_services(&services)?);
let services: Services = serde_json::from_reader(reader)
.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_reload = get_services_to_reload(services.clone(), old_services.clone());
let service_manager = systemd::ServiceManager::new_session()?;
let job_monitor = service_manager.monitor_jobs_init()?;
let service_manager = systemd::ServiceManager::new_session()
.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));
// 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(
&service_manager,
&job_monitor,
stop_services(&service_manager, &services_to_stop)?,
stop_services(&service_manager, &services_to_stop),
&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
// to tell systemd about them.
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 services_to_reload = get_services_to_reload(services, old_services);
let active_targets = get_active_targets(&service_manager)
.map_err(|e| ActivationError::with_partial_result(services.clone(), e))?;
wait_for_jobs(
&service_manager,
&job_monitor,
reload_services(&service_manager, &services_to_reload)?
+ start_units(&service_manager, &active_targets?)?,
reload_services(&service_manager, &services_to_reload)
+ start_units(&service_manager, &active_targets),
&timeout,
)?;
)
.map_err(|e| ActivationError::with_partial_result(services.clone(), e))?;
log::info!("Done");
Ok(())
Ok(services)
}
fn get_active_targets(
service_manager: &systemd::ServiceManager,
) -> Result<Vec<systemd::UnitStatus>> {
) -> anyhow::Result<Vec<systemd::UnitStatus>> {
// We exclude some targets that we do not want to start
let excluded_targets: HashSet<String> =
["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 {
let system_dir = systemd_system_dir(ephemeral);
if system_dir.exists()
@ -177,15 +191,18 @@ fn verify_systemd_dir(ephemeral: bool) -> Result<()> {
Ok(())
}
pub fn deactivate() -> Result<()> {
restore_ephemeral_system_dir()?;
let old_services = read_saved_services()?;
pub fn deactivate(old_services: Services) -> ServiceActivationResult {
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() {
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));
// 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(
&service_manager,
&job_monitor,
stop_services(&service_manager, &old_services)?,
stop_services(&service_manager, &old_services),
&timeout,
)?;
)
// We consider all jobs stopped now..
.map_err(|e| ActivationError::with_partial_result(im::HashMap::new(), e))?;
} else {
log::info!("No services to deactivate.");
}
log::info!("Reloading the systemd daemon...");
service_manager.daemon_reload()?;
serialise_saved_services(&HashMap::new())?;
service_manager
.daemon_reload()
.map_err(|e| ActivationError::with_partial_result(im::HashMap::new(), e))?;
log::info!("Done");
Ok(())
Ok(im::HashMap::new())
}
// 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,
// and we recreate it if needed.
// 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);
if !ephemeral_systemd_system_dir.exists() {
if ephemeral_systemd_system_dir.is_symlink() {
@ -224,43 +243,7 @@ fn restore_ephemeral_system_dir() -> Result<()> {
Ok(())
}
// TODO: we should probably lock this file to avoid concurrent writes
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>> {
fn stop_services(service_manager: &systemd::ServiceManager, services: &Services) -> HashSet<JobId> {
for_each_unit(
|s| service_manager.stop_unit(s),
convert_services(services),
@ -271,7 +254,7 @@ fn stop_services(
fn reload_services(
service_manager: &systemd::ServiceManager,
services: &Services,
) -> Result<HashSet<JobId>> {
) -> HashSet<JobId> {
for_each_unit(
|s| service_manager.reload_unit(s),
convert_services(services),
@ -282,7 +265,7 @@ fn reload_services(
fn start_units(
service_manager: &systemd::ServiceManager,
units: &[systemd::UnitStatus],
) -> Result<HashSet<JobId>> {
) -> HashSet<JobId> {
for_each_unit(
|unit| service_manager.start_unit(unit),
convert_units(units),
@ -301,12 +284,12 @@ fn convert_units(units: &[systemd::UnitStatus]) -> 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
F: Fn(&str) -> Result<R>,
F: Fn(&str) -> anyhow::Result<R>,
S: AsRef<[&'a str]>,
{
let successful_services: HashSet<JobId> =
// TODO: do we want to propagate unit failures here in some way?
units
.as_ref()
.iter()
@ -326,10 +309,7 @@ where
log::error!("{e}");
set
}
});
// TODO: do we want to propagate unit failures here in some way?
Ok(successful_services)
})
}
fn wait_for_jobs(
@ -337,7 +317,7 @@ fn wait_for_jobs(
job_monitor: &systemd::JobMonitor,
jobs: HashSet<JobId>,
timeout: &Option<Duration>,
) -> Result<()> {
) -> anyhow::Result<()> {
if !service_manager.monitor_jobs_finish(job_monitor, timeout, jobs)? {
anyhow::bail!("Timeout waiting for systemd jobs");
}

View file

@ -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 GCROOT_PATH: &str = "/nix/var/nix/gcroots/system-manager-current";
pub const SYSTEM_MANAGER_STATE_DIR: &str = "/var/lib/system-manager/state";
pub const SERVICES_STATE_FILE_NAME: &str = "services.json";
pub const ETC_STATE_FILE_NAME: &str = "etc-files.json";
pub const STATE_FILE_NAME: &str = "system-manager-state.json";
pub const SYSTEM_MANAGER_STATIC_NAME: &str = ".system-manager-static";
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]

View file

@ -294,8 +294,8 @@ fn check_root() -> Result<()> {
}
fn handle_toplevel_error<T>(r: Result<T>) -> ExitCode {
if r.is_err() {
log::error!("{:?}", r);
if let Err(e) = r {
log::error!("{:?}", e);
return ExitCode::FAILURE;
}
ExitCode::SUCCESS