switch-to-configuration-ng: wait for `NameOwnerChanged` after systemd `Reexecute` (#442756)

Changed files
+107 -22
pkgs
by-name
sw
switch-to-configuration-ng
+4
pkgs/by-name/sw/switch-to-configuration-ng/src/build.rs
···
let out_path = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
+
let fdo_dbus_code = code_for_dbus_xml("org.freedesktop.DBus.xml");
+
let mut file = std::fs::File::create(out_path.join("fdo_dbus.rs")).unwrap();
+
file.write_all(fdo_dbus_code.as_bytes()).unwrap();
+
let systemd_manager_code =
code_for_dbus_xml(systemd_dbus_interface_dir.join("org.freedesktop.systemd1.Manager.xml"));
let mut file = std::fs::File::create(out_path.join("systemd_manager.rs")).unwrap();
+12
pkgs/by-name/sw/switch-to-configuration-ng/src/org.freedesktop.DBus.xml
···
+
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+
"https://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+
<node>
+
<!-- From https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-messages. -->
+
<interface name="org.freedesktop.DBus">
+
<signal name="NameOwnerChanged">
+
<arg type="s" direction="out" name="name"/>
+
<arg type="s" direction="out" name="old_owner"/>
+
<arg type="s" direction="out" name="new_owner"/>
+
</signal>
+
</interface>
+
</node>
+91 -22
pkgs/by-name/sw/switch-to-configuration-ng/src/src/main.rs
···
use anyhow::{anyhow, bail, Context, Result};
use dbus::{
blocking::{stdintf::org_freedesktop_dbus::Properties, LocalConnection, Proxy},
+
channel::Sender,
+
strings::{BusName, Interface, Member},
Message,
};
use glob::glob;
···
use regex::Regex;
use syslog::Facility;
+
mod fdo_dbus {
+
#![allow(non_upper_case_globals)]
+
#![allow(non_camel_case_types)]
+
#![allow(non_snake_case)]
+
#![allow(unused)]
+
#![allow(clippy::all)]
+
include!(concat!(env!("OUT_DIR"), "/fdo_dbus.rs"));
+
}
+
mod systemd_manager {
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
···
include!(concat!(env!("OUT_DIR"), "/logind_manager.rs"));
}
-
use crate::systemd_manager::OrgFreedesktopSystemd1Manager;
+
use crate::{
+
fdo_dbus::OrgFreedesktopDBusNameOwnerChanged, systemd_manager::OrgFreedesktopSystemd1Manager,
+
};
use crate::{
logind_manager::OrgFreedesktopLogin1Manager,
systemd_manager::{
···
}
}
-
fn new_dbus_proxies(
-
conn: &LocalConnection,
-
) -> (Proxy<'_, &LocalConnection>, Proxy<'_, &LocalConnection>) {
-
(
-
conn.with_proxy(
-
"org.freedesktop.systemd1",
-
"/org/freedesktop/systemd1",
-
Duration::from_millis(10000),
-
),
-
conn.with_proxy(
-
"org.freedesktop.login1",
-
"/org/freedesktop/login1",
-
Duration::from_millis(10000),
-
),
+
fn fdo_dbus_proxy(conn: &LocalConnection) -> Proxy<'_, &LocalConnection> {
+
conn.with_proxy(
+
"org.freedesktop.DBus",
+
"/org/freedesktop/DBus",
+
Duration::from_millis(500),
+
)
+
}
+
+
fn systemd1_proxy(conn: &LocalConnection) -> Proxy<'_, &LocalConnection> {
+
conn.with_proxy(
+
"org.freedesktop.systemd1",
+
"/org/freedesktop/systemd1",
+
Duration::from_millis(10000),
+
)
+
}
+
+
fn login1_proxy(conn: &LocalConnection) -> Proxy<'_, &LocalConnection> {
+
conn.with_proxy(
+
"org.freedesktop.login1",
+
"/org/freedesktop/login1",
+
Duration::from_millis(10000),
)
}
···
}
}
+
fn reexecute_systemd_manager(
+
dbus_conn: &LocalConnection,
+
fdo_dbus: &Proxy<'_, &LocalConnection>,
+
) -> anyhow::Result<()> {
+
let reexecute_done = Rc::new(RefCell::new(false));
+
let _reexecute_done = reexecute_done.clone();
+
let owner_changed_token = fdo_dbus
+
.match_signal(
+
move |signal: OrgFreedesktopDBusNameOwnerChanged, _: &LocalConnection, _: &Message| {
+
if signal.name.as_str() == "org.freedesktop.systemd1" {
+
*_reexecute_done.borrow_mut() = true;
+
}
+
+
true
+
},
+
)
+
.context("Failed to add signal match for DBus name owner changes")?;
+
+
let bus_name = BusName::from("org.freedesktop.systemd1");
+
let object_path = dbus::Path::from("/org/freedesktop/systemd1");
+
let interface = Interface::new("org.freedesktop.systemd1.Manager")
+
.expect("the org.freedesktop.systemd1.Manager interface name should be valid");
+
let method_name = Member::new("Reexecute").expect("the Reexecute method name should be valid");
+
+
// Systemd does not reply to the Reexecute method.
+
let _serial = dbus_conn
+
.send(Message::method_call(
+
&bus_name,
+
&object_path,
+
&interface,
+
&method_name,
+
))
+
.map_err(|_err| anyhow!("Failed to send org.freedesktop.systemd1.Manager.Reexecute"))?;
+
+
log::debug!("waiting for systemd to finish reexecuting");
+
while !*reexecute_done.borrow() {
+
_ = dbus_conn
+
.process(Duration::from_secs(500))
+
.context("Failed to process dbus messages")?;
+
}
+
+
dbus_conn
+
.remove_match(owner_changed_token)
+
.context("Failed to remove jobs token")?;
+
+
Ok(())
+
}
+
/// Performs switch-to-configuration functionality for a single non-root user
fn do_user_switch(parent_exe: String) -> anyhow::Result<()> {
if Path::new(&parent_exe)
···
}
let dbus_conn = LocalConnection::new_session().context("Failed to open dbus connection")?;
-
let (systemd, _) = new_dbus_proxies(&dbus_conn);
+
let fdo_dbus = fdo_dbus_proxy(&dbus_conn);
+
let systemd = systemd1_proxy(&dbus_conn);
+
+
reexecute_systemd_manager(&dbus_conn, &fdo_dbus)?;
let nixos_activation_done = Rc::new(RefCell::new(false));
let _nixos_activation_done = nixos_activation_done.clone();
···
},
)
.context("Failed to add signal match for systemd removed jobs")?;
-
-
// The systemd user session seems to not send a Reloaded signal, so we don't have anything to
-
// wait on here.
-
_ = systemd.reexecute();
systemd
.restart_unit("nixos-activation.service", "replace")
···
let mut units_to_reload = map_from_list_file(RELOAD_LIST_FILE);
let dbus_conn = LocalConnection::new_system().context("Failed to open dbus connection")?;
-
let (systemd, logind) = new_dbus_proxies(&dbus_conn);
+
let fdo_dbus = fdo_dbus_proxy(&dbus_conn);
+
let systemd = systemd1_proxy(&dbus_conn);
+
let logind = login1_proxy(&dbus_conn);
let submitted_jobs = Rc::new(RefCell::new(HashMap::new()));
let finished_jobs = Rc::new(RefCell::new(HashMap::new()));
···
// just in case the new one has trouble communicating with the running pid 1.
if restart_systemd {
eprintln!("restarting systemd...");
-
_ = systemd.reexecute(); // we don't get a dbus reply here
+
reexecute_systemd_manager(&dbus_conn, &fdo_dbus)?;
log::debug!("waiting for systemd restart to finish");
while !*systemd_reload_status.borrow() {