/
githubmirror
/
rpm-ostree
Обзор
Документация
Войти
/
githubmirror
/
rpm-ostree
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
rust/src/client.rs
367 строк
12 KB
Colin Walters
tree-wide: `cargo clippy --fix` + a few manual warning fixes
19 дек 2024, 00:21
19 дек 2024, 00:21
14dd3de
Код
Авторство
О чём код?
//! Helpers for the client side binary that will speak DBus //! to rpm-ostreed.service. // SPDX-License-Identifier: Apache-2.0 OR MIT use crate::core::OSTREE_BOOTED; use crate::cxxrsutil::*; use crate::ffi::SystemHostType; use crate::utils; use anyhow::{anyhow, Result}; 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::process::Command; /// The well-known bus name. const BUS_NAME: &str = "org.projectatomic.rpmostree1"; /// The global sysroot path 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"; /// A unique DBus connection to the rpm-ostree daemon. /// This currently wraps a C++ client connection. pub(crate) struct ClientConnection { conn: cxx::UniquePtr<crate::ffi::ClientConnection>, #[allow(dead_code)] sysroot_proxy: gio::DBusProxy, #[allow(dead_code)] booted_proxy: gio::DBusProxy, booted_ex_proxy: gio::DBusProxy, } impl ClientConnection { /// Create a new connection object. pub(crate) fn new() -> Result<Self> { require_system_host_type(SystemHostType::OstreeHost)?; let mut conn = crate::ffi::new_client_connection()?; let bus_conn = conn.pin_mut().get_connection(); let bus_conn = bus_conn.glib_reborrow(); // This proxy will synchronously load the properties, including Booted that // we depend on for the next proxy. let sysroot_proxy = gio::DBusProxy::new_sync( &bus_conn, gio::DBusProxyFlags::NONE, None, Some(BUS_NAME), SYSROOT_PATH, "org.projectatomic.rpmostree1.Sysroot", gio::Cancellable::NONE, )?; // Today the daemon mode requires running inside a booted deployment. let booted = sysroot_proxy .cached_property("Booted") .ok_or_else(|| anyhow!("Failed to find booted property"))?; let booted = booted .str() .ok_or_else(|| anyhow!("Booted sysroot is not a string"))?; let booted_proxy = gio::DBusProxy::new_sync( &bus_conn, gio::DBusProxyFlags::NONE, None, Some(BUS_NAME), booted, OS_INTERFACE, gio::Cancellable::NONE, )?; let booted_ex_proxy = gio::DBusProxy::new_sync( &bus_conn, gio::DBusProxyFlags::NONE, None, Some(BUS_NAME), booted, OS_EX_INTERFACE, gio::Cancellable::NONE, )?; Ok(Self { conn, sysroot_proxy, booted_proxy, booted_ex_proxy, }) } /// Returns a proxy for the sysroot #[allow(dead_code)] pub(crate) fn get_sysroot_proxy(&self) -> &gio::DBusProxy { &self.sysroot_proxy } /// Returns a proxy for the booted stateroot (os) #[allow(dead_code)] pub(crate) fn get_os_proxy(&self) -> &gio::DBusProxy { &self.booted_proxy } /// Returns a proxy for the experimental interface to the booted stateroot (os) pub(crate) fn get_os_ex_proxy(&self) -> &gio::DBusProxy { &self.booted_ex_proxy } /// Connect to a transaction (which is a DBus socket) and monitor its progress, /// printing the results to stdout. A signal handler is installed for Ctrl-C/SIGINT /// which will cancel the transaction. pub(crate) fn transaction_connect_progress_sync(&mut self, address: &str) -> Result<()> { Ok(self .conn .pin_mut() .transaction_connect_progress_sync(address)?) } } pub(crate) fn is_http_arg(arg: &str) -> bool { arg.starts_with("https://") || arg.starts_with("http://") } pub(crate) fn is_rpm_arg(arg: &str) -> bool { arg.ends_with(".rpm") || arg.starts_with("file://") } pub(crate) fn is_src_rpm_arg(arg: &str) -> bool { arg.ends_with(".src.rpm") } /// Given a string from the command line, determine if it represents one or more /// RPM URLs we need to fetch, and if so download those URLs and return file /// descriptors for the content. /// TODO(cxx-rs): This would be slightly more elegant as Result<Option<Vec<i32>>> #[context("Handling argument {}", arg)] #[allow(unused_variables)] // Because of the #[cfg] below pub(crate) fn client_handle_fd_argument( arg: &str, arch: &str, is_replace: bool, ) -> CxxResult<Vec<i32>> { #[cfg(feature = "fedora-integration")] if let Some(fds) = crate::fedora_integration::handle_cli_arg(arg, arch, is_replace)? { return Ok(fds.into_iter().map(|f| f.into_raw_fd()).collect()); } if is_src_rpm_arg(arg) { return Err(anyhow!( "{} appears to be a source RPM which are not usually intended to be installed", arg ) .into()); } else if is_http_arg(arg) { Ok(utils::download_url_to_tmpfile(arg, true).map(|f| vec![f.into_raw_fd()])?) } else if is_rpm_arg(arg) { match arg.strip_prefix("file://") { Some(rest) => Ok(vec![std::fs::File::open(rest)?.into_raw_fd()]), None => Ok(vec![std::fs::File::open(arg)?.into_raw_fd()]), } } else { Ok(Vec::new()) } } /// 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(()); } // 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 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()); } Ok(()) } /// 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 .glib_reborrow() .get::<( (u64, u64), (u32, u32), (u32, u32, u32), (u32, u32, u32, u64), (u32, u32), (u64, u64), )>() .unwrap(); let ( (_start_time, _elapsed_secs), (outstanding_fetches, outstanding_writes), (n_scanned_metadata, metadata_fetched, outstanding_metadata_fetches), (total_delta_parts, fetched_delta_parts, _total_delta_superblocks, total_delta_part_size), (fetched, requested), (bytes_transferred, bytes_sec), ) = progress; if outstanding_fetches > 0 { let bytes_transferred_str = glib::format_size_full(bytes_transferred, glib::FormatSizeFlags::empty()); let bytes_sec_str = glib::format_size(bytes_sec); let bytes_sec_str = if bytes_sec == 0 { "-" } else { bytes_sec_str.as_str() }; if total_delta_parts > 0 { let total_str = glib::format_size(total_delta_part_size); format!( "Receiving delta parts: {}/{} {}/s {}/{}", fetched_delta_parts, total_delta_parts, bytes_sec_str, bytes_transferred_str, total_str ) } else if outstanding_metadata_fetches > 0 { format!( "Receiving metadata objects: {}/(estimating) {}/s {}", metadata_fetched, bytes_sec_str, bytes_transferred_str ) } else { let percent = (((fetched as f64) / requested as f64) * 100f64) as u32; format!( "Receiving objects; {}% ({}/{}) {}/s {}", percent, fetched, requested, bytes_sec_str, bytes_transferred_str ) } } else if outstanding_writes > 0 { format!("Writing objects: {outstanding_writes}") } else { format!("Scanning metadata: {n_scanned_metadata}") } } pub(crate) fn running_in_container() -> bool { ostree_ext::container_utils::running_in_container() } pub(crate) fn is_bare_split_xattrs() -> CxxResult<bool> { Ok(ostree_ext::container_utils::is_bare_split_xattrs()?) } pub(crate) fn is_ostree_container() -> CxxResult<bool> { Ok(ostree_ext::container_utils::is_ostree_container()?) } pub(crate) fn get_system_host_type() -> CxxResult<SystemHostType> { let r = if ostree_ext::container_utils::is_ostree_container()? { SystemHostType::OstreeContainer } else if std::path::Path::new(OSTREE_BOOTED).exists() { SystemHostType::OstreeHost } else { SystemHostType::Unknown }; Ok(r) } pub(crate) fn system_host_type_str(t: &SystemHostType) -> &'static str { match *t { SystemHostType::OstreeContainer => "ostree container", SystemHostType::OstreeHost => "ostree host", _ => "unknown", } } /// Return an error if the current system host type does not match expected. pub(crate) fn require_system_host_type(expected: SystemHostType) -> CxxResult<()> { let current = get_system_host_type()?; if current != expected { let expected = system_host_type_str(&expected); let current = system_host_type_str(¤t); return Err(format!("This command requires an {expected} system; found: {current}").into()); } Ok(()) } /// Emit a warning about a potential future incompatible change we may make /// that would require adjustment from the user. pub fn warn_future_incompatibility(msg: impl AsRef<str>) { let msg = msg.as_ref(); eprintln!("warning: {msg}"); std::thread::sleep(std::time::Duration::from_secs(1)); } fn is_yes(s: &str) -> bool { matches!(s.to_lowercase().as_str(), "y" | "yes") } /// Prompt for confirmation pub(crate) fn confirm() -> CxxResult<bool> { let mut stdout = std::io::stdout().lock(); let mut stdin = std::io::stdin().lock(); write!(stdout, "Continue? [y/N] ")?; stdout.flush()?; let mut resp = String::new(); stdin.read_line(&mut resp)?; Ok(is_yes(resp.as_str().trim())) } /// Prompt for confirmation, and return an error if not agreed pub(crate) fn confirm_or_abort() -> CxxResult<()> { if confirm()? { Ok(()) } else { Err(anyhow::format_err!("Operation aborted").into()) } } #[cfg(test)] mod tests { use super::*; use std::{env, path::Path}; #[test] fn test_is_src_rpm() { let rpm = "https://fedora.org/rpms/src/kernel-2.6.1.rpm"; let src_rpm = "file://linux-kernel-2.2.2.src.rpm"; assert!(!is_src_rpm_arg(rpm)); assert!(is_src_rpm_arg(src_rpm)); } #[test] fn test_yes() { for v in ["y", "yes"] { assert!(is_yes(v)) } for v in ["", "n", "no", "DOIT"] { assert!(!is_yes(v)); } } #[test] fn test_running_in_container() { // See also https://bugzilla.redhat.com/show_bug.cgi?id=2278652 let container = env::var_os("container").is_some() || Path::new("/run/.containerenv").exists() || Path::new("/.dockerenv").exists(); assert_eq!(container, running_in_container()); } }