Skip to content

Commit 4ef468f

Browse files
committed
daemon: Add socket activation via /run/rpm-ostreed.socket
For historical reasons, the daemon ends up doing a lot of initialization before even claiming the DBus name. For example, it calls `ostree_sysroot_load()`. We also end up scanning the RPM database, and actually parse all the GPG keys in `/etc/pki/rpm-gpg` etc. This causes two related problems: - By doing all this work before claiming the bus name, we race against the (pretty low) DBus service timeout of 25s. - If something hard fails at initialization, the client can't easily see the error because it just appears in the systemd journal. The client will just see a service timeout. The solution to this is to adopt systemd socket activation. There's a new `rpm-ostreed.socket` and the daemon can be activated that way. The client (when run as root, the socket is only accessible to root right now) connects, which will activate the daemon and attempt initialization - without claiming the DBus name yet. If something goes wrong here, the daemon will reply to the client that activated it with the error, and then also exit with failure. On success, everything continues as normal, including claiming the DBus name. Note that this also logically replaces the code that does explicit `systemctl start rpm-ostreed` invocations. After this patch: ``` $ systemctl stop rpm-ostreed $ umount /boot $ rpm-ostree status error: Couldn't start daemon: Error setting up sysroot: loading sysroot: Unexpected state: /run/ostree-booted found, but no /boot/loader directory ```
1 parent 7993c6f commit 4ef468f

15 files changed

+418
-28
lines changed

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ camino = "1.0.9"
4949
cap-std-ext = "0.26"
5050
cap-std = { version = "0.25", features = ["fs_utf8"] }
5151
# Explicitly force on libc
52-
rustix = { version = "0.35", features = ["use-libc"] }
52+
rustix = { version = "0.35", features = ["use-libc", "net"] }
5353
cap-primitives = "0.25.2"
5454
cap-tempfile = "0.25.2"
5555
chrono = { version = "0.4.21", features = ["serde"] }
@@ -123,6 +123,8 @@ lto = "thin"
123123
# Note: If you add a feature here, you also probably want to update utils.rs:get_features()
124124
fedora-integration = []
125125
rhsm = ["libdnf-sys/rhsm"]
126+
# Enable hard requirement on `rpm-ostreed.socket`; requires https://bugzilla.redhat.com/show_bug.cgi?id=2110012
127+
client-socket = []
126128
bin-unit-tests = []
127129
# ASAN+UBSAN
128130
sanitizers = []

Makefile-daemon.am

+7-3
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,18 @@ systemdunit_service_in_files = \
6363
$(NULL)
6464

6565
systemdunit_service_files = $(systemdunit_service_in_files:.service.in=.service)
66-
systemdunit_timer_files = \
66+
systemdunit_other_files = \
6767
$(srcdir)/src/daemon/rpm-ostreed-automatic.timer \
6868
$(srcdir)/src/daemon/rpm-ostree-countme.timer \
6969
$(NULL)
7070

71+
if CLIENT_SOCKET
72+
systemdunit_other_files += $(srcdir)/src/daemon/rpm-ostreed.socket
73+
endif
74+
7175
systemdunit_DATA = \
7276
$(systemdunit_service_files) \
73-
$(systemdunit_timer_files) \
77+
$(systemdunit_other_files) \
7478
$(NULL)
7579

7680
systemdunitdir = $(prefix)/lib/systemd/system/
@@ -110,7 +114,7 @@ EXTRA_DIST += \
110114
$(sysconf_DATA) \
111115
$(service_in_files) \
112116
$(systemdunit_service_in_files) \
113-
$(systemdunit_timer_files) \
117+
$(systemdunit_other_files) \
114118
$(NULL)
115119

116120
CLEANFILES += \

configure.ac

