Skip to content

Commit 2d70a00

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 1d5f32f commit 2d70a00

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.19", 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>
@@ -2164,6 +2165,10 @@ extern "C"
21642165
::rust::Str opt_deploy_id, ::rust::Str opt_os_name,
21652166
::rpmostreecxx::OstreeDeployment **return$) noexcept;
21662167

2168+
::rust::repr::PtrLen rpmostreecxx$cxxbridge1$daemon_main (bool debug) noexcept;
2169+
2170+
void rpmostreecxx$cxxbridge1$daemon_terminate () noexcept;
2171+
21672172
::rust::repr::PtrLen rpmostreecxx$cxxbridge1$daemon_sanitycheck_environment (
21682173
const ::rpmostreecxx::OstreeSysroot &sysroot) noexcept;
21692174

@@ -2930,6 +2935,40 @@ extern "C"
29302935
return throw$;
29312936
}
29322937

2938+
::rust::repr::PtrLen
2939+
rpmostreecxx$cxxbridge1$daemon_init_inner (bool debug) noexcept
2940+
{
2941+
void (*daemon_init_inner$) (bool) = ::rpmostreecxx::daemon_init_inner;
2942+
::rust::repr::PtrLen throw$;
2943+
::rust::behavior::trycatch (
2944+
[&] {
2945+
daemon_init_inner$ (debug);
2946+
throw$.ptr = nullptr;
2947+
},
2948+
[&] (const char *catch$) noexcept {
2949+
throw$.len = ::std::strlen (catch$);
2950+
throw$.ptr = const_cast<char *> (::cxxbridge1$exception (catch$, throw$.len));
2951+
});
2952+
return throw$;
2953+
}
2954+
2955+
::rust::repr::PtrLen
2956+
rpmostreecxx$cxxbridge1$daemon_main_inner () noexcept
2957+
{
2958+
void (*daemon_main_inner$) () = ::rpmostreecxx::daemon_main_inner;
2959+
::rust::repr::PtrLen throw$;
2960+
::rust::behavior::trycatch (
2961+
[&] {
2962+
daemon_main_inner$ ();
2963+
throw$.ptr = nullptr;
2964+
},
2965+
[&] (const char *catch$) noexcept {
2966+
throw$.len = ::std::strlen (catch$);
2967+
throw$.ptr = const_cast<char *> (::cxxbridge1$exception (catch$, throw$.len));
2968+
});
2969+
return throw$;
2970+
}
2971+
29332972
::rust::repr::PtrLen
29342973
rpmostreecxx$cxxbridge1$client_require_root () noexcept
29352974
{
@@ -3946,6 +3985,22 @@ deployment_get_base (::rpmostreecxx::OstreeSysroot &sysroot, ::rust::Str opt_dep
39463985
return ::std::move (return$.value);
39473986
}
39483987

3988+
void
3989+
daemon_main (bool debug)
3990+
{
3991+
::rust::repr::PtrLen error$ = rpmostreecxx$cxxbridge1$daemon_main (debug);
3992+
if (error$.ptr)
3993+
{
3994+
throw ::rust::impl< ::rust::Error>::error (error$);
3995+
}
3996+
}
3997+
3998+
void
3999+
daemon_terminate () noexcept
4000+
{
4001+
rpmostreecxx$cxxbridge1$daemon_terminate ();
4002+
}
4003+
39494004
void
39504005
daemon_sanitycheck_environment (const ::rpmostreecxx::OstreeSysroot &sysroot)
39514006
{

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>
@@ -1821,6 +1822,10 @@ ::rpmostreecxx::OstreeDeployment *deployment_get_base (::rpmostreecxx::OstreeSys
18211822
::rust::Str opt_deploy_id,
18221823
::rust::Str opt_os_name);
18231824

1825+
void daemon_main (bool debug);
1826+
1827+
void daemon_terminate () noexcept;
1828+
18241829
void daemon_sanitycheck_environment (const ::rpmostreecxx::OstreeSysroot &sysroot);
18251830

18261831
::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)