diff --git a/Cargo.toml b/Cargo.toml index f82e150235..4ca6c44ac6 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" cap-tempfile = "1.0.15" chrono = { version = "0.4.26", features = ["serde"] } 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..df1fb921b7 100644 --- a/rust/src/client.rs +++ b/rust/src/client.rs @@ -6,7 +6,7 @@ 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 fn_error_context::context; use gio::prelude::*; use ostree_ext::{gio, glib}; @@ -49,7 +49,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 +157,70 @@ 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. +/// 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<()> { + use cap_std::io_lifetimes::IntoSocketlike; + + futures::executor::block_on(async { + let c = tokio::net::UnixStream::connect("/run/rpm-ostree/client.sock").await?; + Ok::<_, anyhow::Error>(c) + }) + .context("Connecting")?; + + let address = sockaddr()?; + let socket = rustix::net::socket( + rustix::net::AddressFamily::UNIX, + rustix::net::SocketType::STREAM, + 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()?, + }, + ) + .context("Writing ClientHello")?; + rustix::net::shutdown(&socket, rustix::net::Shutdown::Write).expect("shutdown"); + 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) +} + pub(crate) fn client_start_daemon() -> CxxResult<()> { - let service = "rpm-ostreed.service"; - // Assume non-root can't use systemd right now. + // 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(()); } - // 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"]) - .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. - return Ok(()); - } + let socket = "rpm-ostreed.socket"; let res = Command::new("systemctl") - .args(&["--no-ask-password", "start", service]) + .args(&["--no-ask-password", "start", socket]) .status()?; if !res.success() { let _ = Command::new("systemctl") - .args(&["--no-pager", "status", service]) + .args(&["--no-pager", "status", socket]) .status(); return Err(anyhow!("{}", res).into()); } - 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 b2eafcb4ce..83dd2f5565 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..1385b896c6 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::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::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,142 @@ 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 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 { OwnedFd::from_raw_fd(fd) }.into_socketlike(); + // 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(()) => { + dbg!(&listener); + 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 1439b9f2ff..294f685358 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -140,7 +140,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..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.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'