+8
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ AC_ARG_ENABLE(featuresrs,
6969
AS_HELP_STRING([--enable-featuresrs],
7070
[Rust features, see Cargo.toml for more information]),,
7171
[enable_featuresrs=])
72+
73+
AC_ARG_ENABLE(client-socket,
74+
AS_HELP_STRING([--enable-client-socket],
75+
[(default: no)]),,
76+
[enable_client_socket=no])
77+
AS_IF([test x$enable_client_socket = xyes], [enable_featuresrs="$enable_featuresrs client-socket"])
78+
AM_CONDITIONAL(CLIENT_SOCKET, [echo $enable_featuresrs | grep -q 'client-socket'])
79+
7280
AC_SUBST([RUST_FEATURES], $enable_featuresrs)
7381

7482
# Initialize libtool

packaging/rpm-ostree.spec.in

+8-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ BuildRequires: rust
3434
%bcond_with rhsm
3535
%endif
3636

37+
# https://bugzilla.redhat.com/show_bug.cgi?id=2110012
38+
%if 0%{?fedora} >= 37
39+
%bcond_without client_socket
40+
%else
41+
%bcond_with client_socket
42+
%endif
43+
3744
# RHEL (8,9) doesn't ship zchunk today. Keep this in sync
3845
# with libdnf: https://gitlab.com/redhat/centos-stream/rpms/libdnf/-/blob/762f631e36d1e42c63a794882269d26c156b68c1/libdnf.spec#L45
3946
%if 0%{?rhel}
@@ -179,7 +186,7 @@ env NOCONFIGURE=1 ./autogen.sh
179186
export RUSTFLAGS="%{build_rustflags}"
180187
%endif
181188
%configure --disable-silent-rules --enable-gtk-doc %{?rpmdb_default} %{?with_sanitizers:--enable-sanitizers} %{?with_bin_unit_tests:--enable-bin-unit-tests} \
182-
%{?with_rhsm:--enable-featuresrs=rhsm}
189+
%{?with_rhsm:--enable-featuresrs=rhsm} %{?with_client_socket:--enable-client-socket}
183190

184191
%make_build
185192

rpmostree-cxxrs.cxx

+55
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "rpmostree-package-variants.h"
99
#include "rpmostree-rpm-util.h"
1010
#include "rpmostree-util.h"
11+
#include "rpmostreed-daemon.hpp"
1112
#include "rpmostreemain.h"
1213
#include "src/libpriv/rpmostree-cxxrs-prelude.h"
1314
#include <algorithm>
@@ -2167,6 +2168,10 @@ extern "C"
21672168
::rust::Str opt_deploy_id, ::rust::Str opt_os_name,
21682169
::rpmostreecxx::OstreeDeployment **return$) noexcept;
21692170

2171+
::rust::repr::PtrLen rpmostreecxx$cxxbridge1$daemon_main (bool debug) noexcept;
2172+
2173+
void rpmostreecxx$cxxbridge1$daemon_terminate () noexcept;
2174+
21702175
::rust::repr::PtrLen rpmostreecxx$cxxbridge1$daemon_sanitycheck_environment (
21712176
const ::rpmostreecxx::OstreeSysroot &sysroot) noexcept;
21722177

@@ -2933,6 +2938,40 @@ extern "C"
29332938
return throw$;
29342939
}
29352940

