diff --git a/Makefile-daemon.am b/Makefile-daemon.am index 6e6cb3a3d5..6bcbf4f81a 100644 --- a/Makefile-daemon.am +++ b/Makefile-daemon.am @@ -63,14 +63,15 @@ 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.socket \ $(srcdir)/src/daemon/rpm-ostreed-automatic.timer \ $(srcdir)/src/daemon/rpm-ostree-countme.timer \ $(NULL) systemdunit_DATA = \ $(systemdunit_service_files) \ - $(systemdunit_timer_files) \ + $(systemdunit_other_files) \ $(NULL) systemdunitdir = $(prefix)/lib/systemd/system/ @@ -110,7 +111,7 @@ EXTRA_DIST += \ $(sysconf_DATA) \ $(service_in_files) \ $(systemdunit_service_in_files) \ - $(systemdunit_timer_files) \ + $(systemdunit_other_files) \ $(NULL) CLEANFILES += \ diff --git a/rust/src/daemon.rs b/rust/src/daemon.rs index 1475598c56..6383526fef 100644 --- a/rust/src/daemon.rs +++ b/rust/src/daemon.rs @@ -5,7 +5,11 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::{cxxrsutil::*, variant_utils}; +use anyhow::{anyhow, Context, Result}; use openat_ext::OpenatDirExt; +use std::io::prelude::*; +use std::os::unix::net::{UnixListener, UnixStream}; +use std::os::unix::prelude::FromRawFd; use std::pin::Pin; /// Validate basic assumptions on daemon startup. @@ -26,6 +30,108 @@ pub(crate) fn daemon_sanitycheck_environment( Ok(()) } +/// 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. +pub(crate) fn start_daemon_via_socket() -> CxxResult<()> { + let address = "/run/rpm-ostree/client.sock"; + let s = UnixStream::connect(address) + .with_context(|| anyhow!("Failed to connect to {}", address))?; + let mut s = std::io::BufReader::new(s); + let mut r = String::new(); + s.read_to_string(&mut r) + .context("Reading from client socket")?; + if r.is_empty() { + Ok(()) + } else { + Err(anyhow!("{}", r).into()) + } +} + +fn send_init_result_to_client(client: &UnixStream, err: &Result<()>) { + let mut client = std::io::BufWriter::new(client); + match err { + Ok(_) => { + // On successwe close the stream without writing anything, + // which acknowledges successful startup to the client. + } + Err(e) => { + let msg = e.to_string(); + match client + .write_all(msg.as_bytes()) + .and_then(|_| client.flush()) + { + Ok(_) => {} + Err(inner_err) => { + eprintln!( + "Failed to write error message to client socket (original error: {}): {}", + e, inner_err + ); + } + } + } + } +} + +fn process_clients(listener: UnixListener, res: &Result<()>) { + for stream in listener.incoming() { + match stream { + Ok(stream) => send_init_result_to_client(&stream, res), + Err(e) => { + // This shouldn't be fatal, we continue to start up. + eprintln!("Failed to listen for client stream: {}", e); + } + } + if res.is_err() { + break; + } + } +} + +/// 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<()> { + 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()); + + let mut fds = systemd::daemon::listen_fds(false)?.iter(); + let listener = match fds.next() { + None => { + // If started directly via `systemctl start` or DBus activation, we + // directly propagate the error back to our exit code. + init_res?; + UnixListener::bind("/run/rpmostreed.socket")? + } + Some(fd) => { + if fds.next().is_some() { + return Err(anyhow!("Expected exactly 1 fd from systemd activation")); + } + let listener = unsafe { UnixListener::from_raw_fd(fd) }; + match init_res { + Ok(_) => listener, + Err(e) => { + let err_copy = Err(anyhow!("{}", e)); + process_clients(listener, &err_copy); + return Err(e); + } + } + } + }; + + // On success, we spawn a helper thread that just responds with + // sucess to clients that connect via the socket. In the future, + // perhaps we'll expose an API here. + std::thread::spawn(move || process_clients(listener, &Ok(()))); + + // And now, enter the main loop. + Ok(crate::ffi::daemon_main_inner()?) +} + /// Get a currently unique (for this host) identifier for the /// deployment; TODO - adding the deployment timestamp would make it /// persistently unique, needs API in libostree. diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 6bd6de2574..680656f4ac 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -183,6 +183,8 @@ pub mod ffi { // daemon.rs extern "Rust" { fn daemon_sanitycheck_environment(sysroot: Pin<&mut OstreeSysroot>) -> Result<()>; + fn daemon_main(debug: bool) -> Result<()>; + fn start_daemon_via_socket() -> Result<()>; fn deployment_generate_id(deployment: Pin<&mut OstreeDeployment>) -> String; fn deployment_populate_variant( mut sysroot: Pin<&mut OstreeSysroot>, @@ -474,6 +476,12 @@ pub mod ffi { fn main_print_error(msg: &str); } + unsafe extern "C++" { + include!("rpmostreed-daemon.h"); + 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/src/app/libmain.cxx b/src/app/libmain.cxx index 0eb0ee7462..92c660fc9e 100644 --- a/src/app/libmain.cxx +++ b/src/app/libmain.cxx @@ -296,6 +296,8 @@ rpmostree_option_context_parse (GOptionContext *context, } } + rpmostreecxx::start_daemon_via_socket(); + /* root never needs to auth */ if (getuid () != 0) /* ignore errors; we print out a warning if we fail to spawn pkttyagent */ diff --git a/src/app/rpmostree-builtin-start-daemon.cxx b/src/app/rpmostree-builtin-start-daemon.cxx index 1fe9f7d0b7..1368e02b74 100644 --- a/src/app/rpmostree-builtin-start-daemon.cxx +++ b/src/app/rpmostree-builtin-start-daemon.cxx @@ -33,6 +33,7 @@ #include "rpmostree-builtins.h" #include "rpmostree-util.h" +#include "rpmostreed-utils.h" #include "rpmostreed-daemon.h" #include "rpmostree-libbuiltin.h" @@ -53,6 +54,7 @@ static GOptionEntry opt_entries[] = { NULL } }; +static GDBusConnection *bus = NULL; static RpmostreedDaemon *rpm_ostree_daemon = NULL; static void @@ -225,20 +227,14 @@ on_log_handler (const gchar *log_domain, sd_journal_print (priority, "%s", message); } -gboolean -rpmostree_builtin_start_daemon (int argc, - char **argv, - RpmOstreeCommandInvocation *invocation, - GCancellable *cancellable, - GError **error) +namespace rpmostreecxx { +// 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(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) + 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), on_log_debug, NULL); @@ -258,18 +254,24 @@ rpmostree_builtin_start_daemon (int argc, 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)) { - if (*error) - sd_notifyf (0, "STATUS=error: %s", (*error)->message); - return FALSE; - } + util::throw_gerror(local_error); + if (!start_daemon (bus, &local_error)) + { + 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) @@ -303,6 +305,23 @@ rpmostree_builtin_start_daemon (int argc, 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..020c640360 --- /dev/null +++ b/src/daemon/rpm-ostreed.socket @@ -0,0 +1,9 @@ +[Unit] +ConditionKernelCommandLine=ostree + +[Socket] +ListenStream=/run/rpm-ostree/client.sock +SocketMode=0600 + +[Install] +WantedBy=sockets.target diff --git a/src/daemon/rpmostreed-daemon.h b/src/daemon/rpmostreed-daemon.h index 269d40d9d8..e39a95f026 100644 --- a/src/daemon/rpmostreed-daemon.h +++ b/src/daemon/rpmostreed-daemon.h @@ -36,6 +36,12 @@ G_BEGIN_DECLS #define RPMOSTREE_DRIVER_SD_UNIT "driver-sd-unit" #define RPMOSTREE_DRIVER_NAME "driver-name" +/* Note: Currently actually defined in rpmostree-builtin-start-daemon.cxx for historical reasons */ +namespace rpmostreecxx { +void daemon_init_inner (bool debug); +void daemon_main_inner (); +} + GType rpmostreed_daemon_get_type (void) G_GNUC_CONST; RpmostreedDaemon * rpmostreed_daemon_get (void); GDBusConnection * rpmostreed_daemon_connection (void);