diff --git a/Cargo.toml b/Cargo.toml index 9d7017ce1a..d57387130a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ camino = "1.0.9" cap-std-ext = "0.26" cap-std = { version = "0.25", features = ["fs_utf8"] } # Explicitly force on libc -rustix = { version = "0.35", features = ["use-libc"] } +rustix = { version = "0.35", features = ["use-libc", "net"] } cap-primitives = "0.25.2" cap-tempfile = "0.25.2" chrono = { version = "0.4.19", features = ["serde"] } @@ -123,6 +123,8 @@ lto = "thin" # Note: If you add a feature here, you also probably want to update utils.rs:get_features() fedora-integration = [] rhsm = ["libdnf-sys/rhsm"] +# Enable hard requirement on `rpm-ostreed.socket`; requires https://bugzilla.redhat.com/show_bug.cgi?id=2110012 +client-socket = [] bin-unit-tests = [] # ASAN+UBSAN sanitizers = [] diff --git a/Makefile-daemon.am b/Makefile-daemon.am index 6e6cb3a3d5..95e0f55bcb 100644 --- a/Makefile-daemon.am +++ b/Makefile-daemon.am @@ -63,14 +63,18 @@ systemdunit_service_in_files = \ $(NULL) systemdunit_service_files = $(systemdunit_service_in_files:.service.in=.service) -systemdunit_timer_files = \ +systemdunit_other_files = \ $(srcdir)/src/daemon/rpm-ostreed-automatic.timer \ $(srcdir)/src/daemon/rpm-ostree-countme.timer \ $(NULL) +if CLIENT_SOCKET +systemdunit_other_files += $(srcdir)/src/daemon/rpm-ostreed.socket +endif + systemdunit_DATA = \ $(systemdunit_service_files) \ - $(systemdunit_timer_files) \ + $(systemdunit_other_files) \ $(NULL) systemdunitdir = $(prefix)/lib/systemd/system/ @@ -110,7 +114,7 @@ EXTRA_DIST += \ $(sysconf_DATA) \ $(service_in_files) \ $(systemdunit_service_in_files) \ - $(systemdunit_timer_files) \ + $(systemdunit_other_files) \ $(NULL) CLEANFILES += \ diff --git a/configure.ac b/configure.ac index ba0ed4cf0b..62ad3582b5 100644 --- a/configure.ac +++ b/configure.ac @@ -69,6 +69,14 @@ AC_ARG_ENABLE(featuresrs, AS_HELP_STRING([--enable-featuresrs], [Rust features, see Cargo.toml for more information]),, [enable_featuresrs=]) + +AC_ARG_ENABLE(client-socket, + AS_HELP_STRING([--enable-client-socket], + [(default: no)]),, + [enable_client_socket=no]) +AS_IF([test x$enable_client_socket = xyes], [enable_featuresrs="$enable_featuresrs client-socket"]) +AM_CONDITIONAL(CLIENT_SOCKET, [echo $enable_featuresrs | grep -q 'client-socket']) + AC_SUBST([RUST_FEATURES], $enable_featuresrs) # Initialize libtool diff --git a/packaging/rpm-ostree.spec.in b/packaging/rpm-ostree.spec.in index c7b93040cf..aeb09fb9ee 100644 --- a/packaging/rpm-ostree.spec.in +++ b/packaging/rpm-ostree.spec.in @@ -34,6 +34,13 @@ BuildRequires: rust %bcond_with rhsm %endif +# https://bugzilla.redhat.com/show_bug.cgi?id=2110012 +%if 0%{?fedora} >= 37 +%bcond_without client_socket +%else +%bcond_with client_socket +%endif + # RHEL (8,9) doesn't ship zchunk today. Keep this in sync # with libdnf: https://gitlab.com/redhat/centos-stream/rpms/libdnf/-/blob/762f631e36d1e42c63a794882269d26c156b68c1/libdnf.spec#L45 %if 0%{?rhel} @@ -179,7 +186,7 @@ env NOCONFIGURE=1 ./autogen.sh export RUSTFLAGS="%{build_rustflags}" %endif %configure --disable-silent-rules --enable-gtk-doc %{?rpmdb_default} %{?with_sanitizers:--enable-sanitizers} %{?with_bin_unit_tests:--enable-bin-unit-tests} \ - %{?with_rhsm:--enable-featuresrs=rhsm} + %{?with_rhsm:--enable-featuresrs=rhsm} %{?with_client_socket:--enable-client-socket} %make_build diff --git a/rpmostree-cxxrs.cxx b/rpmostree-cxxrs.cxx index a6ce74cf8b..fcb9395bdf 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 @@ -2164,6 +2165,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; @@ -2930,6 +2935,40 @@ 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; + }, + [&] (const char *catch$) noexcept { + throw$.len = ::std::strlen (catch$); + throw$.ptr = const_cast (::cxxbridge1$exception (catch$, throw$.len)); + }); + 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; + }, + [&] (const char *catch$) noexcept { + throw$.len = ::std::strlen (catch$); + throw$.ptr = const_cast (::cxxbridge1$exception (catch$, throw$.len)); + }); + return throw$; + } + ::rust::repr::PtrLen rpmostreecxx$cxxbridge1$client_require_root () noexcept { @@ -3946,6 +3985,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 b9d046a73a..cb1c65bfc7 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 @@ -1821,6 +1822,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 153d23ca50..5afb1d035b 100644 --- a/rust/src/client.rs +++ b/rust/src/client.rs @@ -6,13 +6,12 @@ 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 cap_std_ext::rustix; use gio::prelude::*; use ostree_ext::{gio, glib}; use std::io::{BufRead, Write}; use std::os::unix::io::IntoRawFd; -use std::process::Command; /// The well-known bus name. const BUS_NAME: &str = "org.projectatomic.rpmostree1"; @@ -49,7 +48,8 @@ impl ClientConnection { SYSROOT_PATH, "org.projectatomic.rpmostree1.Sysroot", gio::NONE_CANCELLABLE, - )?; + ) + .context("Initializing sysroot proxy")?; // Today the daemon mode requires running inside a booted deployment. let booted = sysroot_proxy .cached_property("Booted") @@ -155,6 +155,45 @@ pub(crate) fn client_handle_fd_argument( } } +/// 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. +#[cfg(feature = "client-socket")] +fn start_daemon_via_socket() -> Result<()> { + use cap_std::io_lifetimes::IntoSocketlike; + + let address = sockaddr()?; + let socket = rustix::net::socket( + rustix::net::AddressFamily::UNIX, + rustix::net::SocketType::SEQPACKET, + rustix::net::Protocol::from_raw(0), + )?; + let addr = crate::client::sockaddr()?; + tracing::debug!("Starting daemon via {address:?}"); + rustix::net::connect_unix(&socket, &addr) + .with_context(|| anyhow!("Failed to connect to {address:?}"))?; + let socket = socket.into_socketlike(); + crate::daemon::write_message( + &socket, + crate::daemon::SocketMessage::ClientHello { + selfid: crate::core::self_id()?, + }, + )?; + let resp = crate::daemon::recv_message(&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()), + } +} + +/// Returns the address of the client socket. +pub(crate) fn sockaddr() -> Result { + rustix::net::SocketAddrUnix::new("/run/rpm-ostree/client.sock").map_err(anyhow::Error::msg) +} + /// Explicitly ensure the daemon is started via systemd, if possible. /// /// This works around bugs from DBus activation, see @@ -165,12 +204,16 @@ pub(crate) fn client_handle_fd_argument( /// 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<()> { +#[cfg(not(feature = "client-socket"))] +fn start_daemon_via_systemctl() -> Result<()> { + use std::process::Command; + let service = "rpm-ostreed.service"; // Assume non-root can't use systemd right now. if rustix::process::getuid().as_raw() != 0 { return Ok(()); } + // Unfortunately, RHEL8 systemd will count "systemctl start" // invocations against the restart limit, so query the status // first. @@ -197,6 +240,18 @@ pub(crate) fn client_start_daemon() -> CxxResult<()> { 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(()); + } + #[cfg(feature = "client-socket")] + return start_daemon_via_socket().map_err(Into::into); + #[cfg(not(feature = "client-socket"))] + return start_daemon_via_systemctl().map_err(Into::into); +} + /// Convert the GVariant parameters from the DownloadProgress DBus API to a human-readable English string. pub(crate) fn client_render_download_progress(progress: &crate::ffi::GVariant) -> String { let progress = progress diff --git a/rust/src/core.rs b/rust/src/core.rs index 6c04c7c259..533ac57eae 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -332,6 +332,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 1ad6e3c355..4d304dcaae 100644 --- a/rust/src/daemon.rs +++ b/rust/src/daemon.rs @@ -8,22 +8,40 @@ 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::io_lifetimes::{IntoSocketlike, OwnedFd, OwnedSocketlike}; use cap_std_ext::dirext::CapStdExtDirExt; use cap_std_ext::{cap_std, rustix}; 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::os::unix::fs::PermissionsExt; 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 }, +} + +impl SocketMessage { + // Maximum size of a message. + pub(crate) const BUFSIZE: usize = 8192; +} + /// Validate basic assumptions on daemon startup. pub(crate) fn daemon_sanitycheck_environment(sysroot: &crate::FFIOstreeSysroot) -> CxxResult<()> { let sysroot = &sysroot.glib_reborrow(); @@ -131,6 +149,167 @@ fn deployment_populate_variant_origin( Ok(()) } +pub(crate) fn write_message(conn: &OwnedFd, message: SocketMessage) -> Result<()> { + let sendbuf = serde_json::to_vec(&message)?; + rustix::net::send(conn, &sendbuf, rustix::net::SendFlags::empty())?; + Ok(()) +} + +pub(crate) fn recv_message(conn: &OwnedFd) -> Result { + let mut buf = [0u8; SocketMessage::BUFSIZE]; + let n = rustix::net::recv(&conn, &mut buf, rustix::net::RecvFlags::empty())?; + if n == SocketMessage::BUFSIZE { + anyhow::bail!("Buffer filled to {n} bytes when reading"); + } + assert!(n < SocketMessage::BUFSIZE); + let buf = &buf[0..n]; + let msg: SocketMessage = serde_json::from_slice(buf).context("Parsing client message")?; + Ok(msg) +} + +fn client_hello(client: OwnedSocketlike, e: anyhow::Result<()>) -> Result<()> { + let msg = recv_message(&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(&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: OwnedSocketlike) { + tracing::debug!("Processing clients..."); + loop { + let sock = match rustix::net::accept(&listener) { + Ok(s) => s, + Err(e) => { + tracing::warn!("Failed to accept client: {e}"); + continue; + } + }; + std::thread::spawn(move || { + if let Err(e) = client_hello(sock.into_socketlike(), 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: OwnedSocketlike, e: anyhow::Error) -> Result<()> { + let incoming = rustix::net::accept(&listener)?; + // Send the error to the client + client_hello(incoming.into_socketlike(), 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 (listener, init_res) = match fds.next() { + None => { + // If started directly via `systemctl start` or DBus activation, we + // directly propagate the error back to our exit code without even bothering + // with a socket. + init_res?; + tracing::debug!("Initializing directly (not socket activated)"); + let listener = cfg!(feature = "client-socket") + .then(|| { + let socket = rustix::net::socket( + rustix::net::AddressFamily::UNIX, + rustix::net::SocketType::SEQPACKET, + rustix::net::Protocol::from_raw(0), + )?; + let addr = crate::client::sockaddr()?; + rustix::net::bind_unix(&socket, &addr)?; + Ok::<_, anyhow::Error>(socket.into_socketlike()) + }) + .transpose() + .context("Binding to socket")?; + (listener, Ok(())) + } + Some(fd) => { + 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 { OwnedFd::from_raw_fd(fd) }.into_socketlike(); + // In the socket case, we will process the initialization error later. + (Some(listener), init_res) + } + }; + + if let Some(listener) = listener { + // 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) => { + 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); + } + }; + } else { + init_res?; + } + + 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 95ac73f5e4..d71052bbe8 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -314,6 +314,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( @@ -777,6 +779,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 501e5ebcc9..5b2bd4a69e 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -138,7 +138,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..79aacc3f84 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); + g_autoptr (GDBusConnection) 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.socket b/src/daemon/rpm-ostreed.socket new file mode 100644 index 0000000000..ba05a151fc --- /dev/null +++ b/src/daemon/rpm-ostreed.socket @@ -0,0 +1,9 @@ +[Unit] +ConditionKernelCommandLine=ostree + +[Socket] +ListenSequentialPacket=/run/rpm-ostree/client.sock +SocketMode=0600 + +[Install] +WantedBy=sockets.target diff --git a/src/daemon/rpmostreed-daemon.cxx b/src/daemon/rpmostreed-daemon.cxx index 74d46528c4..ffe2fa846c 100644 --- a/src/daemon/rpmostreed-daemon.cxx +++ b/src/daemon/rpmostreed-daemon.cxx @@ -799,6 +799,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