2941+
::rust::repr::PtrLen
2942+
rpmostreecxx$cxxbridge1$daemon_init_inner (bool debug) noexcept
2943+
{
2944+
void (*daemon_init_inner$) (bool) = ::rpmostreecxx::daemon_init_inner;
2945+
::rust::repr::PtrLen throw$;
2946+
::rust::behavior::trycatch (
2947+
[&] {
2948+
daemon_init_inner$ (debug);
2949+
throw$.ptr = nullptr;
2950+
},
2951+
[&] (const char *catch$) noexcept {
2952+
throw$.len = ::std::strlen (catch$);
2953+
throw$.ptr = const_cast<char *> (::cxxbridge1$exception (catch$, throw$.len));
2954+
});
2955+
return throw$;
2956+
}
2957+
2958+
::rust::repr::PtrLen
2959+
rpmostreecxx$cxxbridge1$daemon_main_inner () noexcept
2960+
{
2961+
void (*daemon_main_inner$) () = ::rpmostreecxx::daemon_main_inner;
2962+
::rust::repr::PtrLen throw$;
2963+
::rust::behavior::trycatch (
2964+
[&] {
2965+
daemon_main_inner$ ();
2966+
throw$.ptr = nullptr;
2967+
},
2968+
[&] (const char *catch$) noexcept {
2969+
throw$.len = ::std::strlen (catch$);
2970+
throw$.ptr = const_cast<char *> (::cxxbridge1$exception (catch$, throw$.len));
2971+
});
2972+
return throw$;
2973+
}
2974+
29362975
::rust::repr::PtrLen
29372976
rpmostreecxx$cxxbridge1$client_require_root () noexcept
29382977
{
@@ -3960,6 +3999,22 @@ deployment_get_base (::rpmostreecxx::OstreeSysroot &sysroot, ::rust::Str opt_dep
39603999
return ::std::move (return$.value);
39614000
}
39624001

4002+
void
4003+
daemon_main (bool debug)
4004+
{
4005+
::rust::repr::PtrLen error$ = rpmostreecxx$cxxbridge1$daemon_main (debug);
4006+
if (error$.ptr)
4007+
{
4008+
throw ::rust::impl< ::rust::Error>::error (error$);
4009+
}
4010+
}
4011+
4012+
void
4013+
daemon_terminate () noexcept
4014+
{
4015+
rpmostreecxx$cxxbridge1$daemon_terminate ();
4016+
}
4017+
39634018
void
39644019
daemon_sanitycheck_environment (const ::rpmostreecxx::OstreeSysroot &sysroot)
39654020
{

rpmostree-cxxrs.h

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "rpmostree-package-variants.h"
99
#include "rpmostree-rpm-util.h"
1010
#include "rpmostree-util.h"
11+
#include "rpmostreed-daemon.hpp"
1112
#include "rpmostreemain.h"
1213
#include "src/libpriv/rpmostree-cxxrs-prelude.h"
1314
#include <algorithm>
@@ -1823,6 +1824,10 @@ ::rpmostreecxx::OstreeDeployment *deployment_get_base (::rpmostreecxx::OstreeSys
18231824
::rust::Str opt_deploy_id,
18241825
::rust::Str opt_os_name);
18251826

1827+
void daemon_main (bool debug);
1828+
1829+
void daemon_terminate () noexcept;
1830+
18261831
void daemon_sanitycheck_environment (const ::rpmostreecxx::OstreeSysroot &sysroot);
18271832

18281833
::rust::String deployment_generate_id (const ::rpmostreecxx::OstreeDeployment &deployment) noexcept;

rust/src/client.rs

+59-4
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,12 @@ use crate::core::OSTREE_BOOTED;
66
use crate::cxxrsutil::*;
77
use crate::ffi::SystemHostType;
88
use crate::utils;
9-
use anyhow::{anyhow, Result};
9+
use anyhow::{anyhow, Context, Result};
1010
use cap_std_ext::rustix;
1111
use gio::prelude::*;
1212
use ostree_ext::{gio, glib};
1313
use std::io::{BufRead, Write};
1414
use std::os::unix::io::IntoRawFd;
15-
use std::process::Command;
1615

1716
/// The well-known bus name.
1817
const BUS_NAME: &str = "org.projectatomic.rpmostree1";
@@ -49,7 +48,8 @@ impl ClientConnection {
4948
SYSROOT_PATH,
5049
"org.projectatomic.rpmostree1.Sysroot",
5150
gio::NONE_CANCELLABLE,
52-
)?;
51+
)
52+
.context("Initializing sysroot proxy")?;
5353
// Today the daemon mode requires running inside a booted deployment.
5454
let booted = sysroot_proxy
5555
.cached_property("Booted")
@@ -155,6 +155,45 @@ pub(crate) fn client_handle_fd_argument(
155155
}
156156
}
157157

