From c993732dc7c7aff15255099fcd7edd4c3fea3ab2 Mon Sep 17 00:00:00 2001 From: RishabhSaini Date: Sun, 23 May 2021 09:12:41 -0400 Subject: [PATCH] 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 ``` Co-authored-by: Colin Walters --- Cargo.toml | 2 +- Makefile-daemon.am | 7 +- rpmostree-cxxrs.cxx | 49 +++++++ rpmostree-cxxrs.h | 5 + rust/src/client.rs | 95 +++++++----- rust/src/core.rs | 8 ++ rust/src/daemon.rs | 159 ++++++++++++++++++++- rust/src/lib.rs | 8 ++ rust/src/main.rs | 4 +- src/app/rpmostree-builtin-start-daemon.cxx | 51 ++++--- src/daemon/rpm-ostreed.service | 1 + src/daemon/rpm-ostreed.socket | 10 ++ src/daemon/rpmostreed-daemon.cxx | 1 + src/daemon/rpmostreed-daemon.hpp | 28 ++++ tests/kolainst/nondestructive/misc.sh | 4 + 15 files changed, 371 insertions(+), 61 deletions(-) create mode 100644 src/daemon/rpm-ostreed.socket create mode 100644 src/daemon/rpmostreed-daemon.hpp diff --git a/Cargo.toml b/Cargo.toml index 04f40cfc48..1738bdd1db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ cap-std-ext = "2.0" cap-std = { version = "1.0.3", features = ["fs_utf8"] } containers-image-proxy = "0.5.1" # Explicitly force on libc -rustix = { version = "0.37", features = ["use-libc"] } +rustix = { version = "0.37", features = ["use-libc", "net"] } cap-primitives = "1.0.3" chrono = { version = "0.4.26", features = ["serde"] } clap = { version = "4.3", features = ["derive"] } diff --git a/Makefile-daemon.am b/Makefile-daemon.am index 4233d90db1..478941470f 100644 --- a/Makefile-daemon.am +++ b/Makefile-daemon.am @@ -63,14 +63,15 @@ systemdunit_service_file_names = \ $(NULL) systemdunit_service_files = $(addprefix $(srcdir)/src/daemon/,$(systemdunit_service_file_names)) -systemdunit_timer_files = \ +systemdunit_other_files = \ $(srcdir)/src/daemon/rpm-ostreed-automatic.timer \ $(srcdir)/src/daemon/rpm-ostree-countme.timer \ + $(srcdir)/src/daemon/rpm-ostreed.socket \ $(NULL) systemdunit_DATA = \ $(systemdunit_service_files) \ - $(systemdunit_timer_files) \ + $(systemdunit_other_files) \ $(NULL) systemdunitdir = $(prefix)/lib/systemd/system/ @@ -103,7 +104,7 @@ EXTRA_DIST += \ $(sysconf_DATA) \ $(service_in_files) \ $(systemdunit_service_in_files) \ - $(systemdunit_timer_files) \ + $(systemdunit_other_files) \ $(NULL) CLEANFILES += \ diff --git a/rpmostree-cxxrs.cxx b/rpmostree-cxxrs.cxx index 62a8ea164b..d1b8b3d0de 100644 --- a/rpmostree-cxxrs.cxx +++ b/rpmostree-cxxrs.cxx @@ -8,6 +8,7 @@ #include "rpmostree-package-variants.h" #include "rpmostree-rpm-util.h" #include "rpmostree-util.h" +#include "rpmostreed-daemon.hpp" #include "rpmostreemain.h" #include "src/libpriv/rpmostree-cxxrs-prelude.h" #include @@ -2195,6 +2196,10 @@ extern "C" ::rust::Str opt_deploy_id, ::rust::Str opt_os_name, ::rpmostreecxx::OstreeDeployment **return$) noexcept; + ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$daemon_main (bool debug) noexcept; + + void rpmostreecxx$cxxbridge1$daemon_terminate () noexcept; + ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$daemon_sanitycheck_environment ( const ::rpmostreecxx::OstreeSysroot &sysroot) noexcept; @@ -2970,6 +2975,34 @@ extern "C" return throw$; } + ::rust::repr::PtrLen + rpmostreecxx$cxxbridge1$daemon_init_inner (bool debug) noexcept + { + void (*daemon_init_inner$) (bool) = ::rpmostreecxx::daemon_init_inner; + ::rust::repr::PtrLen throw$; + ::rust::behavior::trycatch ( + [&] { + daemon_init_inner$ (debug); + throw$.ptr = nullptr; + }, + ::rust::detail::Fail (throw$)); + return throw$; + } + + ::rust::repr::PtrLen + rpmostreecxx$cxxbridge1$daemon_main_inner () noexcept + { + void (*daemon_main_inner$) () = ::rpmostreecxx::daemon_main_inner; + ::rust::repr::PtrLen throw$; + ::rust::behavior::trycatch ( + [&] { + daemon_main_inner$ (); + throw$.ptr = nullptr; + }, + ::rust::detail::Fail (throw$)); + return throw$; + } + ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$client_require_root () noexcept { @@ -4038,6 +4071,22 @@ deployment_get_base (::rpmostreecxx::OstreeSysroot &sysroot, ::rust::Str opt_dep return ::std::move (return$.value); } +void +daemon_main (bool debug) +{ + ::rust::repr::PtrLen error$ = rpmostreecxx$cxxbridge1$daemon_main (debug); + if (error$.ptr) + { + throw ::rust::impl< ::rust::Error>::error (error$); + } +} + +void +daemon_terminate () noexcept +{ + rpmostreecxx$cxxbridge1$daemon_terminate (); +} + void daemon_sanitycheck_environment (const ::rpmostreecxx::OstreeSysroot &sysroot) { diff --git a/rpmostree-cxxrs.h b/rpmostree-cxxrs.h index dd2f59e89f..1e2546167d 100644 --- a/rpmostree-cxxrs.h +++ b/rpmostree-cxxrs.h @@ -8,6 +8,7 @@ #include "rpmostree-package-variants.h" #include "rpmostree-rpm-util.h" #include "rpmostree-util.h" +#include "rpmostreed-daemon.hpp" #include "rpmostreemain.h" #include "src/libpriv/rpmostree-cxxrs-prelude.h" #include @@ -1843,6 +1844,10 @@ ::rpmostreecxx::OstreeDeployment *deployment_get_base (::rpmostreecxx::OstreeSys ::rust::Str opt_deploy_id, ::rust::Str opt_os_name); +void daemon_main (bool debug); + +void daemon_terminate () noexcept; + void daemon_sanitycheck_environment (const ::rpmostreecxx::OstreeSysroot &sysroot); ::rust::String deployment_generate_id (const ::rpmostreecxx::OstreeDeployment &deployment) noexcept; diff --git a/rust/src/client.rs b/rust/src/client.rs index 35efd4ef70..4ebf69dadf 100644 --- a/rust/src/client.rs +++ b/rust/src/client.rs @@ -6,12 +6,14 @@ use crate::core::OSTREE_BOOTED; use crate::cxxrsutil::*; use crate::ffi::SystemHostType; use crate::utils; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, Context, Result}; +use camino::Utf8Path; use fn_error_context::context; use gio::prelude::*; use ostree_ext::{gio, glib}; use std::io::{BufRead, Write}; use std::os::unix::io::IntoRawFd; +use std::os::unix::net::UnixStream; use std::process::Command; /// The well-known bus name. @@ -20,6 +22,7 @@ const BUS_NAME: &str = "org.projectatomic.rpmostree1"; const SYSROOT_PATH: &str = "/org/projectatomic/rpmostree1/Sysroot"; const OS_INTERFACE: &str = "org.projectatomic.rpmostree1.OS"; const OS_EX_INTERFACE: &str = "org.projectatomic.rpmostree1.OSExperimental"; +const CLIENT_SOCKET_PATH: &str = "/run/rpm-ostree/client.sock"; /// A unique DBus connection to the rpm-ostree daemon. /// This currently wraps a C++ client connection. @@ -49,7 +52,8 @@ impl ClientConnection { SYSROOT_PATH, "org.projectatomic.rpmostree1.Sysroot", gio::Cancellable::NONE, - )?; + ) + .context("Initializing sysroot proxy")?; // Today the daemon mode requires running inside a booted deployment. let booted = sysroot_proxy .cached_property("Booted") @@ -156,46 +160,63 @@ pub(crate) fn client_handle_fd_argument( } } -/// Explicitly ensure the daemon is started via systemd, if possible. -/// -/// This works around bugs from DBus activation, see -/// https://github.com/coreos/rpm-ostree/pull/2932 -/// -/// Basically we load too much data before claiming the bus name, -/// and dbus doesn't give us a useful error. Instead, let's talk -/// to systemd directly and use its client tools to scrape errors. -/// -/// What we really should do probably is use native socket activation. -pub(crate) fn client_start_daemon() -> CxxResult<()> { - let service = "rpm-ostreed.service"; - // Assume non-root can't use systemd right now. - if rustix::process::getuid().as_raw() != 0 { - return Ok(()); +// If the client socket doesn't exist, try to ask systemd to start it. +// This can happen even when the socket unit is installed because +// presets may not enable it. +fn check_and_start_daemon_socket() -> Result { + if Utf8Path::new(CLIENT_SOCKET_PATH).exists() { + return Ok(true); } - // Unfortunately, RHEL8 systemd will count "systemctl start" - // invocations against the restart limit, so query the status - // first. - let activeres = Command::new("systemctl") - .args(&["is-active", "rpm-ostreed"]) + let socket_unit = "rpm-ostreed.socket"; + tracing::debug!("{CLIENT_SOCKET_PATH} does not exist, explicitly starting socket unit"); + let start_result = Command::new("systemctl") + .args(&["--no-ask-password", "start", socket_unit]) .output()?; - // Explicitly don't check the error return value, we don't want to - // hard fail on it. - if String::from_utf8_lossy(&activeres.stdout).starts_with("active") { - // It's active, we're done. Note that while this is a race - // condition, that's fine because it will be handled by DBus - // activation. + if !start_result.status.success() { + let err = String::from_utf8_lossy(&start_result.stderr); + tracing::warn!("Failed to start {socket_unit}: {err}"); + return Ok(false); + } + Ok(true) +} + +/// Connect to the client socket and ensure the daemon is initialized; +/// this avoids DBus and ensures that we get any early startup errors +/// returned cleanly. +#[context("Starting daemon via socket")] +fn start_daemon_via_socket() -> Result<()> { + let capable = check_and_start_daemon_socket()?; + if !capable { + tracing::debug!("Falling back to DBus activation"); return Ok(()); } - let res = Command::new("systemctl") - .args(&["--no-ask-password", "start", service]) - .status()?; - if !res.success() { - let _ = Command::new("systemctl") - .args(&["--no-pager", "status", service]) - .status(); - return Err(anyhow!("{}", res).into()); + + tracing::debug!("Starting daemon via {CLIENT_SOCKET_PATH}"); + let mut socket = UnixStream::connect(CLIENT_SOCKET_PATH)?; + crate::daemon::write_message( + &mut socket, + crate::daemon::SocketMessage::ClientHello { + selfid: crate::core::self_id()?, + }, + ) + .context("Writing ClientHello")?; + let resp = crate::daemon::recv_message(&mut socket)?; + match resp { + crate::daemon::SocketMessage::ServerOk => Ok(()), + crate::daemon::SocketMessage::ServerError { msg } => { + Err(anyhow!("server error: {msg}").into()) + } + o => Err(anyhow!("unexpected message: {o:?}").into()), } - Ok(()) +} + +pub(crate) fn client_start_daemon() -> CxxResult<()> { + // systemctl and socket paths only work for root right now; in the future + // the socket may be opened up. + if rustix::process::getuid().as_raw() != 0 { + return Ok(()); + } + return start_daemon_via_socket().map_err(Into::into); } /// Convert the GVariant parameters from the DownloadProgress DBus API to a human-readable English string. diff --git a/rust/src/core.rs b/rust/src/core.rs index a8edeab657..69f66394dc 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -350,6 +350,14 @@ fn stage_container_rpm_files(rpms: Vec) -> CxxResult> { Ok(r) } +/// Return an opaque identifier for the current executing binary. This can +/// be passed via IPC to verify that client and server are running the same code. +pub(crate) fn self_id() -> Result { + use std::os::unix::fs::MetadataExt; + let metadata = std::fs::metadata("/proc/self/exe").context("Failed to read /proc/self/exe")?; + Ok(format!("dev={};inode={}", metadata.dev(), metadata.ino())) +} + #[cfg(test)] mod test { use super::*; diff --git a/rust/src/daemon.rs b/rust/src/daemon.rs index c78b9e372b..4aff657935 100644 --- a/rust/src/daemon.rs +++ b/rust/src/daemon.rs @@ -8,22 +8,35 @@ use crate::cxxrsutil::*; use crate::ffi::{ OverrideReplacementSource, OverrideReplacementType, ParsedRevision, ParsedRevisionKind, }; -use anyhow::{anyhow, format_err, Result}; +use anyhow::{anyhow, format_err, Context, Result}; use cap_std::fs::Dir; use cap_std_ext::cap_std; use cap_std_ext::dirext::CapStdExtDirExt; use fn_error_context::context; use glib::prelude::*; +use once_cell::sync::Lazy; use ostree_ext::{gio, glib, ostree}; -use rustix::fd::BorrowedFd; +use rustix::fd::{BorrowedFd, FromRawFd}; use rustix::fs::MetadataExt; +use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; -use std::io::Read; +use std::io::{Read, Write}; use std::os::unix::fs::PermissionsExt; +use std::os::unix::net::{UnixListener, UnixStream}; use std::path::Path; +use std::sync::Mutex; +use tokio::sync::oneshot::Sender; const RPM_OSTREED_COMMIT_VERIFICATION_CACHE: &str = "rpm-ostree/gpgcheck-cache"; +// Messages sent across the socket +#[derive(Debug, Serialize, Deserialize)] +pub(crate) enum SocketMessage { + ClientHello { selfid: String }, + ServerOk, + ServerError { msg: String }, +} + /// Validate basic assumptions on daemon startup. pub(crate) fn daemon_sanitycheck_environment(sysroot: &crate::FFIOstreeSysroot) -> CxxResult<()> { let sysroot = &sysroot.glib_reborrow(); @@ -131,6 +144,146 @@ fn deployment_populate_variant_origin( Ok(()) } +// Serialize to JSON and write to the stream, then close it. There's no framing, so +// closing the socket signals end of message. +#[context("Sending activation message")] +pub(crate) fn write_message(conn: &mut UnixStream, message: SocketMessage) -> Result<()> { + let sendbuf = serde_json::to_vec(&message)?; + conn.write_all(&sendbuf)?; + // Flush writes + conn.shutdown(std::net::Shutdown::Write)?; + Ok(()) +} + +// Receive and parse a single message from the socket. +#[context("Receving activation message")] +pub(crate) fn recv_message(conn: &mut UnixStream) -> Result { + let mut buf = Vec::new(); + conn.read_to_end(&mut buf)?; + conn.shutdown(std::net::Shutdown::Read)?; + let msg: SocketMessage = serde_json::from_slice(&buf).context("Parsing client message")?; + Ok(msg) +} + +fn client_hello(mut client: UnixStream, e: anyhow::Result<()>) -> Result<()> { + let msg = recv_message(&mut client)?; + let reply = match msg { + SocketMessage::ClientHello { selfid } => { + let myid = crate::core::self_id()?; + if selfid != myid { + // For now, make this not an error + tracing::warn!("Client reported id: {selfid} different from mine: {myid}"); + } + match e { + Ok(()) => SocketMessage::ServerOk, + Err(e) => SocketMessage::ServerError { + msg: format!("{e}"), + }, + } + } + o => SocketMessage::ServerError { + msg: format!("Unexpected message: {o:?}"), + }, + }; + write_message(&mut client, reply).context("Writing client reply")?; + tracing::debug!("Acknowleged client"); + Ok(()) +} + +static SHUTDOWN_SIGNAL: Lazy>>> = Lazy::new(|| Mutex::new(None)); + +fn run_acknowledgement_worker(listener: UnixListener) { + tracing::debug!("Processing clients..."); + loop { + let (sock, addr) = match listener.accept() { + Ok(s) => s, + Err(e) => { + tracing::warn!("Failed to accept client: {e}"); + continue; + } + }; + tracing::debug!("Connection from {addr:?}"); + std::thread::spawn(move || { + if let Err(e) = client_hello(sock, Ok(())) { + tracing::warn!("error acknowledging client: {e}"); + } + }); + } +} + +/// Ensure all asynchronous tasks in this Rust half of the daemon code are stopped. +/// Called from C++. +pub(crate) fn daemon_terminate() { + if let Some(chan) = (*SHUTDOWN_SIGNAL).lock().unwrap().take() { + let _ = chan.send(()); + } +} + +fn process_one_client(listener: UnixListener, e: anyhow::Error) -> Result<()> { + let (incoming, addr) = listener.accept()?; + tracing::debug!("Connection from {addr:?}"); + // Send the error to the client + client_hello(incoming, Err(e))?; + // We've successfully sent the error; the caller of this function will + // propagate the error. + Ok(()) +} + +/// Perform initialization steps required by systemd service activation. +/// +/// This ensures that the system is running under systemd, then receives the +/// socket-FD for main IPC logic, and notifies systemd about ready-state. +pub(crate) fn daemon_main(debug: bool) -> Result<()> { + let handle = tokio::runtime::Handle::current(); + let _tokio_guard = handle.enter(); + if !systemd::daemon::booted()? { + return Err(anyhow!("not running as a systemd service")); + } + let init_res: Result<()> = crate::ffi::daemon_init_inner(debug).map_err(|e| e.into()); + tracing::debug!("Initialization result: {init_res:?}"); + + let mut fds = systemd::daemon::listen_fds(false)?.iter(); + let fd = match fds.next() { + Some(fd) => fd, + None => return Err(anyhow!("Missing rpm-ostreed.socket")), + }; + if fds.next().is_some() { + return Err(anyhow!("Expected exactly 1 fd from systemd activation")); + } + tracing::debug!("Initializing from socket activation; fd={fd}"); + let listener = unsafe { UnixListener::from_raw_fd(fd) }; + // In the socket case, we will process the initialization error later. + + // On success, we spawn a helper task that just responds with + // sucess to clients that connect via the socket. In the future, + // perhaps we'll expose an API here. + tracing::debug!("Spawning acknowledgement task"); + match init_res { + Ok(()) => { + std::thread::spawn(move || run_acknowledgement_worker(listener)); + } + Err(e) => { + tracing::debug!("Sending error to client: {}", e); + let err_copy = anyhow::format_err!("{e}"); + let r = std::thread::spawn(move || { + if let Err(suberr) = process_one_client(listener, err_copy) { + tracing::warn!("Failed to respond to client: {suberr}") + } + }); + // Block until we've written the reply to the client; + if let Err(e) = r.join() { + tracing::warn!("Failed to join response thread: {e:?}"); + } + // And finally propagate out the error + return Err(e); + } + }; + + tracing::debug!("Entering daemon mainloop"); + // And now, enter the main loop. + Ok(crate::ffi::daemon_main_inner()?) +} + /// Serialize information about the given deployment into the `dict`; /// this will be exposed via DBus and is hence public API. pub(crate) fn deployment_populate_variant( diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 43b3688521..69058663cd 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -334,6 +334,8 @@ pub mod ffi { // daemon.rs extern "Rust" { + fn daemon_main(debug: bool) -> Result<()>; + fn daemon_terminate(); fn daemon_sanitycheck_environment(sysroot: &OstreeSysroot) -> Result<()>; fn deployment_generate_id(deployment: &OstreeDeployment) -> String; fn deployment_populate_variant( @@ -801,6 +803,12 @@ pub mod ffi { fn c_unit_tests() -> Result<()>; } + unsafe extern "C++" { + include!("rpmostreed-daemon.hpp"); + fn daemon_init_inner(debug: bool) -> Result<()>; + fn daemon_main_inner() -> Result<()>; + } + unsafe extern "C++" { include!("rpmostree-clientlib.h"); fn client_require_root() -> Result<()>; diff --git a/rust/src/main.rs b/rust/src/main.rs index 63f0bf2168..14ddaa0ea0 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -148,7 +148,9 @@ fn inner_main() -> Result { .enable_all() .build() .context("Failed to build tokio runtime")?; - runtime.block_on(dispatch_multicall(callname, args)) + let r = runtime.block_on(dispatch_multicall(callname, args)); + tracing::debug!("Exiting inner main with result: {r:?}"); + r } fn print_error(e: anyhow::Error) { diff --git a/src/app/rpmostree-builtin-start-daemon.cxx b/src/app/rpmostree-builtin-start-daemon.cxx index dcc5382361..0e8430e171 100644 --- a/src/app/rpmostree-builtin-start-daemon.cxx +++ b/src/app/rpmostree-builtin-start-daemon.cxx @@ -35,6 +35,7 @@ #include "rpmostree-libbuiltin.h" #include "rpmostree-util.h" #include "rpmostreed-daemon.h" +#include "rpmostreed-utils.h" typedef enum { @@ -53,6 +54,7 @@ static GOptionEntry opt_entries[] = { { "debug", 'd', 0, G_OPTION_ARG_NONE, &opt "Use system root SYSROOT (default: /)", "SYSROOT" }, { NULL } }; +static GDBusConnection *bus = NULL; static RpmostreedDaemon *rpm_ostree_daemon = NULL; static void @@ -211,17 +213,15 @@ on_log_handler (const gchar *log_domain, GLogLevelFlags log_level, const gchar * sd_journal_print (priority, "%s", message); } -gboolean -rpmostree_builtin_start_daemon (int argc, char **argv, RpmOstreeCommandInvocation *invocation, - GCancellable *cancellable, GError **error) +namespace rpmostreecxx { - g_autoptr (GOptionContext) opt_context = g_option_context_new (" - start the daemon process"); - g_option_context_add_main_entries (opt_context, opt_entries, NULL); - - if (!g_option_context_parse (opt_context, &argc, &argv, error)) - return FALSE; - - if (opt_debug) +// This function is always called from the Rust side. Hopefully +// soon we'll move more of this code into daemon.rs. +void +daemon_init_inner (bool debug) +{ + g_autoptr (GError) local_error = NULL; + if (debug) { g_autoptr (GIOChannel) channel = NULL; g_log_set_handler (G_LOG_DOMAIN, (GLogLevelFlags)(G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_INFO), @@ -243,19 +243,24 @@ rpmostree_builtin_start_daemon (int argc, char **argv, RpmOstreeCommandInvocatio g_unix_signal_add (SIGTERM, on_sigint, NULL); /* Get an explicit ref to the bus so we can use it later */ - g_autoptr (GDBusConnection) bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error); + bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &local_error); if (!bus) - return FALSE; - if (!start_daemon (bus, error)) + util::throw_gerror (local_error); + if (!start_daemon (bus, &local_error)) { - if (*error) - sd_notifyf (0, "STATUS=error: %s", (*error)->message); - return FALSE; + sd_notifyf (0, "STATUS=error: %s", local_error->message); + util::throw_gerror (local_error); } +} +// Called from rust side to enter mainloop. +void +daemon_main_inner () +{ state_transition (APPSTATE_RUNNING); g_debug ("Entering main event loop"); + g_assert (rpm_ostree_daemon); rpmostreed_daemon_run_until_idle_exit (rpm_ostree_daemon); if (bus) @@ -285,6 +290,20 @@ rpmostree_builtin_start_daemon (int argc, char **argv, RpmOstreeCommandInvocatio g_autoptr (GMainContext) mainctx = g_main_context_default (); while (appstate == APPSTATE_FLUSHING) g_main_context_iteration (mainctx, TRUE); +} +} /* namespace */ + +gboolean +rpmostree_builtin_start_daemon (int argc, char **argv, RpmOstreeCommandInvocation *invocation, + GCancellable *cancellable, GError **error) +{ + g_autoptr (GOptionContext) opt_context = g_option_context_new (" - start the daemon process"); + g_option_context_add_main_entries (opt_context, opt_entries, NULL); + + if (!g_option_context_parse (opt_context, &argc, &argv, error)) + return FALSE; + + rpmostreecxx::daemon_main (opt_debug); return TRUE; } diff --git a/src/daemon/rpm-ostreed.service b/src/daemon/rpm-ostreed.service index 406ead652f..3ec413981e 100644 --- a/src/daemon/rpm-ostreed.service +++ b/src/daemon/rpm-ostreed.service @@ -21,6 +21,7 @@ MountFlags=slave # but as a subprocess. ProtectHome=true NotifyAccess=main +Sockets=rpm-ostreed.socket # Significantly bump this timeout from the default because # we do a lot of stuff on daemon startup. TimeoutStartSec=5m diff --git a/src/daemon/rpm-ostreed.socket b/src/daemon/rpm-ostreed.socket new file mode 100644 index 0000000000..bf0bfc0b55 --- /dev/null +++ b/src/daemon/rpm-ostreed.socket @@ -0,0 +1,10 @@ +[Unit] +ConditionKernelCommandLine=ostree + +[Socket] +ListenStream=/run/rpm-ostree/client.sock +# We only allow root to connect out of conservatism for now +SocketMode=0600 + +[Install] +WantedBy=sockets.target diff --git a/src/daemon/rpmostreed-daemon.cxx b/src/daemon/rpmostreed-daemon.cxx index 8fbf2dd58f..1231395ede 100644 --- a/src/daemon/rpmostreed-daemon.cxx +++ b/src/daemon/rpmostreed-daemon.cxx @@ -820,6 +820,7 @@ rpmostreed_daemon_run_until_idle_exit (RpmostreedDaemon *self) update_status (self); while (self->running) g_main_context_iteration (NULL, TRUE); + rpmostreecxx::daemon_terminate (); } void diff --git a/src/daemon/rpmostreed-daemon.hpp b/src/daemon/rpmostreed-daemon.hpp new file mode 100644 index 0000000000..4c788f0960 --- /dev/null +++ b/src/daemon/rpmostreed-daemon.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma once + +#include "rust/cxx.h" + +namespace rpmostreecxx +{ +/* Note: Currently actually defined in rpmostree-builtin-start-daemon.cxx for historical reasons */ +void daemon_init_inner (bool debug); +void daemon_main_inner (); +} \ No newline at end of file diff --git a/tests/kolainst/nondestructive/misc.sh b/tests/kolainst/nondestructive/misc.sh index 223bd45d61..c943939131 100755 --- a/tests/kolainst/nondestructive/misc.sh +++ b/tests/kolainst/nondestructive/misc.sh @@ -109,6 +109,10 @@ rpmostree_busctl_call_os Search as 1 should-not-exist-p-equals-np > out.txt assert_file_has_content_literal out.txt 'aa{sv} 0' echo "ok dbus Search" +systemctl stop rpm-ostreed +rpmostree_busctl_call_os ListRepos +echo "restarting the daemon works" + rpm-ostree search testdaemon > out.txt assert_file_has_content_literal out.txt '===== Name Matched =====' assert_file_has_content_literal out.txt 'testdaemon : awesome-daemon-for-testing'