This commit is contained in:
R-VdP 2023-03-08 00:24:10 +01:00
parent 0df7ab21e5
commit cf91b29724
No known key found for this signature in database
8 changed files with 357 additions and 59 deletions

70
Cargo.lock generated
View file

@ -43,9 +43,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.4"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
dependencies = [
"bitflags",
"clap_derive",
@ -58,9 +58,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.1.0"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
dependencies = [
"heck",
"proc-macro-error",
@ -71,9 +71,9 @@ dependencies = [
[[package]]
name = "clap_lex"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
dependencies = [
"os_str_bytes",
]
@ -89,6 +89,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "env_logger"
version = "0.10.0"
@ -131,9 +137,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "humantime"
@ -143,9 +149,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "io-lifetimes"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
dependencies = [
"libc",
"windows-sys",
@ -153,9 +159,9 @@ dependencies = [
[[package]]
name = "is-terminal"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
dependencies = [
"hermit-abi",
"io-lifetimes",
@ -164,10 +170,19 @@ dependencies = [
]
[[package]]
name = "itoa"
version = "1.0.5"
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "libc"
@ -230,9 +245,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.17.0"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "os_str_bytes"
@ -313,9 +328,9 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustix"
version = "0.36.8"
version = "0.36.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
dependencies = [
"bitflags",
"errno",
@ -327,9 +342,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "serde"
@ -353,9 +368,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.92"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
@ -376,9 +391,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
@ -393,6 +408,7 @@ dependencies = [
"clap",
"dbus",
"env_logger",
"itertools",
"log",
"nix",
"serde",
@ -410,9 +426,9 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.6"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "version_check"

View file

@ -14,6 +14,7 @@ anyhow = "1.0.68"
clap = { version = "4.1.4", features = ["derive"] }
dbus = "0.9.7"
env_logger = "0.10.0"
itertools = "0.10.5"
log = "0.4.17"
nix = "0.26.2"
serde = { version = "1.0.152", features = ["derive"] }

View file

@ -104,15 +104,12 @@ in
${system-manager}/bin/system-manager deactivate "$@"
'';
linkFarmEntryFromDrv = drv: {
name = drv.name;
path = drv;
};
linkFarmBinEntryFromDrv = drv: {
name = "bin/${drv.name}";
linkFarmNestedEntryFromDrv = dirs: drv: {
name = lib.concatStringsSep "/" (dirs ++ [ "${drv.name}" ]);
path = drv;
};
linkFarmEntryFromDrv = linkFarmNestedEntryFromDrv [ ];
linkFarmBinEntryFromDrv = linkFarmNestedEntryFromDrv [ "bin" ];
in
returnIfNoAssertions (
pkgs.linkFarm "system-manager" [

View file

@ -54,6 +54,20 @@ let
group = "root";
};
"a/nested/example/foo3" = {
text = "boo!";
mode = "0764";
user = "root";
group = "root";
};
"a/nested/example2/foo3" = {
text = "boo!";
mode = "0764";
user = "root";
group = "root";
};
out-of-store = {
source = "/run/systemd/system/";
};

View file

@ -11,6 +11,8 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
log::info!("Running in ephemeral mode");
}
// TODO we probably need to first deactivate left-over files and services
// before we start putting in place the new ones.
etc_files::activate(store_path, ephemeral)?;
services::activate(store_path, ephemeral)?;
Ok(())

View file

@ -1,14 +1,19 @@
use anyhow::Result;
use anyhow::{anyhow, Result};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::DirBuilder;
use std::fs::{DirBuilder, Permissions};
use std::os::unix::prelude::PermissionsExt;
use std::path;
use std::path::{Path, PathBuf};
use std::{fs, io};
use crate::{create_link, create_store_link, StorePath};
use crate::{
create_link, create_store_link, remove_dir, remove_file, remove_link, StorePath,
ETC_STATE_FILE_NAME, SYSTEM_MANAGER_STATE_DIR,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EtcFile {
source: StorePath,
@ -22,13 +27,77 @@ struct EtcFile {
type EtcFiles = HashMap<String, EtcFile>;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EtcFilesConfig {
entries: EtcFiles,
static_env: StorePath,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EtcStateInfo {
status: EtcFileStatus,
// 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, EtcStateInfo>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum EtcFileStatus {
Managed,
Unmanaged,
}
impl EtcFileStatus {
fn merge(&self, other: &Self) -> Self {
use EtcFileStatus::*;
match (self, other) {
(Unmanaged, Unmanaged) => Unmanaged,
_ => Managed,
}
}
}
impl EtcStateInfo {
fn new() -> Self {
Self::with_status(EtcFileStatus::Unmanaged)
}
fn with_status(status: EtcFileStatus) -> Self {
Self {
status,
nested: HashMap::new(),
}
}
// TODO unit tests
fn register_managed_path(mut self, components: &[String], path: String) -> Self {
let state = components.iter().fold(&mut self, |state, component| {
if !state.nested.contains_key(component) {
let new_state = Self::new();
state.nested.insert(component.to_owned(), new_state);
}
state.nested.get_mut(component).unwrap()
});
state
.nested
.insert(path, Self::with_status(EtcFileStatus::Managed));
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct CreatedEtcFile {
path: PathBuf,
}
pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
log::info!("Reading etc file definitions...");
let file = fs::File::open(Path::new(&store_path.store_path).join("etcFiles/etcFiles.json"))?;
@ -40,48 +109,205 @@ pub fn activate(store_path: &StorePath, ephemeral: bool) -> Result<()> {
log::debug!("Storing /etc entries in {}", etc_dir.display());
DirBuilder::new().recursive(true).create(&etc_dir)?;
create_store_link(
&config.static_env,
etc_dir.join(".system-manager-static").as_path(),
)?;
config
.entries
.into_iter()
.try_for_each(|(name, entry)| create_etc_link(&name, &entry, &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 register the crated files (including .system-manager-static) in a
// state file, and clean up old files from the previous generation
// TODO remove all paths that are in the state file from the previous generation
// and not in the current one.
let state = read_created_files()?;
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(())
}
// TODO we need to record in a state file which files we created and then remove them
pub fn deactivate() -> Result<()> {
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");
Ok(())
}
fn create_etc_link(name: &str, entry: &EtcFile, etc_dir: &Path) -> Result<()> {
fn remove_created_file<P>(created_file: &P, root_dir: &str) -> Result<()>
where
P: AsRef<Path>,
{
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 {
if let Some(parent) = path.parent() {
if parent
.file_name()
.filter(|name| &root_dir != name)
.is_some()
{
log::debug!("Recursing up into directory {}...", parent.display());
return remove_created_file(&parent, root_dir);
}
log::debug!("Reached root dir: {}", parent.display());
}
}
Ok(())
}
fn create_etc_links<'a, E>(entries: E, etc_dir: &Path, state: EtcStateInfo) -> EtcStateInfo
where
E: Iterator<Item = &'a EtcFile>,
{
entries.fold(state, |state, entry| {
let (new_state, status) = create_etc_link(entry, etc_dir, state);
match status {
Ok(_) => new_state,
Err(e) => {
log::error!("Error while creating file in {}: {e}", etc_dir.display());
new_state
}
}
})
}
fn create_etc_link(
entry: &EtcFile,
etc_dir: &Path,
state: EtcStateInfo,
) -> (EtcStateInfo, Result<()>) {
if entry.mode == "symlink" {
if let Some(path::Component::Normal(link_target)) =
entry.target.components().into_iter().next()
{
create_link(
let link_name = etc_dir.join(link_target);
match create_link(
Path::new(".")
.join(".system-manager-static")
.join("etc")
.join(link_target)
.as_path(),
etc_dir.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 {
anyhow::bail!("Cannot create link for this entry ({name}).")
(
state,
Err(anyhow!("Cannot create link: {}", entry.target.display(),)),
)
}
} else {
Ok(())
let dirbuilder = DirBuilder::new();
let target_path = etc_dir.join(entry.target.as_path());
// 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(|_| {
copy_file(
entry
.source
.store_path
.join("etc")
.join(&entry.target)
.as_path(),
&target_path,
&entry.mode,
)
}) {
Ok(_) => (
new_state.register_managed_path(
&created_paths,
target_path
.file_name()
.unwrap()
.to_string_lossy()
.into_owned(),
),
Ok(()),
),
e => (new_state, e),
}
}
}
fn copy_file(source: &Path, target: &Path, mode: &str) -> Result<()> {
fs::copy(source, target)?;
let mode_int = u32::from_str_radix(mode, 8).map_err(anyhow::Error::from)?;
fs::set_permissions(target, Permissions::from_mode(mode_int))?;
Ok(())
}
fn etc_dir(ephemeral: bool) -> PathBuf {
if ephemeral {
Path::new("/run").join("etc")
@ -89,3 +315,35 @@ fn etc_dir(ephemeral: bool) -> PathBuf {
Path::new("/etc").to_path_buf()
}
}
fn serialise_created_files(created_files: &EtcStateInfo) -> Result<()> {
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_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, created_files)?;
Ok(())
}
fn read_created_files() -> Result<EtcStateInfo> {
let state_file = Path::new(SYSTEM_MANAGER_STATE_DIR).join(ETC_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(created_files) => return Ok(created_files),
Err(e) => {
log::error!("Error reading the state file, ignoring.");
log::error!("{:?}", e);
}
}
}
Ok(EtcStateInfo::new())
}

View file

@ -87,8 +87,6 @@ pub fn deactivate() -> Result<()> {
let old_linked_services = read_linked_services()?;
log::debug!("{:?}", old_linked_services);
serialise_linked_services(&HashMap::new())?;
let service_manager = systemd::ServiceManager::new_session()?;
let timeout = Some(Duration::from_secs(30));
@ -102,6 +100,8 @@ pub fn deactivate() -> Result<()> {
log::info!("Reloading the systemd daemon...");
service_manager.daemon_reload()?;
serialise_linked_services(&HashMap::new())?;
log::info!("Done");
Ok(())
}

View file

@ -14,7 +14,7 @@ const PROFILE_NAME: &str = "system-manager";
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 ETC_STATE_FILE_NAME: &str = "etc-files.json";
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(from = "String", into = "String", rename_all = "camelCase")]
@ -77,6 +77,16 @@ fn remove_link(from: &Path) -> Result<()> {
}
}
fn remove_file(from: &Path) -> Result<()> {
log::info!("Removing file: {}", from.display());
fs::remove_file(from).map_err(anyhow::Error::from)
}
fn remove_dir(from: &Path) -> Result<()> {
log::info!("Removing directory: {}", from.display());
fs::remove_dir(from).map_err(anyhow::Error::from)
}
pub fn compose<A, B, C, G, F>(f: F, g: G) -> impl Fn(A) -> C
where
F: Fn(B) -> C,