158+
/// Connect to the client socket and ensure the daemon is initialized;
159+
/// this avoids DBus and ensures that we get any early startup errors
160+
/// returned cleanly.
161+
#[cfg(feature = "client-socket")]
162+
fn start_daemon_via_socket() -> Result<()> {
163+
use cap_std::io_lifetimes::IntoSocketlike;
164+
165+
let address = sockaddr()?;
166+
let socket = rustix::net::socket(
167+
rustix::net::AddressFamily::UNIX,
168+
rustix::net::SocketType::SEQPACKET,
169+
rustix::net::Protocol::from_raw(0),
170+
)?;
171+
let addr = crate::client::sockaddr()?;
172+
tracing::debug!("Starting daemon via {address:?}");
173+
rustix::net::connect_unix(&socket, &addr)
174+
.with_context(|| anyhow!("Failed to connect to {address:?}"))?;
175+
let socket = socket.into_socketlike();
176+
crate::daemon::write_message(
177+
&socket,
178+
crate::daemon::SocketMessage::ClientHello {
179+
selfid: crate::core::self_id()?,
180+
},
181+
)?;
182+
let resp = crate::daemon::recv_message(&socket)?;
183+
match resp {
184+
crate::daemon::SocketMessage::ServerOk => Ok(()),
185+
crate::daemon::SocketMessage::ServerError { msg } => {
186+
Err(anyhow!("server error: {msg}").into())
187+
}
188+
o => Err(anyhow!("unexpected message: {o:?}").into()),
189+
}
190+
}
191+
192+
/// Returns the address of the client socket.
193+
pub(crate) fn sockaddr() -> Result<rustix::net::SocketAddrUnix> {
194+
rustix::net::SocketAddrUnix::new("/run/rpm-ostree/client.sock").map_err(anyhow::Error::msg)
195+
}
196+
158197
/// Explicitly ensure the daemon is started via systemd, if possible.
159198
///
160199
/// This works around bugs from DBus activation, see
@@ -165,12 +204,16 @@ pub(crate) fn client_handle_fd_argument(
165204
/// to systemd directly and use its client tools to scrape errors.
166205
///
167206
/// What we really should do probably is use native socket activation.
168-
pub(crate) fn client_start_daemon() -> CxxResult<()> {
207+
#[cfg(not(feature = "client-socket"))]
208+
fn start_daemon_via_systemctl() -> Result<()> {
209+
use std::process::Command;
210+
169211
let service = "rpm-ostreed.service";
170212
// Assume non-root can't use systemd right now.
171213
if rustix::process::getuid().as_raw() != 0 {
172214
return Ok(());
173215
}
216+
174217
// Unfortunately, RHEL8 systemd will count "systemctl start"
175218
// invocations against the restart limit, so query the status
176219
// first.
@@ -197,6 +240,18 @@ pub(crate) fn client_start_daemon() -> CxxResult<()> {
197240
Ok(())
198241
}
199242

243+
pub(crate) fn client_start_daemon() -> CxxResult<()> {
244+
// systemctl and socket paths only work for root right now; in the future
245+
// the socket may be opened up.
246+
if rustix::process::getuid().as_raw() != 0 {
247+
return Ok(());
248+
}
249+
#[cfg(feature = "client-socket")]
250+
return start_daemon_via_socket().map_err(Into::into);
251+
#[cfg(not(feature = "client-socket"))]
252+
return start_daemon_via_systemctl().map_err(Into::into);
253+
}
254+
200255
/// Convert the GVariant parameters from the DownloadProgress DBus API to a human-readable English string.
201256
pub(crate) fn client_render_download_progress(progress: &crate::ffi::GVariant) -> String {
202257
let progress = progress

rust/src/core.rs

+8
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,14 @@ fn stage_container_rpm_files(rpms: Vec<File>) -> CxxResult<Vec<String>> {
332332
Ok(r)
333333
}
334334

335+
/// Return an opaque identifier for the current executing binary. This can
336+
/// be passed via IPC to verify that client and server are running the same code.
337+
pub(crate) fn self_id() -> Result<String> {
338+
use std::os::unix::fs::MetadataExt;
339+
let metadata = std::fs::metadata("/proc/self/exe").context("Failed to read /proc/self/exe")?;
340+
Ok(format!("dev={};inode={}", metadata.dev(), metadata.ino()))
341+
}
342+
335343
#[cfg(test)]
336344
mod test {
337345
use super::*;

0 commit comments

Comments
 (0)