Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 90 additions & 21 deletions crates/system-manager-engine/src/activate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ pub(crate) mod services;
mod tmp_files;
pub(crate) mod users;

use anyhow::Result;
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
use serde_json::error::Category;
use std::collections::HashSet;
use std::fs::DirBuilder;
use std::io::Seek;
use std::path::{Path, PathBuf};
use std::{fs, io, process};
use thiserror::Error;

use crate::activate::etc_files::FileTree;
use crate::activate::etc_files::etc_tree::StateV0;
use crate::{StorePath, STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR};

#[derive(Error, Debug)]
Expand All @@ -33,30 +36,91 @@ impl<R> ActivationError<R> {

pub type ActivationResult<R> = Result<R, ActivationError<R>>;

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct State {
pub(crate) file_tree: FileTree,
pub enum FileStatus {
Managed,
ManagedWithBackup,
}

type EtcTree = HashSet<PathBuf>;
type BackedUpFiles = HashSet<PathBuf>;
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EtcFilesState {
pub files: EtcTree,
pub backed_up_files: BackedUpFiles,
}

impl EtcFilesState {
pub fn contains(&self, path: &Path) -> bool {
self.files.contains(path) || self.backed_up_files.contains(path)
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StateV1 {
pub(crate) file_tree: EtcFilesState,
pub(crate) services: services::Services,
pub(crate) version: u32,
}

impl Default for StateV1 {
fn default() -> Self {
Self {
file_tree: EtcFilesState::default(),
services: services::Services::default(),
version: 1,
}
}
}

impl State {
impl StateV1 {
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())
})
let mut reader = io::BufReader::new(fs::File::open(state_file)?);
// if state is v1
let rv1: serde_json::Result<StateV1> = serde_json::from_reader(&mut reader);
match rv1 {
Ok(v1) => Ok(v1),
Err(e) => {
// State might be v0. Let's try to parse it.
if e.classify() == Category::Data {
reader.rewind()?;
let filetree: StateV0 =
serde_json::from_reader(&mut reader).map_err(|e| {
anyhow!(
"Cannot parse state, it doesn't match any supported format: {}",
e
)
})?;
log::info!("The state is in the V0 format. Migrating it to the V1 format.");
// Backup the old state, just in case. Better be safe than sorry.
let mut backup_path = state_file.to_owned();
backup_path.add_extension("v0back");
log::info!(
"Create a backup of the v0 state at {}.",
&backup_path.display()
);
fs::copy(state_file, backup_path)?;
Ok(filetree.into())
Comment thread
picnoir marked this conversation as resolved.
} else {
// We don't know what that state is.
Err(anyhow!("Unexpected serde_json error: {}", e))
}
}
}
// else parse v0 then migrate
} else {
Ok(Self::default())
}
}

pub fn write_to_file(&self, state_file: &Path) -> Result<()> {
log::info!("Writing state info into file: {}", state_file.display());
log::debug!("State: {:?}", self);
let writer = io::BufWriter::new(fs::File::create(state_file)?);

serde_json::to_writer(writer, self)?;
Expand All @@ -76,7 +140,7 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
}

let state_file = &get_state_file()?;
let old_state = State::from_file(state_file)?;
let old_state = StateV1::from_file(state_file)?;

log::info!("Activating etc files...");

Expand All @@ -91,7 +155,7 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
}

log::info!("Activating tmp files...");
let tmp_result = tmp_files::activate(&etc_tree);
let tmp_result = tmp_files::activate(&etc_tree.files);
if let Err(e) = &tmp_result {
log::error!("Error during activation of tmp files");
log::error!("{e}");
Expand All @@ -101,15 +165,17 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {

log::info!("Activating systemd services...");
let final_state = match services::activate(store_path, old_state.services, ephemeral) {
Ok(services) => State {
Ok(services) => StateV1 {
file_tree: etc_tree,
services,
version: 1,
},
Err(ActivationError::WithPartialResult { result, source }) => {
log::error!("Error during activation: {source:?}");
State {
StateV1 {
file_tree: etc_tree,
services: result,
version: 1,
}
}
};
Expand All @@ -123,7 +189,8 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
}
Err(ActivationError::WithPartialResult { result, source }) => {
log::error!("Error during activation: {source:?}");
let final_state = State {
log::debug!("Resulting file tree: {:?}", result);
let final_state = StateV1 {
file_tree: result,
..old_state
};
Expand All @@ -145,30 +212,32 @@ pub fn prepopulate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
}

let state_file = &get_state_file()?;
let old_state = State::from_file(state_file)?;
let old_state = StateV1::from_file(state_file)?;

log::info!("Activating etc files...");

match etc_files::activate(store_path, old_state.file_tree, ephemeral) {
Ok(etc_tree) => {
log::info!("Registering systemd services...");
match services::get_active_services(store_path, old_state.services) {
Ok(services) => State {
Ok(services) => StateV1 {
file_tree: etc_tree,
services,
version: 1,
},
Err(ActivationError::WithPartialResult { result, source }) => {
log::error!("Error during activation: {source:?}");
State {
StateV1 {
file_tree: etc_tree,
services: result,
version: 1,
}
}
}
}
Err(ActivationError::WithPartialResult { result, source }) => {
log::error!("Error during activation: {source:?}");
State {
StateV1 {
file_tree: result,
..old_state
}
Expand Down
Loading