switch-to-configuration-ng: various mount-related fixes (#443001)

This commit is contained in:
Will Fancher
2025-10-12 19:27:44 +00:00
committed by GitHub
2 changed files with 85 additions and 23 deletions

View File

@@ -91,6 +91,14 @@ in
virtualisation.fileSystems."/".device = lib.mkForce "auto";
};
automount.configuration = {
virtualisation.fileSystems."/testauto" = {
device = "tmpfs";
fsType = "tmpfs";
options = [ "x-systemd.automount" ];
};
};
swap.configuration.swapDevices = lib.mkVMOverride [
{
device = "/swapfile";
@@ -627,6 +635,17 @@ in
(pkgs.writeText "dbus-reload-dummy" "dbus reload dummy")
];
};
generators.configuration =
{ lib, pkgs, ... }:
{
systemd.generators.simple-generator = pkgs.writeShellScript "simple-generator" ''
${lib.getExe' pkgs.coreutils "cat"} >$1/simple-generated.service <<EOF
[Service]
ExecStart=${lib.getExe' pkgs.coreutils "sleep"} infinity
EOF
'';
};
};
};
@@ -826,9 +845,21 @@ in
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
# add an automount
out = switch_to_specialisation("${machine}", "automount")
assert_lacks(out, "stopping the following units:")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_contains(out, "the following new units were started: testauto.automount\n")
# remove the automount
out = switch_to_specialisation("${machine}", "")
assert_contains(out, "stopping the following units: testauto.automount\n")
assert_lacks(out, "reloading the following units:")
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
with subtest("swaps"):
switch_to_specialisation("${machine}", "")
# add a swap
out = switch_to_specialisation("${machine}", "swap")
assert_lacks(out, "stopping the following units:")
@@ -1520,5 +1551,13 @@ in
assert_lacks(out, "\nrestarting the following units:")
assert_lacks(out, "\nstarting the following units:")
assert_lacks(out, "the following new units were started:")
with subtest("generators"):
out = switch_to_specialisation("${machine}", "generators")
# The service is not started by anything, so we start it manually
machine.succeed("systemctl start simple-generated.service && systemctl is-active simple-generated.service")
out = switch_to_specialisation("${machine}", "")
# Assert switching to a different generation doesn't touch units created by generators
machine.succeed("systemctl is-active simple-generated.service")
'';
}

View File

@@ -197,17 +197,17 @@ fn required_env(var: &str) -> anyhow::Result<String> {
std::env::var(var).with_context(|| format!("missing required environment variable ${var}"))
}
#[derive(Debug)]
struct UnitState {
struct UnitState<'a, 'b> {
state: String,
substate: String,
proxy: Proxy<'a, &'b LocalConnection>,
}
// Asks the currently running systemd instance via dbus which units are active. Returns a hash
// where the key is the name of each unit and the value a hash of load, state, substate.
fn get_active_units(
systemd_manager: &Proxy<'_, &LocalConnection>,
) -> Result<HashMap<String, UnitState>> {
fn get_active_units<'a, 'b>(
systemd_manager: &Proxy<'a, &'b LocalConnection>,
) -> Result<HashMap<String, UnitState<'a, 'b>>> {
let units = systemd_manager
.list_units_by_patterns(Vec::new(), Vec::new())
.context("Failed to list systemd units")?;
@@ -219,29 +219,36 @@ fn get_active_units(
id,
_description,
_load_state,
active_state,
sub_state,
state,
substate,
following,
_unit_path,
unit_path,
_job_id,
_job_type,
_job_path,
)| {
if following.is_empty() && active_state != "inactive" {
Some((id, active_state, sub_state))
let proxy = systemd_manager.connection.with_proxy(
"org.freedesktop.systemd1",
unit_path,
Duration::from_millis(5000),
);
if following.is_empty() && state != "inactive" {
Some((
id,
UnitState {
state,
substate,
proxy,
},
))
} else {
None
}
},
)
.fold(HashMap::new(), |mut acc, (id, active_state, sub_state)| {
acc.insert(
id,
UnitState {
state: active_state,
substate: sub_state,
},
);
.fold(HashMap::new(), |mut acc, (id, unit_state)| {
acc.insert(id, unit_state);
acc
}))
@@ -811,9 +818,12 @@ fn parse_fstab(fstab: impl BufRead) -> (HashMap<String, Filesystem>, HashMap<Str
// Converts a path to the name of a systemd mount unit that would be responsible for mounting this
// path.
fn path_to_unit_name(bin_path: &Path, path: &str) -> String {
fn path_to_unit_name(bin_path: &Path, path: &str, is_automount: bool) -> String {
let Ok(output) = std::process::Command::new(bin_path.join("systemd-escape"))
.arg("--suffix=mount")
.arg(format!(
"--suffix={}",
if is_automount { "automount" } else { "mount" }
))
.arg("-p")
.arg(path)
.output()
@@ -823,7 +833,7 @@ fn path_to_unit_name(bin_path: &Path, path: &str) -> String {
};
let Ok(unit) = String::from_utf8(output.stdout) else {
eprintln!("Unable to convert systemd-espape output to valid UTF-8");
eprintln!("Unable to convert systemd-escape output to valid UTF-8");
die();
};
@@ -1189,6 +1199,17 @@ won't take effect until you reboot the system.
.context("Invalid regex for matching systemd unit names")?;
for (unit, unit_state) in &current_active_units {
// Don't touch units not explicitly written by NixOS (e.g. units created by generators in
// /run/systemd/generator*)
if !unit_state
.proxy
.get("org.freedesktop.systemd1.Unit", "FragmentPath")
.map(|fragment_path: String| fragment_path.starts_with("/etc/systemd/system"))
.unwrap_or_default()
{
continue;
}
let current_unit_file = Path::new("/etc/systemd/system").join(unit);
let new_unit_file = toplevel.join("etc/systemd/system").join(unit);
@@ -1319,8 +1340,10 @@ won't take effect until you reboot the system.
.unwrap_or_default();
for (mountpoint, current_filesystem) in current_filesystems {
let is_automount = current_filesystem.options.contains("x-systemd.automount");
// Use current version of systemctl binary before daemon is reexeced.
let unit = path_to_unit_name(&current_system_bin, &mountpoint);
let unit = path_to_unit_name(&current_system_bin, &mountpoint, is_automount);
if let Some(new_filesystem) = new_filesystems.get(&mountpoint) {
if current_filesystem.fs_type != new_filesystem.fs_type
|| current_filesystem.device != new_filesystem.device