diff --git a/.github/workflows/ci-rust.yml b/.github/workflows/ci-rust.yml index 6094cf6f84..a4576120bd 100644 --- a/.github/workflows/ci-rust.yml +++ b/.github/workflows/ci-rust.yml @@ -78,6 +78,12 @@ jobs: - name: Install Rust toolchains run: rustup toolchain install stable + - name: Run clippy linter + run: cargo clippy + + - name: Run rustfmt + run: cargo fmt --all -- --check + - name: Install cargo-binstall uses: taiki-e/install-action@v2 with: @@ -89,9 +95,6 @@ jobs: - name: Run the tests run: cargo tarpaulin --out xml - - name: Lint formatting - run: cargo fmt --all -- --check - # send the code coverage for the Rust part to the coveralls.io - name: Coveralls GitHub Action uses: coverallsapp/github-action@v2 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f9481c5eb3..a1a1a8c5d2 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -47,6 +47,7 @@ dependencies = [ "agama-lib", "agama-locale-data", "anyhow", + "async-trait", "cidr", "gettext-rs", "log", @@ -328,9 +329,9 @@ checksum = "b4eb2cdb97421e01129ccb49169d8279ed21e829929144f4a22a6e54ac549ca1" [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" dependencies = [ "proc-macro2", "quote", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 6e2475d875..36ab33dc58 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -5,6 +5,10 @@ members = [ "agama-derive", "agama-lib", "agama-locale-data", - "agama-settings" + "agama-settings", ] +resolver = "2" +[workspace.package] +rust-version = "1.74" +edition = "2021" diff --git a/rust/agama-cli/src/logs.rs b/rust/agama-cli/src/logs.rs index 9b94adb8e3..7038eaa121 100644 --- a/rust/agama-cli/src/logs.rs +++ b/rust/agama-cli/src/logs.rs @@ -279,7 +279,7 @@ impl LogItem for LogCmd { /// Collect existing / requested paths which should already exist in the system. /// Turns them into list of log sources -fn paths_to_log_sources(paths: &Vec, tmp_dir: &TempDir) -> Vec> { +fn paths_to_log_sources(paths: &[String], tmp_dir: &TempDir) -> Vec> { let mut log_sources: Vec> = Vec::new(); for path in paths.iter() { @@ -293,10 +293,7 @@ fn paths_to_log_sources(paths: &Vec, tmp_dir: &TempDir) -> Vec, - tmp_dir: &TempDir, -) -> Vec> { +fn cmds_to_log_sources(commands: &[(String, String)], tmp_dir: &TempDir) -> Vec> { let mut log_sources: Vec> = Vec::new(); for cmd in commands.iter() { diff --git a/rust/agama-dbus-server/Cargo.toml b/rust/agama-dbus-server/Cargo.toml index e6e95c67a0..6877d621f4 100644 --- a/rust/agama-dbus-server/Cargo.toml +++ b/rust/agama-dbus-server/Cargo.toml @@ -2,13 +2,14 @@ name = "agama-dbus-server" version = "0.1.0" edition = "2021" +rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0" -agama-locale-data = { path="../agama-locale-data" } -agama-lib = { path="../agama-lib" } +agama-locale-data = { path = "../agama-locale-data" } +agama-lib = { path = "../agama-lib" } log = "0.4" simplelog = "0.12.1" systemd-journal-logger = "1.0" @@ -25,3 +26,4 @@ gettext-rs = { version = "0.7.0", features = ["gettext-system"] } regex = "1.10.2" once_cell = "1.18.0" macaddr = "1.0" +async-trait = "0.1.75" diff --git a/rust/agama-dbus-server/src/locale.rs b/rust/agama-dbus-server/src/l10n.rs similarity index 100% rename from rust/agama-dbus-server/src/locale.rs rename to rust/agama-dbus-server/src/l10n.rs diff --git a/rust/agama-dbus-server/src/locale/helpers.rs b/rust/agama-dbus-server/src/l10n/helpers.rs similarity index 100% rename from rust/agama-dbus-server/src/locale/helpers.rs rename to rust/agama-dbus-server/src/l10n/helpers.rs diff --git a/rust/agama-dbus-server/src/locale/keyboard.rs b/rust/agama-dbus-server/src/l10n/keyboard.rs similarity index 100% rename from rust/agama-dbus-server/src/locale/keyboard.rs rename to rust/agama-dbus-server/src/l10n/keyboard.rs diff --git a/rust/agama-dbus-server/src/locale/locale.rs b/rust/agama-dbus-server/src/l10n/locale.rs similarity index 100% rename from rust/agama-dbus-server/src/locale/locale.rs rename to rust/agama-dbus-server/src/l10n/locale.rs diff --git a/rust/agama-dbus-server/src/locale/timezone.rs b/rust/agama-dbus-server/src/l10n/timezone.rs similarity index 96% rename from rust/agama-dbus-server/src/locale/timezone.rs rename to rust/agama-dbus-server/src/l10n/timezone.rs index d032122d66..bfa7ddae8f 100644 --- a/rust/agama-dbus-server/src/locale/timezone.rs +++ b/rust/agama-dbus-server/src/l10n/timezone.rs @@ -131,11 +131,7 @@ mod tests { fn test_read_timezone_without_country() { let mut db = TimezonesDatabase::new(); db.read("es").unwrap(); - let timezone = db - .entries() - .into_iter() - .find(|tz| tz.code == "UTC") - .unwrap(); + let timezone = db.entries().iter().find(|tz| tz.code == "UTC").unwrap(); assert_eq!(timezone.country, None); } @@ -145,7 +141,7 @@ mod tests { db.read("en").unwrap(); let timezone = db .entries() - .into_iter() + .iter() .find(|tz| tz.code == "Europe/Kiev") .unwrap(); assert_eq!(timezone.country, Some("Ukraine".to_string())); diff --git a/rust/agama-dbus-server/src/lib.rs b/rust/agama-dbus-server/src/lib.rs index a4c9dc66c8..5c04239e62 100644 --- a/rust/agama-dbus-server/src/lib.rs +++ b/rust/agama-dbus-server/src/lib.rs @@ -1,4 +1,4 @@ pub mod error; -pub mod locale; +pub mod l10n; pub mod network; pub mod questions; diff --git a/rust/agama-dbus-server/src/main.rs b/rust/agama-dbus-server/src/main.rs index c890f5db71..3145228fd3 100644 --- a/rust/agama-dbus-server/src/main.rs +++ b/rust/agama-dbus-server/src/main.rs @@ -1,4 +1,7 @@ -use agama_dbus_server::{locale, locale::helpers, network, questions}; +use agama_dbus_server::{ + l10n::{self, helpers}, + network, questions, +}; use agama_lib::connection_to; use anyhow::Context; @@ -36,7 +39,7 @@ async fn main() -> Result<(), Box> { // When adding more services here, the order might be important. questions::export_dbus_objects(&connection).await?; log::info!("Started questions interface"); - locale::export_dbus_objects(&connection, &locale).await?; + l10n::export_dbus_objects(&connection, &locale).await?; log::info!("Started locale interface"); network::export_dbus_objects(&connection).await?; log::info!("Started network interface"); diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index 3ddf6687d9..0b3d00b5c2 100644 --- a/rust/agama-dbus-server/src/network/action.rs +++ b/rust/agama-dbus-server/src/network/action.rs @@ -2,6 +2,7 @@ use crate::network::model::Connection; use agama_lib::network::types::DeviceType; use tokio::sync::oneshot; use uuid::Uuid; +use zbus::zvariant::OwnedObjectPath; use super::error::NetworkStateError; @@ -15,19 +16,33 @@ pub type ControllerConnection = (Connection, Vec); #[derive(Debug)] pub enum Action { /// Add a new connection with the given name and type. - AddConnection(String, DeviceType), + AddConnection( + String, + DeviceType, + Responder>, + ), /// Gets a connection GetConnection(Uuid, Responder>), + /// Gets a connection + GetConnectionPath(String, Responder>), + /// Get connections paths + GetConnectionsPaths(Responder>), /// Gets a controller connection GetController( Uuid, Responder>, ), + /// Get devices paths + GetDevicesPaths(Responder>), /// Sets a controller's ports. It uses the Uuid of the controller and the IDs or interface names /// of the ports. - SetPorts(Uuid, Vec, Responder>), + SetPorts( + Uuid, + Box>, + Responder>, + ), /// Update a connection (replacing the old one). - UpdateConnection(Connection), + UpdateConnection(Box), /// Remove the connection with the given Uuid. RemoveConnection(String), /// Apply the current configuration. diff --git a/rust/agama-dbus-server/src/network/adapter.rs b/rust/agama-dbus-server/src/network/adapter.rs index b5d24c4250..c96ce8e649 100644 --- a/rust/agama-dbus-server/src/network/adapter.rs +++ b/rust/agama-dbus-server/src/network/adapter.rs @@ -1,8 +1,10 @@ use crate::network::NetworkState; +use async_trait::async_trait; use std::error::Error; /// A trait for the ability to read/write from/to a network service +#[async_trait] pub trait Adapter { - fn read(&self) -> Result>; - fn write(&self, network: &NetworkState) -> Result<(), Box>; + async fn read(&self) -> Result>; + async fn write(&self, network: &NetworkState) -> Result<(), Box>; } diff --git a/rust/agama-dbus-server/src/network/dbus.rs b/rust/agama-dbus-server/src/network/dbus.rs index ba95f4c9ac..30fe51b7aa 100644 --- a/rust/agama-dbus-server/src/network/dbus.rs +++ b/rust/agama-dbus-server/src/network/dbus.rs @@ -8,4 +8,4 @@ pub mod service; mod tree; pub use service::NetworkService; -pub(crate) use tree::{ObjectsRegistry, Tree}; +pub(crate) use tree::Tree; diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index 3cd5fb8e3d..be287a5776 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -3,614 +3,12 @@ //! This module contains the set of D-Bus interfaces that are exposed by [D-Bus network //! service](crate::NetworkService). -use super::ObjectsRegistry; -use crate::network::{ - action::Action, - error::NetworkStateError, - model::{ - BondConfig, Connection as NetworkConnection, ConnectionConfig, Device as NetworkDevice, - MacAddress, SecurityProtocol, WirelessConfig, WirelessMode, - }, -}; - -use agama_lib::network::types::SSID; -use std::{str::FromStr, sync::Arc}; -use tokio::sync::{mpsc::UnboundedSender, oneshot}; -use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard}; -use uuid::Uuid; -use zbus::{ - dbus_interface, - zvariant::{ObjectPath, OwnedObjectPath}, - SignalContext, -}; - +mod common; +mod connection_configs; +mod connections; +mod devices; mod ip_config; +pub use connection_configs::{Bond, Wireless}; +pub use connections::{Connection, Connections, Match}; +pub use devices::{Device, Devices}; pub use ip_config::Ip; - -/// D-Bus interface for the network devices collection -/// -/// It offers an API to query the devices collection. -pub struct Devices { - objects: Arc>, -} - -impl Devices { - /// Creates a Devices interface object. - /// - /// * `objects`: Objects paths registry. - pub fn new(objects: Arc>) -> Self { - Self { objects } - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Devices")] -impl Devices { - /// Returns the D-Bus paths of the network devices. - pub async fn get_devices(&self) -> Vec { - let objects = self.objects.lock().await; - objects - .devices_paths() - .iter() - .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) - .collect() - } -} - -/// D-Bus interface for a network device -/// -/// It offers an API to query basic networking devices information (e.g., the name). -pub struct Device { - device: NetworkDevice, -} - -impl Device { - /// Creates an interface object. - /// - /// * `device`: network device. - pub fn new(device: NetworkDevice) -> Self { - Self { device } - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Device")] -impl Device { - /// Device name. - /// - /// Kernel device name, e.g., eth0, enp1s0, etc. - #[dbus_interface(property)] - pub fn name(&self) -> &str { - &self.device.name - } - - /// Device type. - /// - /// Possible values: 0 = loopback, 1 = ethernet, 2 = wireless. - /// - /// See [agama_lib::network::types::DeviceType]. - #[dbus_interface(property, name = "Type")] - pub fn device_type(&self) -> u8 { - self.device.type_ as u8 - } -} - -/// D-Bus interface for the set of connections. -/// -/// It offers an API to query the connections collection. -pub struct Connections { - actions: Arc>>, - objects: Arc>, -} - -impl Connections { - /// Creates a Connections interface object. - /// - /// * `objects`: Objects paths registry. - pub fn new(objects: Arc>, actions: UnboundedSender) -> Self { - Self { - objects, - actions: Arc::new(Mutex::new(actions)), - } - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connections")] -impl Connections { - /// Returns the D-Bus paths of the network connections. - pub async fn get_connections(&self) -> Vec { - let objects = self.objects.lock().await; - objects - .connections_paths() - .iter() - .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) - .collect() - } - - /// Adds a new network connection. - /// - /// * `id`: connection name. - /// * `ty`: connection type (see [agama_lib::network::types::DeviceType]). - pub async fn add_connection(&mut self, id: String, ty: u8) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::AddConnection(id.clone(), ty.try_into()?)) - .unwrap(); - Ok(()) - } - - /// Returns the D-Bus path of the network connection. - /// - /// * `id`: connection ID. - pub async fn get_connection(&self, id: &str) -> zbus::fdo::Result { - let objects = self.objects.lock().await; - match objects.connection_path(id) { - Some(path) => Ok(path.into()), - None => Err(NetworkStateError::UnknownConnection(id.to_string()).into()), - } - } - - /// Removes a network connection. - /// - /// * `uuid`: connection UUID.. - pub async fn remove_connection(&mut self, id: &str) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::RemoveConnection(id.to_string())) - .unwrap(); - Ok(()) - } - - /// Applies the network configuration. - /// - /// It includes adding, updating and removing connections as needed. - pub async fn apply(&self) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions.send(Action::Apply).unwrap(); - Ok(()) - } - - /// Notifies than a new interface has been added. - #[dbus_interface(signal)] - pub async fn connection_added( - ctxt: &SignalContext<'_>, - id: &str, - path: &ObjectPath<'_>, - ) -> zbus::Result<()>; -} - -/// D-Bus interface for a network connection -/// -/// It offers an API to query a connection. -pub struct Connection { - actions: Arc>>, - connection: Arc>, -} - -impl Connection { - /// Creates a Connection interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - connection, - } - } - - /// Returns the underlying connection. - async fn get_connection(&self) -> MutexGuard { - self.connection.lock().await - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - async fn update_connection<'a>( - &self, - connection: MutexGuard<'a, NetworkConnection>, - ) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection")] -impl Connection { - /// Connection ID. - /// - /// Unique identifier of the network connection. It may or not be the same that the used by the - /// backend. For instance, when using NetworkManager (which is the only supported backend by - /// now), it uses the original ID but appending a number in case the ID is duplicated. - #[dbus_interface(property)] - pub async fn id(&self) -> String { - self.get_connection().await.id.to_string() - } - - /// Connection UUID. - /// - /// Unique identifier of the network connection. It may or not be the same that the used by the - /// backend. - #[dbus_interface(property)] - pub async fn uuid(&self) -> String { - self.get_connection().await.uuid.to_string() - } - - #[dbus_interface(property)] - pub async fn controller(&self) -> String { - let connection = self.get_connection().await; - match connection.controller { - Some(uuid) => uuid.to_string(), - None => "".to_string(), - } - } - - #[dbus_interface(property)] - pub async fn interface(&self) -> String { - let connection = self.get_connection().await; - connection.interface.as_deref().unwrap_or("").to_string() - } - - #[dbus_interface(property)] - pub async fn set_interface(&mut self, name: &str) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.interface = Some(name.to_string()); - self.update_connection(connection).await - } - - /// Custom mac-address - #[dbus_interface(property)] - pub async fn mac_address(&self) -> String { - self.get_connection().await.mac_address.to_string() - } - - #[dbus_interface(property)] - pub async fn set_mac_address(&mut self, mac_address: &str) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.mac_address = MacAddress::from_str(mac_address)?; - self.update_connection(connection).await - } -} - -/// D-Bus interface for Match settings -pub struct Match { - actions: Arc>>, - connection: Arc>, -} - -impl Match { - /// Creates a Match Settings interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - connection, - } - } - - /// Returns the underlying connection. - async fn get_connection(&self) -> MutexGuard { - self.connection.lock().await - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - async fn update_connection<'a>( - &self, - connection: MutexGuard<'a, NetworkConnection>, - ) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -/// D-Bus interface for Bond settings. -pub struct Bond { - actions: Arc>>, - uuid: Uuid, -} - -impl Bond { - /// Creates a Bond interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `uuid`: connection UUID. - pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - uuid, - } - } - - /// Gets the connection. - async fn get_connection(&self) -> Result { - let actions = self.actions.lock().await; - let (tx, rx) = oneshot::channel(); - actions.send(Action::GetConnection(self.uuid, tx)).unwrap(); - rx.await - .unwrap() - .ok_or(NetworkStateError::UnknownConnection(self.uuid.to_string())) - } - - /// Gets the bonding configuration. - pub async fn get_config(&self) -> Result { - let connection = self.get_connection().await?; - match connection.config { - ConnectionConfig::Bond(bond) => Ok(bond), - _ => panic!("Not a bond connection. This is most probably a bug."), - } - } - - /// Updates the bond configuration. - /// - /// * `func`: function to update the configuration. - pub async fn update_config(&self, func: F) -> Result<(), NetworkStateError> - where - F: FnOnce(&mut BondConfig), - { - let mut connection = self.get_connection().await?; - match &mut connection.config { - ConnectionConfig::Bond(bond) => func(bond), - _ => panic!("Not a bond connection. This is most probably a bug."), - } - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Bond")] -impl Bond { - /// Bonding mode. - #[dbus_interface(property)] - pub async fn mode(&self) -> zbus::fdo::Result { - let config = self.get_config().await?; - Ok(config.mode.to_string()) - } - - #[dbus_interface(property)] - pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { - let mode = mode.try_into()?; - self.update_config(|c| c.mode = mode).await?; - Ok(()) - } - - /// List of bonding options. - #[dbus_interface(property)] - pub async fn options(&self) -> zbus::fdo::Result { - let config = self.get_config().await?; - Ok(config.options.to_string()) - } - - #[dbus_interface(property)] - pub async fn set_options(&mut self, opts: &str) -> zbus::fdo::Result<()> { - let opts = opts.try_into()?; - self.update_config(|c| c.options = opts).await?; - Ok(()) - } - - /// List of bond ports. - /// - /// For the port names, it uses the interface name (preferred) or, as a fallback, - /// the connection ID of the port. - #[dbus_interface(property)] - pub async fn ports(&self) -> zbus::fdo::Result> { - let actions = self.actions.lock().await; - let (tx, rx) = oneshot::channel(); - actions.send(Action::GetController(self.uuid, tx)).unwrap(); - - let (_, ports) = rx.await.unwrap()?; - Ok(ports) - } - - #[dbus_interface(property)] - pub async fn set_ports(&mut self, ports: Vec) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - let (tx, rx) = oneshot::channel(); - actions - .send(Action::SetPorts(self.uuid, ports, tx)) - .unwrap(); - let result = rx.await.unwrap(); - Ok(result?) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Match")] -impl Match { - /// List of driver names to match. - #[dbus_interface(property)] - pub async fn driver(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.driver.clone() - } - - #[dbus_interface(property)] - pub async fn set_driver(&mut self, driver: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.driver = driver; - self.update_connection(connection).await - } - - /// List of paths to match agains the ID_PATH udev property of devices. - #[dbus_interface(property)] - pub async fn path(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.path.clone() - } - - #[dbus_interface(property)] - pub async fn set_path(&mut self, path: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.path = path; - self.update_connection(connection).await - } - /// List of interface names to match. - #[dbus_interface(property)] - pub async fn interface(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.interface.clone() - } - - #[dbus_interface(property)] - pub async fn set_interface(&mut self, interface: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.interface = interface; - self.update_connection(connection).await - } - - /// List of kernel options to match. - #[dbus_interface(property)] - pub async fn kernel(&self) -> Vec { - let connection = self.get_connection().await; - connection.match_config.kernel.clone() - } - - #[dbus_interface(property)] - pub async fn set_kernel(&mut self, kernel: Vec) -> zbus::fdo::Result<()> { - let mut connection = self.get_connection().await; - connection.match_config.kernel = kernel; - self.update_connection(connection).await - } -} - -/// D-Bus interface for wireless settings -pub struct Wireless { - actions: Arc>>, - connection: Arc>, -} - -impl Wireless { - /// Creates a Wireless interface object. - /// - /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - connection, - } - } - - /// Gets the wireless connection. - /// - /// Beware that it crashes when it is not a wireless connection. - async fn get_wireless(&self) -> MappedMutexGuard { - MutexGuard::map(self.connection.lock().await, |c| match &mut c.config { - ConnectionConfig::Wireless(config) => config, - _ => panic!("Not a wireless connection. This is most probably a bug."), - }) - } - - /// Updates the wireless configuration. - /// - /// * `func`: function to update the configuration. - pub async fn update_config(&self, func: F) -> Result<(), NetworkStateError> - where - F: FnOnce(&mut WirelessConfig), - { - let mut connection = self.connection.lock().await; - match &mut connection.config { - ConnectionConfig::Wireless(wireless) => func(wireless), - _ => panic!("Not a wireless connection. This is most probably a bug."), - } - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Wireless")] -impl Wireless { - /// Network SSID. - #[dbus_interface(property, name = "SSID")] - pub async fn ssid(&self) -> Vec { - let wireless = self.get_wireless().await; - wireless.ssid.clone().into() - } - - #[dbus_interface(property, name = "SSID")] - pub async fn set_ssid(&mut self, ssid: Vec) -> zbus::fdo::Result<()> { - self.update_config(|c| c.ssid = SSID(ssid)).await?; - Ok(()) - } - - /// Wireless connection mode. - /// - /// Possible values: "unknown", "adhoc", "infrastructure", "ap" or "mesh". - /// - /// See [crate::network::model::WirelessMode]. - #[dbus_interface(property)] - pub async fn mode(&self) -> String { - let wireless = self.get_wireless().await; - wireless.mode.to_string() - } - - #[dbus_interface(property)] - pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { - let mode: WirelessMode = mode.try_into()?; - self.update_config(|c| c.mode = mode).await?; - Ok(()) - } - - /// Password to connect to the wireless network. - #[dbus_interface(property)] - pub async fn password(&self) -> String { - let wireless = self.get_wireless().await; - wireless.password.clone().unwrap_or("".to_string()) - } - - #[dbus_interface(property)] - pub async fn set_password(&mut self, password: String) -> zbus::fdo::Result<()> { - self.update_config(|c| { - c.password = if password.is_empty() { - None - } else { - Some(password) - }; - }) - .await?; - Ok(()) - } - - /// Wireless security protocol. - /// - /// Possible values: "none", "owe", "ieee8021x", "wpa-psk", "sae", "wpa-eap", - /// "wpa-eap-suite-b192". - /// - /// See [crate::network::model::SecurityProtocol]. - #[dbus_interface(property)] - pub async fn security(&self) -> String { - let wireless = self.get_wireless().await; - wireless.security.to_string() - } - - #[dbus_interface(property)] - pub async fn set_security(&mut self, security: &str) -> zbus::fdo::Result<()> { - let security: SecurityProtocol = security - .try_into() - .map_err(|_| NetworkStateError::InvalidSecurityProtocol(security.to_string()))?; - self.update_config(|c| c.security = security).await?; - Ok(()) - } -} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs new file mode 100644 index 0000000000..e5a49f122c --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs @@ -0,0 +1,81 @@ +//! Traits to build network D-Bus interfaces. +//! +//! There are a set of operations that are shared by many D-Bus interfaces (retrieving or updating a connection, a configuration etc.). +//! The traits in this module implements the common pieces to make it easier to build new +//! interfaces and reduce code duplication. +//! +//! Note: it is not clear to us whether using traits or simple structs is better for this use case. +//! We could change the approach in the future. +use crate::network::{ + error::NetworkStateError, + model::{Connection as NetworkConnection, ConnectionConfig}, + Action, +}; +use async_trait::async_trait; +use tokio::sync::{mpsc::UnboundedSender, oneshot, MutexGuard}; +use uuid::Uuid; + +#[async_trait] +pub trait ConnectionInterface { + fn uuid(&self) -> Uuid; + + async fn actions(&self) -> MutexGuard>; + + async fn get_connection(&self) -> Result { + let actions = self.actions().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::GetConnection(self.uuid(), tx)) + .unwrap(); + rx.await + .unwrap() + .ok_or(NetworkStateError::UnknownConnection( + self.uuid().to_string(), + )) + } + + /// Updates the connection data in the NetworkSystem. + /// + /// * `func`: function to update the connection. + async fn update_connection(&self, func: F) -> Result<(), NetworkStateError> + where + F: FnOnce(&mut NetworkConnection) + std::marker::Send, + { + let mut connection = self.get_connection().await?; + func(&mut connection); + let actions = self.actions().await; + actions + .send(Action::UpdateConnection(Box::new(connection))) + .unwrap(); + Ok(()) + } +} + +#[async_trait] +pub trait ConnectionConfigInterface: ConnectionInterface { + async fn get_config(&self) -> Result + where + T: TryFrom, + { + let connection = self.get_connection().await?; + connection.config.try_into() + } + + async fn update_config(&self, func: F) -> Result<(), NetworkStateError> + where + F: FnOnce(&mut T) + std::marker::Send, + T: Into + + TryFrom + + std::marker::Send, + { + let mut connection = self.get_connection().await?; + let mut config: T = connection.config.clone().try_into()?; + func(&mut config); + connection.config = config.into(); + let actions = self.actions().await; + actions + .send(Action::UpdateConnection(Box::new(connection))) + .unwrap(); + Ok(()) + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs new file mode 100644 index 0000000000..c0967bb93e --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs @@ -0,0 +1,214 @@ +use agama_lib::network::types::SSID; +use async_trait::async_trait; +use std::sync::Arc; +use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex, MutexGuard}; +use uuid::Uuid; +use zbus::dbus_interface; + +use crate::network::{ + action::Action, + error::NetworkStateError, + model::{BondConfig, SecurityProtocol, WirelessConfig, WirelessMode}, +}; + +use super::common::{ConnectionConfigInterface, ConnectionInterface}; + +/// D-Bus interface for Bond settings. +pub struct Bond { + actions: Arc>>, + uuid: Uuid, +} + +impl Bond { + /// Creates a Bond interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: connection UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Bond")] +impl Bond { + /// Bonding mode. + #[dbus_interface(property)] + pub async fn mode(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.mode.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { + let mode = mode.try_into()?; + self.update_config::(|c| c.mode = mode) + .await?; + Ok(()) + } + + /// List of bonding options. + #[dbus_interface(property)] + pub async fn options(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.options.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_options(&mut self, opts: &str) -> zbus::fdo::Result<()> { + let opts = opts.try_into()?; + self.update_config::(|c| c.options = opts) + .await?; + Ok(()) + } + + /// List of bond ports. + /// + /// For the port names, it uses the interface name (preferred) or, as a fallback, + /// the connection ID of the port. + #[dbus_interface(property)] + pub async fn ports(&self) -> zbus::fdo::Result> { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions.send(Action::GetController(self.uuid, tx)).unwrap(); + + let (_, ports) = rx.await.unwrap()?; + Ok(ports) + } + + #[dbus_interface(property)] + pub async fn set_ports(&mut self, ports: Vec) -> zbus::fdo::Result<()> { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::SetPorts(self.uuid, Box::new(ports), tx)) + .unwrap(); + let result = rx.await.unwrap(); + Ok(result?) + } +} + +#[async_trait] +impl ConnectionInterface for Bond { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} + +impl ConnectionConfigInterface for Bond {} + +/// D-Bus interface for wireless settings +pub struct Wireless { + actions: Arc>>, + uuid: Uuid, +} + +impl Wireless { + /// Creates a Wireless interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: connection UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Wireless")] +impl Wireless { + /// Network SSID. + #[dbus_interface(property, name = "SSID")] + pub async fn ssid(&self) -> zbus::fdo::Result> { + let config = self.get_config::().await?; + Ok(config.ssid.into()) + } + + #[dbus_interface(property, name = "SSID")] + pub async fn set_ssid(&mut self, ssid: Vec) -> zbus::fdo::Result<()> { + self.update_config::(|c| c.ssid = SSID(ssid)) + .await?; + Ok(()) + } + + /// Wireless connection mode. + /// + /// Possible values: "unknown", "adhoc", "infrastructure", "ap" or "mesh". + /// + /// See [crate::network::model::WirelessMode]. + #[dbus_interface(property)] + pub async fn mode(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.mode.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_mode(&mut self, mode: &str) -> zbus::fdo::Result<()> { + let mode: WirelessMode = mode.try_into()?; + self.update_config::(|c| c.mode = mode) + .await?; + Ok(()) + } + + /// Password to connect to the wireless network. + #[dbus_interface(property)] + pub async fn password(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.password.unwrap_or_default()) + } + + #[dbus_interface(property)] + pub async fn set_password(&mut self, password: String) -> zbus::fdo::Result<()> { + self.update_config::(|c| { + c.password = if password.is_empty() { + None + } else { + Some(password) + }; + }) + .await?; + Ok(()) + } + + /// Wireless security protocol. + /// + /// Possible values: "none", "owe", "ieee8021x", "wpa-psk", "sae", "wpa-eap", + /// "wpa-eap-suite-b192". + /// + /// See [crate::network::model::SecurityProtocol]. + #[dbus_interface(property)] + pub async fn security(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.security.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_security(&mut self, security: &str) -> zbus::fdo::Result<()> { + let security: SecurityProtocol = security + .try_into() + .map_err(|_| NetworkStateError::InvalidSecurityProtocol(security.to_string()))?; + self.update_config::(|c| c.security = security) + .await?; + Ok(()) + } +} + +#[async_trait] +impl ConnectionInterface for Wireless { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} + +impl ConnectionConfigInterface for Wireless {} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs new file mode 100644 index 0000000000..9fa57d7e6f --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -0,0 +1,307 @@ +use async_trait::async_trait; +use std::{str::FromStr, sync::Arc}; +use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex, MutexGuard}; +use uuid::Uuid; +use zbus::{ + dbus_interface, + zvariant::{ObjectPath, OwnedObjectPath}, + SignalContext, +}; + +use super::common::ConnectionInterface; +use crate::network::{error::NetworkStateError, model::MacAddress, Action}; + +/// D-Bus interface for the set of connections. +/// +/// It offers an API to query the connections collection. +pub struct Connections { + actions: Arc>>, +} + +impl Connections { + /// Creates a Connections interface object. + /// + /// * `objects`: Objects paths registry. + pub fn new(actions: UnboundedSender) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connections")] +impl Connections { + /// Returns the D-Bus paths of the network connections. + pub async fn get_connections(&self) -> zbus::fdo::Result> { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions.send(Action::GetConnectionsPaths(tx)).unwrap(); + let result = rx.await.unwrap(); + Ok(result) + } + + /// Adds a new network connection. + /// + /// * `id`: connection name. + /// * `ty`: connection type (see [agama_lib::network::types::DeviceType]). + pub async fn add_connection( + &mut self, + id: String, + ty: u8, + #[zbus(signal_context)] ctxt: SignalContext<'_>, + ) -> zbus::fdo::Result { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::AddConnection(id.clone(), ty.try_into()?, tx)) + .unwrap(); + let path = rx.await.unwrap()?; + Self::connection_added(&ctxt, &id, &path).await?; + Ok(path) + } + + /// Returns the D-Bus path of the network connection. + /// + /// * `id`: connection ID. + pub async fn get_connection(&self, id: &str) -> zbus::fdo::Result { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::GetConnectionPath(id.to_string(), tx)) + .unwrap(); + let path = rx + .await + .unwrap() + .ok_or(NetworkStateError::UnknownConnection(id.to_string()))?; + Ok(path) + } + + /// Removes a network connection. + /// + /// * `uuid`: connection UUID.. + pub async fn remove_connection(&mut self, id: &str) -> zbus::fdo::Result<()> { + let actions = self.actions.lock().await; + actions + .send(Action::RemoveConnection(id.to_string())) + .unwrap(); + Ok(()) + } + + /// Applies the network configuration. + /// + /// It includes adding, updating and removing connections as needed. + pub async fn apply(&self) -> zbus::fdo::Result<()> { + let actions = self.actions.lock().await; + actions.send(Action::Apply).unwrap(); + Ok(()) + } + + /// Notifies than a new interface has been added. + #[dbus_interface(signal)] + pub async fn connection_added( + ctxt: &SignalContext<'_>, + id: &str, + path: &ObjectPath<'_>, + ) -> zbus::Result<()>; +} + +/// D-Bus interface for a network connection +/// +/// It offers an API to query a connection. +pub struct Connection { + actions: Arc>>, + uuid: Uuid, +} + +impl Connection { + /// Creates a Connection interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: network connection's UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection")] +impl Connection { + /// Connection ID. + /// + /// Unique identifier of the network connection. It may or not be the same that the used by the + /// backend. For instance, when using NetworkManager (which is the only supported backend by + /// now), it uses the original ID but appending a number in case the ID is duplicated. + #[dbus_interface(property)] + pub async fn id(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.id) + } + + /// Connection UUID. + /// + /// Unique identifier of the network connection. It may or not be the same that the used by the + /// backend. + #[dbus_interface(property)] + pub async fn uuid(&self) -> String { + self.uuid.to_string() + } + + #[dbus_interface(property)] + pub async fn controller(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + let result = match connection.controller { + Some(uuid) => uuid.to_string(), + None => "".to_string(), + }; + Ok(result) + } + + #[dbus_interface(property)] + pub async fn interface(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.interface.unwrap_or_default()) + } + + #[dbus_interface(property)] + pub async fn set_interface(&mut self, name: &str) -> zbus::fdo::Result<()> { + let interface = Some(name.to_string()); + self.update_connection(|c| c.interface = interface).await?; + Ok(()) + } + + /// Custom mac-address + #[dbus_interface(property)] + pub async fn mac_address(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.mac_address.to_string()) + } + + #[dbus_interface(property)] + pub async fn set_mac_address(&mut self, mac_address: &str) -> zbus::fdo::Result<()> { + let mac_address = MacAddress::from_str(mac_address)?; + self.update_connection(|c| c.mac_address = mac_address) + .await?; + Ok(()) + } + + /// Whether the network interface should be active or not + #[dbus_interface(property)] + pub async fn active(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.is_up()) + } + + #[dbus_interface(property)] + pub async fn set_active(&mut self, active: bool) -> zbus::fdo::Result<()> { + self.update_connection(|c| { + if active { + c.set_up(); + } else { + c.set_down(); + } + }) + .await?; + Ok(()) + } +} + +#[async_trait] +impl ConnectionInterface for Connection { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} + +/// D-Bus interface for Match settings +pub struct Match { + actions: Arc>>, + uuid: Uuid, +} + +impl Match { + /// Creates a Match Settings interface object. + /// + /// * `actions`: sending-half of a channel to send actions. + /// * `uuid`: nework connection's UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Match")] +impl Match { + /// List of driver names to match. + #[dbus_interface(property)] + pub async fn driver(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.driver) + } + + #[dbus_interface(property)] + pub async fn set_driver(&mut self, driver: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.driver = driver) + .await?; + Ok(()) + } + + /// List of paths to match agains the ID_PATH udev property of devices. + #[dbus_interface(property)] + pub async fn path(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.path) + } + + #[dbus_interface(property)] + pub async fn set_path(&mut self, path: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.path = path) + .await?; + Ok(()) + } + /// List of interface names to match. + #[dbus_interface(property)] + pub async fn interface(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.interface) + } + + #[dbus_interface(property)] + pub async fn set_interface(&mut self, interface: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.interface = interface) + .await?; + Ok(()) + } + + /// List of kernel options to match. + #[dbus_interface(property)] + pub async fn kernel(&self) -> zbus::fdo::Result> { + let connection = self.get_connection().await?; + Ok(connection.match_config.kernel) + } + + #[dbus_interface(property)] + pub async fn set_kernel(&mut self, kernel: Vec) -> zbus::fdo::Result<()> { + self.update_connection(|c| c.match_config.kernel = kernel) + .await?; + Ok(()) + } +} + +#[async_trait] +impl ConnectionInterface for Match { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs new file mode 100644 index 0000000000..8f29d84f35 --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs @@ -0,0 +1,71 @@ +use crate::network::{model::Device as NetworkDevice, Action}; +use std::sync::Arc; +use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex}; +use zbus::{dbus_interface, zvariant::OwnedObjectPath}; + +/// D-Bus interface for the network devices collection +/// +/// It offers an API to query the devices collection. +pub struct Devices { + actions: Arc>>, +} + +impl Devices { + /// Creates a Devices interface object. + /// + /// * `objects`: Objects paths registry. + pub fn new(actions: UnboundedSender) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Devices")] +impl Devices { + /// Returns the D-Bus paths of the network devices. + pub async fn get_devices(&self) -> zbus::fdo::Result> { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions.send(Action::GetDevicesPaths(tx)).unwrap(); + let result = rx.await.unwrap(); + Ok(result) + } +} + +/// D-Bus interface for a network device +/// +/// It offers an API to query basic networking devices information (e.g., the name). +pub struct Device { + device: NetworkDevice, +} + +impl Device { + /// Creates an interface object. + /// + /// * `device`: network device. + pub fn new(device: NetworkDevice) -> Self { + Self { device } + } +} + +#[dbus_interface(name = "org.opensuse.Agama1.Network.Device")] +impl Device { + /// Device name. + /// + /// Kernel device name, e.g., eth0, enp1s0, etc. + #[dbus_interface(property)] + pub fn name(&self) -> &str { + &self.device.name + } + + /// Device type. + /// + /// Possible values: 0 = loopback, 1 = ethernet, 2 = wireless. + /// + /// See [agama_lib::network::types::DeviceType]. + #[dbus_interface(property, name = "Type")] + pub fn device_type(&self) -> u8 { + self.device.type_ as u8 + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs index a0051c75c6..dfbfd6ad30 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs @@ -6,71 +6,51 @@ //! to the `Ip` struct. use crate::network::{ action::Action, - model::{Connection as NetworkConnection, IpConfig, Ipv4Method, Ipv6Method}, + error::NetworkStateError, + model::{IpConfig, Ipv4Method, Ipv6Method}, }; +use async_trait::async_trait; use cidr::IpInet; use std::{net::IpAddr, sync::Arc}; use tokio::sync::mpsc::UnboundedSender; -use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard}; +use tokio::sync::{Mutex, MutexGuard}; +use uuid::Uuid; use zbus::dbus_interface; +use super::common::ConnectionInterface; + /// D-Bus interface for IPv4 and IPv6 settings pub struct Ip { actions: Arc>>, - connection: Arc>, + uuid: Uuid, } impl Ip { /// Creates an IP interface object. /// /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { + /// * `uuid`: connection UUID.. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { Self { actions: Arc::new(Mutex::new(actions)), - connection, + uuid, } } - /// Returns the underlying connection. - async fn get_connection(&self) -> MutexGuard { - self.connection.lock().await - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - async fn update_connection<'a>( - &self, - connection: MutexGuard<'a, NetworkConnection>, - ) -> zbus::fdo::Result<()> { - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); - Ok(()) - } -} - -impl Ip { /// Returns the IpConfig struct. - async fn get_ip_config(&self) -> MappedMutexGuard { - MutexGuard::map(self.get_connection().await, |c| &mut c.ip_config) + async fn get_ip_config(&self) -> Result { + self.get_connection().await.map(|c| c.ip_config) } /// Updates the IpConfig struct. /// /// * `func`: function to update the configuration. - async fn update_config(&self, func: F) -> zbus::fdo::Result<()> + async fn update_ip_config(&self, func: F) -> zbus::fdo::Result<()> where - F: Fn(&mut IpConfig), + F: Fn(&mut IpConfig) + std::marker::Send, { - let mut connection = self.get_connection().await; - func(&mut connection.ip_config); - self.update_connection(connection).await?; + self.update_connection(move |c| func(&mut c.ip_config)) + .await?; Ok(()) } } @@ -81,15 +61,16 @@ impl Ip { /// /// When the method is 'auto', these addresses are used as additional addresses. #[dbus_interface(property)] - pub async fn addresses(&self) -> Vec { - let ip_config = self.get_ip_config().await; - ip_config.addresses.iter().map(|a| a.to_string()).collect() + pub async fn addresses(&self) -> zbus::fdo::Result> { + let ip_config = self.get_ip_config().await?; + let addresses = ip_config.addresses.iter().map(|a| a.to_string()).collect(); + Ok(addresses) } #[dbus_interface(property)] pub async fn set_addresses(&mut self, addresses: Vec) -> zbus::fdo::Result<()> { let addresses = helpers::parse_addresses::(addresses); - self.update_config(|ip| ip.addresses = addresses.clone()) + self.update_ip_config(|ip| ip.addresses = addresses.clone()) .await } @@ -99,15 +80,15 @@ impl Ip { /// /// See [crate::network::model::Ipv4Method]. #[dbus_interface(property)] - pub async fn method4(&self) -> String { - let ip_config = self.get_ip_config().await; - ip_config.method4.to_string() + pub async fn method4(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + Ok(ip_config.method4.to_string()) } #[dbus_interface(property)] pub async fn set_method4(&mut self, method: &str) -> zbus::fdo::Result<()> { let method: Ipv4Method = method.parse()?; - self.update_config(|ip| ip.method4 = method).await + self.update_ip_config(|ip| ip.method4 = method).await } /// IPv6 configuration method. @@ -116,32 +97,33 @@ impl Ip { /// /// See [crate::network::model::Ipv6Method]. #[dbus_interface(property)] - pub async fn method6(&self) -> String { - let ip_config = self.get_ip_config().await; - ip_config.method6.to_string() + pub async fn method6(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + Ok(ip_config.method6.to_string()) } #[dbus_interface(property)] pub async fn set_method6(&mut self, method: &str) -> zbus::fdo::Result<()> { let method: Ipv6Method = method.parse()?; - self.update_config(|ip| ip.method6 = method).await + self.update_ip_config(|ip| ip.method6 = method).await } /// Name server addresses. #[dbus_interface(property)] - pub async fn nameservers(&self) -> Vec { - let ip_config = self.get_ip_config().await; - ip_config + pub async fn nameservers(&self) -> zbus::fdo::Result> { + let ip_config = self.get_ip_config().await?; + let nameservers = ip_config .nameservers .iter() .map(IpAddr::to_string) - .collect() + .collect(); + Ok(nameservers) } #[dbus_interface(property)] pub async fn set_nameservers(&mut self, addresses: Vec) -> zbus::fdo::Result<()> { let addresses = helpers::parse_addresses::(addresses); - self.update_config(|ip| ip.nameservers = addresses.clone()) + self.update_ip_config(|ip| ip.nameservers = addresses.clone()) .await } @@ -149,36 +131,38 @@ impl Ip { /// /// An empty string removes the current value. #[dbus_interface(property)] - pub async fn gateway4(&self) -> String { - let ip_config = self.get_ip_config().await; - match ip_config.gateway4 { + pub async fn gateway4(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + let gateway = match ip_config.gateway4 { Some(ref address) => address.to_string(), None => "".to_string(), - } + }; + Ok(gateway) } #[dbus_interface(property)] pub async fn set_gateway4(&mut self, gateway: String) -> zbus::fdo::Result<()> { let gateway = helpers::parse_gateway(gateway)?; - self.update_config(|ip| ip.gateway4 = gateway).await + self.update_ip_config(|ip| ip.gateway4 = gateway).await } /// Network gateway for IPv6. /// /// An empty string removes the current value. #[dbus_interface(property)] - pub async fn gateway6(&self) -> String { - let ip_config = self.get_ip_config().await; - match ip_config.gateway6 { + pub async fn gateway6(&self) -> zbus::fdo::Result { + let ip_config = self.get_ip_config().await?; + let result = match ip_config.gateway6 { Some(ref address) => address.to_string(), None => "".to_string(), - } + }; + Ok(result) } #[dbus_interface(property)] pub async fn set_gateway6(&mut self, gateway: String) -> zbus::fdo::Result<()> { let gateway = helpers::parse_gateway(gateway)?; - self.update_config(|ip| ip.gateway6 = gateway).await + self.update_ip_config(|ip| ip.gateway6 = gateway).await } } @@ -229,3 +213,14 @@ mod helpers { } } } + +#[async_trait] +impl ConnectionInterface for Ip { + fn uuid(&self) -> Uuid { + self.uuid + } + + async fn actions(&self) -> MutexGuard> { + self.actions.lock().await + } +} diff --git a/rust/agama-dbus-server/src/network/dbus/service.rs b/rust/agama-dbus-server/src/network/dbus/service.rs index 9bbf17ffa7..b085cf4855 100644 --- a/rust/agama-dbus-server/src/network/dbus/service.rs +++ b/rust/agama-dbus-server/src/network/dbus/service.rs @@ -17,7 +17,6 @@ impl NetworkService { connection: &Connection, adapter: T, ) -> Result<(), Box> { - let connection = connection.clone(); let mut network = NetworkSystem::new(connection.clone(), adapter); tokio::spawn(async move { diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 45568fcf42..149d9ab1ef 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -1,10 +1,9 @@ use agama_lib::error::ServiceError; -use tokio::sync::Mutex; use zbus::zvariant::{ObjectPath, OwnedObjectPath}; use crate::network::{action::Action, dbus::interfaces, model::*}; use log; -use std::{collections::HashMap, sync::Arc}; +use std::collections::HashMap; use tokio::sync::mpsc::UnboundedSender; const CONNECTIONS_PATH: &str = "/org/opensuse/Agama1/Network/connections"; @@ -14,7 +13,7 @@ const DEVICES_PATH: &str = "/org/opensuse/Agama1/Network/devices"; pub struct Tree { connection: zbus::Connection, actions: UnboundedSender, - objects: Arc>, + objects: ObjectsRegistry, } impl Tree { @@ -37,7 +36,7 @@ impl Tree { /// /// * `connections`: list of connections. pub async fn set_connections( - &self, + &mut self, connections: &mut [Connection], ) -> Result<(), ServiceError> { self.remove_connections().await?; @@ -63,56 +62,42 @@ impl Tree { let path = ObjectPath::try_from(path.as_str()).unwrap(); self.add_interface(&path, interfaces::Device::new(dev.clone())) .await?; - let mut objects = self.objects.lock().await; - objects.register_device(&dev.name, path); + self.objects.register_device(&dev.name, path); } - self.add_interface( - DEVICES_PATH, - interfaces::Devices::new(Arc::clone(&self.objects)), - ) - .await?; + self.add_interface(DEVICES_PATH, interfaces::Devices::new(self.actions.clone())) + .await?; Ok(()) } - /// Adds a connection to the D-Bus tree. + /// Adds a connection to the D-Bus tree and returns the D-Bus path. /// - /// * `connection`: connection to add. + /// * `conn`: connection to add. /// * `notify`: whether to notify the added connection pub async fn add_connection( - &self, + &mut self, conn: &mut Connection, - notify: bool, - ) -> Result<(), ServiceError> { - let mut objects = self.objects.lock().await; - - let orig_id = conn.id.to_owned(); + ) -> Result { let uuid = conn.uuid; - let (id, path) = objects.register_connection(conn); + let (id, path) = self.objects.register_connection(conn); if id != conn.id { conn.id = id.clone(); } - log::info!("Publishing network connection '{}'", id); + let path: OwnedObjectPath = path.into(); + log::info!("Publishing network connection '{}' on '{}'", id, &path); - let cloned = Arc::new(Mutex::new(conn.clone())); self.add_interface( &path, - interfaces::Connection::new(self.actions.clone(), Arc::clone(&cloned)), + interfaces::Connection::new(self.actions.clone(), uuid), ) .await?; - self.add_interface( - &path, - interfaces::Ip::new(self.actions.clone(), Arc::clone(&cloned)), - ) - .await?; + self.add_interface(&path, interfaces::Ip::new(self.actions.clone(), uuid)) + .await?; - self.add_interface( - &path, - interfaces::Match::new(self.actions.clone(), Arc::clone(&cloned)), - ) - .await?; + self.add_interface(&path, interfaces::Match::new(self.actions.clone(), uuid)) + .await?; if let ConnectionConfig::Bond(_) = conn.config { self.add_interface(&path, interfaces::Bond::new(self.actions.clone(), uuid)) @@ -120,44 +105,53 @@ impl Tree { } if let ConnectionConfig::Wireless(_) = conn.config { - self.add_interface( - &path, - interfaces::Wireless::new(self.actions.clone(), Arc::clone(&cloned)), - ) - .await?; - } - - if notify { - self.notify_connection_added(&orig_id, &path).await?; + self.add_interface(&path, interfaces::Wireless::new(self.actions.clone(), uuid)) + .await?; } - Ok(()) + Ok(path) } /// Removes a connection from the tree /// /// * `id`: connection ID. pub async fn remove_connection(&mut self, id: &str) -> Result<(), ServiceError> { - let mut objects = self.objects.lock().await; - let Some(path) = objects.connection_path(id) else { + let Some(path) = self.objects.connection_path(id) else { return Ok(()); }; self.remove_connection_on(path.as_str()).await?; - objects.deregister_connection(id).unwrap(); + self.objects.deregister_connection(id).unwrap(); Ok(()) } + /// Returns all devices paths. + pub fn devices_paths(&self) -> Vec { + self.objects.devices_paths() + } + + /// Returns all connection paths. + pub fn connections_paths(&self) -> Vec { + self.objects.connections_paths() + } + + pub fn connection_path(&self, id: &str) -> Option { + self.objects.connection_path(id).map(|o| o.into()) + } + /// Adds connections to the D-Bus tree. /// /// * `connections`: list of connections. - async fn add_connections(&self, connections: &mut [Connection]) -> Result<(), ServiceError> { + async fn add_connections( + &mut self, + connections: &mut [Connection], + ) -> Result<(), ServiceError> { for conn in connections.iter_mut() { - self.add_connection(conn, false).await?; + self.add_connection(conn).await?; } self.add_interface( CONNECTIONS_PATH, - interfaces::Connections::new(Arc::clone(&self.objects), self.actions.clone()), + interfaces::Connections::new(self.actions.clone()), ) .await?; @@ -165,25 +159,23 @@ impl Tree { } /// Clears all the connections from the tree. - async fn remove_connections(&self) -> Result<(), ServiceError> { - let mut objects = self.objects.lock().await; - for path in objects.connections.values() { + async fn remove_connections(&mut self) -> Result<(), ServiceError> { + for path in self.objects.connections.values() { self.remove_connection_on(path.as_str()).await?; } - objects.connections.clear(); + self.objects.connections.clear(); Ok(()) } /// Clears all the devices from the tree. async fn remove_devices(&mut self) -> Result<(), ServiceError> { let object_server = self.connection.object_server(); - let mut objects = self.objects.lock().await; - for path in objects.devices.values() { + for path in self.objects.devices.values() { object_server .remove::(path.as_str()) .await?; } - objects.devices.clear(); + self.objects.devices.clear(); Ok(()) } @@ -201,33 +193,20 @@ impl Tree { Ok(()) } - async fn add_interface(&self, path: &str, iface: T) -> Result + async fn add_interface(&mut self, path: &str, iface: T) -> Result where T: zbus::Interface, { let object_server = self.connection.object_server(); Ok(object_server.at(path, iface).await?) } - - /// Notify that a new connection has been added - async fn notify_connection_added( - &self, - id: &str, - path: &ObjectPath<'_>, - ) -> Result<(), ServiceError> { - let object_server = self.connection.object_server(); - let iface_ref = object_server - .interface::<_, interfaces::Connections>(CONNECTIONS_PATH) - .await?; - Ok(interfaces::Connections::connection_added(iface_ref.signal_context(), id, path).await?) - } } /// Objects paths for known devices and connections /// /// Connections are indexed by its Id, which is expected to be unique. #[derive(Debug, Default)] -pub struct ObjectsRegistry { +struct ObjectsRegistry { /// device_name (eth0) -> object_path devices: HashMap, /// id -> object_path @@ -275,13 +254,13 @@ impl ObjectsRegistry { } /// Returns all devices paths. - pub fn devices_paths(&self) -> Vec { - self.devices.values().map(|p| p.to_string()).collect() + pub fn devices_paths(&self) -> Vec { + self.devices.values().cloned().collect() } /// Returns all connection paths. - pub fn connections_paths(&self) -> Vec { - self.connections.values().map(|p| p.to_string()).collect() + pub fn connections_paths(&self) -> Vec { + self.connections.values().cloned().collect() } /// Proposes a connection ID. diff --git a/rust/agama-dbus-server/src/network/error.rs b/rust/agama-dbus-server/src/network/error.rs index e3873eaa27..6b674b7efb 100644 --- a/rust/agama-dbus-server/src/network/error.rs +++ b/rust/agama-dbus-server/src/network/error.rs @@ -27,6 +27,8 @@ pub enum NetworkStateError { InvalidBondOptions, #[error("Not a controller connection: '{0}'")] NotControllerConnection(String), + #[error("Unexpected configuration")] + UnexpectedConfiguration, } impl From for zbus::fdo::Error { diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index e37237f381..0205b2fe2b 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -412,6 +412,18 @@ pub enum ConnectionConfig { Bond(BondConfig), } +impl From for ConnectionConfig { + fn from(value: BondConfig) -> Self { + Self::Bond(value) + } +} + +impl From for ConnectionConfig { + fn from(value: WirelessConfig) -> Self { + Self::Wireless(value) + } +} + #[derive(Debug, Error)] #[error("Invalid MAC address: {0}")] pub struct InvalidMacAddress(String); @@ -613,6 +625,17 @@ pub struct WirelessConfig { pub security: SecurityProtocol, } +impl TryFrom for WirelessConfig { + type Error = NetworkStateError; + + fn try_from(value: ConnectionConfig) -> Result { + match value { + ConnectionConfig::Wireless(config) => Ok(config), + _ => Err(NetworkStateError::UnexpectedConfiguration), + } + } +} + #[derive(Debug, Default, Clone, Copy, PartialEq)] pub enum WirelessMode { Unknown = 0, @@ -735,3 +758,14 @@ pub struct BondConfig { pub mode: BondMode, pub options: BondOptions, } + +impl TryFrom for BondConfig { + type Error = NetworkStateError; + + fn try_from(value: ConnectionConfig) -> Result { + match value { + ConnectionConfig::Bond(config) => Ok(config), + _ => Err(NetworkStateError::UnexpectedConfiguration), + } + } +} diff --git a/rust/agama-dbus-server/src/network/nm/adapter.rs b/rust/agama-dbus-server/src/network/nm/adapter.rs index 6aa590fb0e..eb42b36afb 100644 --- a/rust/agama-dbus-server/src/network/nm/adapter.rs +++ b/rust/agama-dbus-server/src/network/nm/adapter.rs @@ -4,8 +4,8 @@ use crate::network::{ Adapter, }; use agama_lib::error::ServiceError; +use async_trait::async_trait; use log; -use tokio::{runtime::Handle, task}; /// An adapter for NetworkManager pub struct NetworkManagerAdapter<'a> { @@ -27,16 +27,12 @@ impl<'a> NetworkManagerAdapter<'a> { } } +#[async_trait] impl<'a> Adapter for NetworkManagerAdapter<'a> { - fn read(&self) -> Result> { - task::block_in_place(|| { - Handle::current().block_on(async { - let devices = self.client.devices().await?; - let connections = self.client.connections().await?; - - Ok(NetworkState::new(devices, connections)) - }) - }) + async fn read(&self) -> Result> { + let devices = self.client.devices().await?; + let connections = self.client.connections().await?; + Ok(NetworkState::new(devices, connections)) } /// Writes the connections to NetworkManager. @@ -46,31 +42,27 @@ impl<'a> Adapter for NetworkManagerAdapter<'a> { /// simpler approach. /// /// * `network`: network model. - fn write(&self, network: &NetworkState) -> Result<(), Box> { + async fn write(&self, network: &NetworkState) -> Result<(), Box> { // By now, traits do not support async functions. Using `task::block_on` allows // to use 'await'. - task::block_in_place(|| { - Handle::current().block_on(async { - for conn in ordered_connections(network) { - if !Self::is_writable(conn) { - continue; - } + for conn in ordered_connections(network) { + if !Self::is_writable(conn) { + continue; + } - let result = if conn.is_removed() { - self.client.remove_connection(conn.uuid).await - } else { - let ctrl = conn - .controller - .and_then(|uuid| network.get_connection_by_uuid(uuid)); - self.client.add_or_update_connection(conn, ctrl).await - }; + let result = if conn.is_removed() { + self.client.remove_connection(conn.uuid).await + } else { + let ctrl = conn + .controller + .and_then(|uuid| network.get_connection_by_uuid(uuid)); + self.client.add_or_update_connection(conn, ctrl).await + }; - if let Err(e) = result { - log::error!("Could not process the connection {}: {}", conn.id, e); - } - } - }) - }); + if let Err(e) = result { + log::error!("Could not process the connection {}: {}", conn.id, e); + } + } // FIXME: indicate which connections could not be written. Ok(()) } diff --git a/rust/agama-dbus-server/src/network/nm/dbus.rs b/rust/agama-dbus-server/src/network/nm/dbus.rs index df3ce6cdf8..3dc7846a38 100644 --- a/rust/agama-dbus-server/src/network/nm/dbus.rs +++ b/rust/agama-dbus-server/src/network/nm/dbus.rs @@ -781,10 +781,7 @@ mod test { )])); let dbus_conn = HashMap::from([ - ( - "connection".to_string(), - connection_section.try_into().unwrap(), - ), + ("connection".to_string(), connection_section), (BOND_KEY.to_string(), bond_options.try_into().unwrap()), ]); diff --git a/rust/agama-dbus-server/src/network/nm/proxies.rs b/rust/agama-dbus-server/src/network/nm/proxies.rs index 8bfa6f43bb..fd933a3d9d 100644 --- a/rust/agama-dbus-server/src/network/nm/proxies.rs +++ b/rust/agama-dbus-server/src/network/nm/proxies.rs @@ -12,6 +12,7 @@ //! …consequently `zbus-xmlgen` did not generate code for the above interfaces. //! Also some proxies can be used against multiple services when they share interface. +use agama_lib::dbus::OwnedNestedHash; use zbus::dbus_proxy; #[dbus_proxy( @@ -267,16 +268,7 @@ trait Device { fn disconnect(&self) -> zbus::Result<()>; /// GetAppliedConnection method - fn get_applied_connection( - &self, - flags: u32, - ) -> zbus::Result<( - std::collections::HashMap< - String, - std::collections::HashMap, - >, - u64, - )>; + fn get_applied_connection(&self, flags: u32) -> zbus::Result<(OwnedNestedHash, u64)>; /// Reapply method fn reapply( diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index 4500c3be43..5272d58596 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -1,8 +1,13 @@ use super::error::NetworkStateError; use crate::network::{dbus::Tree, model::Connection, Action, Adapter, NetworkState}; -use std::error::Error; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use agama_lib::network::types::DeviceType; +use std::{error::Error, sync::Arc}; +use tokio::sync::{ + mpsc::{self, UnboundedReceiver, UnboundedSender}, + Mutex, +}; use uuid::Uuid; +use zbus::zvariant::OwnedObjectPath; /// Represents the network system using holding the state and setting up the D-Bus tree. pub struct NetworkSystem { @@ -11,7 +16,7 @@ pub struct NetworkSystem { /// Side of the channel to send actions. actions_tx: UnboundedSender, actions_rx: UnboundedReceiver, - tree: Tree, + tree: Arc>, /// Adapter to read/write the network state. adapter: T, } @@ -24,15 +29,15 @@ impl NetworkSystem { state: NetworkState::default(), actions_tx, actions_rx, - tree, + tree: Arc::new(Mutex::new(tree)), adapter, } } /// Writes the network configuration. pub async fn write(&mut self) -> Result<(), Box> { - self.adapter.write(&self.state)?; - self.state = self.adapter.read()?; + self.adapter.write(&self.state).await?; + self.state = self.adapter.read().await?; Ok(()) } @@ -45,11 +50,10 @@ impl NetworkSystem { /// Populates the D-Bus tree with the known devices and connections. pub async fn setup(&mut self) -> Result<(), Box> { - self.state = self.adapter.read()?; - self.tree - .set_connections(&mut self.state.connections) - .await?; - self.tree.set_devices(&self.state.devices).await?; + self.state = self.adapter.read().await?; + let mut tree = self.tree.lock().await; + tree.set_connections(&mut self.state.connections).await?; + tree.set_devices(&self.state.devices).await?; Ok(()) } @@ -67,43 +71,80 @@ impl NetworkSystem { /// Dispatch an action. pub async fn dispatch_action(&mut self, action: Action) -> Result<(), Box> { match action { - Action::AddConnection(name, ty) => { - let mut conn = Connection::new(name, ty); - self.tree.add_connection(&mut conn, true).await?; - self.state.add_connection(conn)?; + Action::AddConnection(name, ty, tx) => { + let result = self.add_connection_action(name, ty).await; + tx.send(result).unwrap(); } - Action::GetConnection(uuid, rx) => { + Action::GetConnection(uuid, tx) => { let conn = self.state.get_connection_by_uuid(uuid); - rx.send(conn.cloned()).unwrap(); + tx.send(conn.cloned()).unwrap(); } - Action::GetController(uuid, rx) => { + Action::GetConnectionPath(id, tx) => { + let tree = self.tree.lock().await; + let path = tree.connection_path(&id); + tx.send(path).unwrap(); + } + Action::GetController(uuid, tx) => { let result = self.get_controller_action(uuid); - rx.send(result).unwrap() + tx.send(result).unwrap() + } + Action::GetDevicesPaths(tx) => { + let tree = self.tree.lock().await; + tx.send(tree.devices_paths()).unwrap(); + } + Action::GetConnectionsPaths(tx) => { + let tree = self.tree.lock().await; + tx.send(tree.connections_paths()).unwrap(); } Action::SetPorts(uuid, ports, rx) => { - let result = self.set_ports_action(uuid, ports); + let result = self.set_ports_action(uuid, *ports); rx.send(result).unwrap(); } Action::UpdateConnection(conn) => { - self.state.update_connection(conn)?; + self.state.update_connection(*conn)?; } Action::RemoveConnection(id) => { - self.tree.remove_connection(&id).await?; + let mut tree = self.tree.lock().await; + tree.remove_connection(&id).await?; self.state.remove_connection(&id)?; } Action::Apply => { self.write().await?; // TODO: re-creating the tree is kind of brute-force and it sends signals about // adding/removing interfaces. We should add/update/delete objects as needed. - self.tree - .set_connections(&mut self.state.connections) - .await?; + // NOTE updating the tree at the same time than dispatching actions can cause a + // deadlock. We might consider using message passing too but at this point + // is enough to use a separate task. + let mut connections = self.state.connections.clone(); + let tree = Arc::clone(&self.tree); + tokio::spawn(async move { + let mut tree = tree.lock().await; + if let Err(e) = tree.set_connections(&mut connections).await { + log::error!("Could not update the D-Bus tree: {}", e); + } + }); } } Ok(()) } + async fn add_connection_action( + &mut self, + name: String, + ty: DeviceType, + ) -> Result { + let mut conn = Connection::new(name, ty); + // TODO: handle tree handling problems + let mut tree = self.tree.lock().await; + let path = tree + .add_connection(&mut conn) + .await + .expect("Could not update the D-Bus tree"); + self.state.add_connection(conn)?; + Ok(path) + } + fn set_ports_action( &mut self, uuid: Uuid, diff --git a/rust/agama-dbus-server/tests/common/mod.rs b/rust/agama-dbus-server/tests/common/mod.rs index 47841518e8..009d95bfb1 100644 --- a/rust/agama-dbus-server/tests/common/mod.rs +++ b/rust/agama-dbus-server/tests/common/mod.rs @@ -109,7 +109,7 @@ impl NameOwnerChangedStream { loop { let signal = self.0.next().await.unwrap().unwrap(); let (sname, _, _): (String, String, String) = signal.body().unwrap(); - if &sname == name { + if sname == name { return; } } diff --git a/rust/agama-dbus-server/tests/network.rs b/rust/agama-dbus-server/tests/network.rs index 87b1aaa475..616067d651 100644 --- a/rust/agama-dbus-server/tests/network.rs +++ b/rust/agama-dbus-server/tests/network.rs @@ -11,6 +11,7 @@ use agama_lib::network::{ types::DeviceType, NetworkClient, }; +use async_trait::async_trait; use cidr::IpInet; use std::error::Error; use tokio::test; @@ -18,12 +19,16 @@ use tokio::test; #[derive(Default)] pub struct NetworkTestAdapter(network::NetworkState); +#[async_trait] impl Adapter for NetworkTestAdapter { - fn read(&self) -> Result> { + async fn read(&self) -> Result> { Ok(self.0.clone()) } - fn write(&self, _network: &network::NetworkState) -> Result<(), Box> { + async fn write( + &self, + _network: &network::NetworkState, + ) -> Result<(), Box> { unimplemented!("Not used in tests"); } } @@ -102,7 +107,7 @@ async fn test_add_bond_connection() -> Result<(), Box> { let adapter = NetworkTestAdapter(NetworkState::default()); - let _service = NetworkService::start(&server.connection(), adapter).await?; + NetworkService::start(&server.connection(), adapter).await?; server.request_name().await?; let client = NetworkClient::new(server.connection().clone()).await?; @@ -128,7 +133,7 @@ async fn test_add_bond_connection() -> Result<(), Box> { let conns = async_retry(|| client.connections()).await?; assert_eq!(conns.len(), 2); - let conn = conns.iter().find(|c| c.id == "bond0".to_string()).unwrap(); + let conn = conns.iter().find(|c| &c.id == "bond0").unwrap(); assert_eq!(conn.id, "bond0"); assert_eq!(conn.device_type(), DeviceType::Bond); let bond = conn.bond.clone().unwrap(); diff --git a/rust/agama-lib/src/error.rs b/rust/agama-lib/src/error.rs index b1fc9ba579..f01a929837 100644 --- a/rust/agama-lib/src/error.rs +++ b/rust/agama-lib/src/error.rs @@ -6,9 +6,9 @@ use zbus; #[derive(Error, Debug)] pub enum ServiceError { - #[error("D-Bus service error")] + #[error("D-Bus service error: {0}")] DBus(#[from] zbus::Error), - #[error("Could not connect to Agama bus at '{0}'")] + #[error("Could not connect to Agama bus at '{0}': {1}")] DBusConnectionError(String, #[source] zbus::Error), // it's fine to say only "Error" because the original // specific error will be printed too @@ -20,7 +20,7 @@ pub enum ServiceError { FailedRegistration(String), #[error("Failed to find these patterns: {0:?}")] UnknownPatterns(Vec), - #[error("Error: {0}")] + #[error("Could not perform action '{0}'")] UnsuccessfulAction(String), } diff --git a/rust/agama-lib/src/network/proxies.rs b/rust/agama-lib/src/network/proxies.rs index a3b1916abf..77aed57feb 100644 --- a/rust/agama-lib/src/network/proxies.rs +++ b/rust/agama-lib/src/network/proxies.rs @@ -37,7 +37,7 @@ trait Connections { /// /// `name`: connection name. /// `ty`: connection type. - fn add_connection(&self, name: &str, ty: u8) -> zbus::Result<()>; + fn add_connection(&self, name: &str, ty: u8) -> zbus::Result; /// Apply method fn apply(&self) -> zbus::Result<()>; diff --git a/rust/agama-lib/src/software/proxies.rs b/rust/agama-lib/src/software/proxies.rs index a37cc1b341..e7298f2187 100644 --- a/rust/agama-lib/src/software/proxies.rs +++ b/rust/agama-lib/src/software/proxies.rs @@ -3,6 +3,17 @@ //! This code was generated by `zbus-xmlgen` `3.1.1` from DBus introspection data. use zbus::dbus_proxy; +/// Software patterns map. +/// +/// It uses the pattern name as key and a tuple containing the following information as value: +/// +/// * Category. +/// * Description. +/// * Icon. +/// * Summary. +/// * Order. +pub type PatternsMap = std::collections::HashMap; + #[dbus_proxy( interface = "org.opensuse.Agama.Software1", default_service = "org.opensuse.Agama.Software1", @@ -22,10 +33,7 @@ trait Software1 { fn is_package_installed(&self, name: &str) -> zbus::Result; /// ListPatterns method - fn list_patterns( - &self, - filtered: bool, - ) -> zbus::Result>; + fn list_patterns(&self, filtered: bool) -> zbus::Result; /// Probe method fn probe(&self) -> zbus::Result<()>; @@ -50,6 +58,19 @@ trait Software1 { fn selected_patterns(&self) -> zbus::Result>; } +/// Product definition. +/// +/// It is composed of the following elements: +/// +/// * Product ID. +/// * Display name. +/// * Some additional data which includes a "description" key. +pub type Product = ( + String, + String, + std::collections::HashMap, +); + #[dbus_proxy( interface = "org.opensuse.Agama.Software1.Product", default_service = "org.opensuse.Agama.Software1", @@ -61,15 +82,7 @@ trait SoftwareProduct { /// AvailableProducts property #[dbus_proxy(property)] - fn available_products( - &self, - ) -> zbus::Result< - Vec<( - String, - String, - std::collections::HashMap, - )>, - >; + fn available_products(&self) -> zbus::Result>; /// SelectedProduct property #[dbus_proxy(property)] diff --git a/rust/agama-lib/src/storage/store.rs b/rust/agama-lib/src/storage/store.rs index 7c6ea81b51..926366f721 100644 --- a/rust/agama-lib/src/storage/store.rs +++ b/rust/agama-lib/src/storage/store.rs @@ -2,7 +2,6 @@ use super::{StorageClient, StorageSettings}; use crate::error::ServiceError; -use std::default::Default; use zbus::Connection; /// Loads and stores the storage settings from/to the D-Bus service. @@ -26,7 +25,6 @@ impl<'a> StorageStore<'a> { boot_device, lvm, encryption_password, - ..Default::default() }) } diff --git a/rust/agama-lib/src/users/client.rs b/rust/agama-lib/src/users/client.rs index d510b20fd0..437ff3f21d 100644 --- a/rust/agama-lib/src/users/client.rs +++ b/rust/agama-lib/src/users/client.rs @@ -1,6 +1,6 @@ //! Implements a client to access Agama's users service. -use super::proxies::Users1Proxy; +use super::proxies::{FirstUser as FirstUserFromDBus, Users1Proxy}; use crate::error::ServiceError; use agama_settings::{settings::Settings, SettingValue, SettingsError}; use serde::Serialize; @@ -22,15 +22,7 @@ pub struct FirstUser { } impl FirstUser { - pub fn from_dbus( - dbus_data: zbus::Result<( - String, - String, - String, - bool, - std::collections::HashMap, - )>, - ) -> zbus::Result { + pub fn from_dbus(dbus_data: zbus::Result) -> zbus::Result { let data = dbus_data?; Ok(Self { full_name: data.0, diff --git a/rust/agama-lib/src/users/proxies.rs b/rust/agama-lib/src/users/proxies.rs index f1a71a2825..6e2793fd2f 100644 --- a/rust/agama-lib/src/users/proxies.rs +++ b/rust/agama-lib/src/users/proxies.rs @@ -3,6 +3,23 @@ //! This code was generated by `zbus-xmlgen` `3.1.0` from DBus introspection data.`. use zbus::dbus_proxy; +/// First user as it comes from D-Bus. +/// +/// It is composed of: +/// +/// * full name +/// * user name +/// * password +/// * auto-login (enabled or not) +/// * some optional and additional data +pub type FirstUser = ( + String, + String, + String, + bool, + std::collections::HashMap, +); + #[dbus_proxy( interface = "org.opensuse.Agama.Users1", default_service = "org.opensuse.Agama.Manager1", @@ -37,15 +54,7 @@ trait Users1 { /// FirstUser property #[dbus_proxy(property)] - fn first_user( - &self, - ) -> zbus::Result<( - String, - String, - String, - bool, - std::collections::HashMap, - )>; + fn first_user(&self) -> zbus::Result; /// RootPasswordSet property #[dbus_proxy(property)] diff --git a/rust/agama-locale-data/src/lib.rs b/rust/agama-locale-data/src/lib.rs index 7c4c313660..71d6c1654c 100644 --- a/rust/agama-locale-data/src/lib.rs +++ b/rust/agama-locale-data/src/lib.rs @@ -162,9 +162,9 @@ mod tests { let first = result.first().expect("no keyboards"); assert_eq!(first, "Africa/Abidjan"); // test that we filter out deprecates Asmera ( there is already recent Asmara) - let asmera = result.iter().find(|&t| *t == "Africa/Asmera".to_string()); + let asmera = result.iter().find(|&t| t == "Africa/Asmera"); assert_eq!(asmera, None); - let asmara = result.iter().find(|&t| *t == "Africa/Asmara".to_string()); + let asmara = result.iter().find(|&t| t == "Africa/Asmara"); assert_eq!(asmara, Some(&"Africa/Asmara".to_string())); // here test that timezones from timezones matches ones in langtable ( as timezones can contain deprecated ones) // so this test catch if there is new zone that is not translated or if a zone is become deprecated diff --git a/rust/rustfmt.toml b/rust/rustfmt.toml new file mode 100644 index 0000000000..3a26366d4d --- /dev/null +++ b/rust/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" diff --git a/service/po/cs.po b/service/po/cs.po index 3114b0f02f..5dec1670a4 100644 --- a/service/po/cs.po +++ b/service/po/cs.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-21 02:17+0000\n" +"POT-Creation-Date: 2023-12-27 02:08+0000\n" "PO-Revision-Date: 2023-12-15 16:52+0000\n" "Last-Translator: Ladislav Slezák \n" "Language-Team: Czech =2 && n<=4) ? 1 : 2;\n" "X-Generator: Weblate 4.9.1\n" +#. Runs the config phase +#: service/lib/agama/manager.rb:88 +msgid "Probing Storage" +msgstr "" + +#: service/lib/agama/manager.rb:89 +msgid "Probing Software" +msgstr "" + +#. Runs the install phase +#. rubocop:disable Metrics/AbcSize +#: service/lib/agama/manager.rb:109 +msgid "Partitioning" +msgstr "" + +#. propose software after /mnt is already separated, so it uses proper +#. target +#: service/lib/agama/manager.rb:117 +#, fuzzy +msgid "Installing Software" +msgstr "Inicializuji zdroje" + +#: service/lib/agama/manager.rb:120 +msgid "Writing Users" +msgstr "" + +#: service/lib/agama/manager.rb:121 +msgid "Writing Network Configuration" +msgstr "" + +#: service/lib/agama/manager.rb:122 +msgid "Saving Language Settings" +msgstr "" + +#: service/lib/agama/manager.rb:123 +#, fuzzy +msgid "Writing repositories information" +msgstr "Repozitáře se zapisují do cílového systému" + +#: service/lib/agama/manager.rb:124 +msgid "Finishing storage configuration" +msgstr "" + #. Callback to handle unsigned files #. #. @param filename [String] File name @@ -112,3 +155,39 @@ msgstr "Produkt musí být zaregistrován" #, c-format msgid "Found %s dependency issues." msgstr "Nalezeno %s problémů v závislostech." + +#. Probes storage devices and performs an initial proposal +#: service/lib/agama/storage/manager.rb:111 +msgid "Activating storage devices" +msgstr "" + +#: service/lib/agama/storage/manager.rb:112 +msgid "Probing storage devices" +msgstr "" + +#: service/lib/agama/storage/manager.rb:113 +#, fuzzy +msgid "Calculating the storage proposal" +msgstr "Probíhá návrh softwaru" + +#: service/lib/agama/storage/manager.rb:114 +msgid "Selecting Linux Security Modules" +msgstr "" + +#. Prepares the partitioning to install the system +#: service/lib/agama/storage/manager.rb:122 +msgid "Preparing bootloader proposal" +msgstr "" + +#. first make bootloader proposal to be sure that required packages are installed +#: service/lib/agama/storage/manager.rb:127 +msgid "Adding storage-related packages" +msgstr "" + +#: service/lib/agama/storage/manager.rb:128 +msgid "Preparing the storage devices" +msgstr "" + +#: service/lib/agama/storage/manager.rb:129 +msgid "Writing bootloader sysconfig" +msgstr "" diff --git a/service/po/fr.po b/service/po/fr.po index b2313b0bb5..8030ac4e91 100644 --- a/service/po/fr.po +++ b/service/po/fr.po @@ -7,11 +7,11 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-21 02:17+0000\n" +"POT-Creation-Date: 2023-12-27 02:08+0000\n" "PO-Revision-Date: 2023-12-22 15:02+0000\n" "Last-Translator: faila fail \n" -"Language-Team: French \n" +"Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,6 +19,49 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 4.9.1\n" +#. Runs the config phase +#: service/lib/agama/manager.rb:88 +msgid "Probing Storage" +msgstr "" + +#: service/lib/agama/manager.rb:89 +msgid "Probing Software" +msgstr "" + +#. Runs the install phase +#. rubocop:disable Metrics/AbcSize +#: service/lib/agama/manager.rb:109 +msgid "Partitioning" +msgstr "" + +#. propose software after /mnt is already separated, so it uses proper +#. target +#: service/lib/agama/manager.rb:117 +#, fuzzy +msgid "Installing Software" +msgstr "Initialisation des sources" + +#: service/lib/agama/manager.rb:120 +msgid "Writing Users" +msgstr "" + +#: service/lib/agama/manager.rb:121 +msgid "Writing Network Configuration" +msgstr "" + +#: service/lib/agama/manager.rb:122 +msgid "Saving Language Settings" +msgstr "" + +#: service/lib/agama/manager.rb:123 +#, fuzzy +msgid "Writing repositories information" +msgstr "Écriture des dépôts sur le système cible" + +#: service/lib/agama/manager.rb:124 +msgid "Finishing storage configuration" +msgstr "" + #. Callback to handle unsigned files #. #. @param filename [String] File name @@ -36,8 +79,8 @@ msgstr "Le fichier %{filename}" #: service/lib/agama/software/callbacks/signature.rb:71 #, perl-brace-format msgid "" -"%{source} is not digitally signed. The origin and integrity of the file cannot" -" be verified. Use it anyway?" +"%{source} is not digitally signed. The origin and integrity of the file " +"cannot be verified. Use it anyway?" msgstr "" "%{source} n'est pas signé numériquement. L'origine et l'intégrité du fichier " "ne peuvent être vérifiées. L'utiliser quand même ?" @@ -49,8 +92,8 @@ msgstr "" #: service/lib/agama/software/callbacks/signature.rb:94 #, perl-brace-format msgid "" -"The key %{id} (%{name}) with fingerprint %{fingerprint} is unknown. Do you wan" -"t to trust this key?" +"The key %{id} (%{name}) with fingerprint %{fingerprint} is unknown. Do you " +"want to trust this key?" msgstr "" "La clé %{id} (%{name}) avec l'empreinte digitale %{fingerprint} est " "inconnue. Voulez-vous faire confiance à cette clé ?" @@ -112,3 +155,39 @@ msgstr "" #, c-format msgid "Found %s dependency issues." msgstr "%s problèmes de dépendance trouvé(s) (Need a plural option)" + +#. Probes storage devices and performs an initial proposal +#: service/lib/agama/storage/manager.rb:111 +msgid "Activating storage devices" +msgstr "" + +#: service/lib/agama/storage/manager.rb:112 +msgid "Probing storage devices" +msgstr "" + +#: service/lib/agama/storage/manager.rb:113 +#, fuzzy +msgid "Calculating the storage proposal" +msgstr "Calcul de l'offre de logiciel" + +#: service/lib/agama/storage/manager.rb:114 +msgid "Selecting Linux Security Modules" +msgstr "" + +#. Prepares the partitioning to install the system +#: service/lib/agama/storage/manager.rb:122 +msgid "Preparing bootloader proposal" +msgstr "" + +#. first make bootloader proposal to be sure that required packages are installed +#: service/lib/agama/storage/manager.rb:127 +msgid "Adding storage-related packages" +msgstr "" + +#: service/lib/agama/storage/manager.rb:128 +msgid "Preparing the storage devices" +msgstr "" + +#: service/lib/agama/storage/manager.rb:129 +msgid "Writing bootloader sysconfig" +msgstr "" diff --git a/service/po/id.po b/service/po/id.po index c3b469052f..fa8ee002c1 100644 --- a/service/po/id.po +++ b/service/po/id.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-21 02:17+0000\n" +"POT-Creation-Date: 2023-12-27 02:08+0000\n" "PO-Revision-Date: 2023-12-16 21:02+0000\n" "Last-Translator: Arif Budiman \n" "Language-Team: Indonesian \n" -"Language-Team: Japanese \n" +"Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,6 +19,47 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.9.1\n" +#. Runs the config phase +#: service/lib/agama/manager.rb:88 +msgid "Probing Storage" +msgstr "ストレージを検出しています" + +#: service/lib/agama/manager.rb:89 +msgid "Probing Software" +msgstr "ソフトウエアを検出しています" + +#. Runs the install phase +#. rubocop:disable Metrics/AbcSize +#: service/lib/agama/manager.rb:109 +msgid "Partitioning" +msgstr "パーティションを設定しています" + +#. propose software after /mnt is already separated, so it uses proper +#. target +#: service/lib/agama/manager.rb:117 +msgid "Installing Software" +msgstr "ソフトウエアをインストールしています" + +#: service/lib/agama/manager.rb:120 +msgid "Writing Users" +msgstr "ユーザを書き込んでいます" + +#: service/lib/agama/manager.rb:121 +msgid "Writing Network Configuration" +msgstr "ネットワーク設定を書き込んでいます" + +#: service/lib/agama/manager.rb:122 +msgid "Saving Language Settings" +msgstr "言語設定を保存しています" + +#: service/lib/agama/manager.rb:123 +msgid "Writing repositories information" +msgstr "リポジトリ情報を書き込んでいます" + +#: service/lib/agama/manager.rb:124 +msgid "Finishing storage configuration" +msgstr "ストレージ設定を完了しています" + #. Callback to handle unsigned files #. #. @param filename [String] File name @@ -112,3 +153,38 @@ msgstr "製品を登録しなければなりません" #, c-format msgid "Found %s dependency issues." msgstr "%s 個の依存関係の問題が見つかりました。" + +#. Probes storage devices and performs an initial proposal +#: service/lib/agama/storage/manager.rb:111 +msgid "Activating storage devices" +msgstr "ストレージデバイスを有効化しています" + +#: service/lib/agama/storage/manager.rb:112 +msgid "Probing storage devices" +msgstr "ストレージデバイスを検出しています" + +#: service/lib/agama/storage/manager.rb:113 +msgid "Calculating the storage proposal" +msgstr "ストレージの提案内容を作成しています" + +#: service/lib/agama/storage/manager.rb:114 +msgid "Selecting Linux Security Modules" +msgstr "Linux セキュリティモジュールを選択しています" + +#. Prepares the partitioning to install the system +#: service/lib/agama/storage/manager.rb:122 +msgid "Preparing bootloader proposal" +msgstr "ブートローダの提案内容を準備しています" + +#. first make bootloader proposal to be sure that required packages are installed +#: service/lib/agama/storage/manager.rb:127 +msgid "Adding storage-related packages" +msgstr "ストレージ関連のパッケージを追加しています" + +#: service/lib/agama/storage/manager.rb:128 +msgid "Preparing the storage devices" +msgstr "ストレージデバイスを準備しています" + +#: service/lib/agama/storage/manager.rb:129 +msgid "Writing bootloader sysconfig" +msgstr "ブートローダの sysconfig を書き込んでいます" diff --git a/service/po/sv.po b/service/po/sv.po index 961a8e2ce5..d2dd76f325 100644 --- a/service/po/sv.po +++ b/service/po/sv.po @@ -7,11 +7,11 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-12-21 02:17+0000\n" -"PO-Revision-Date: 2023-12-17 18:02+0000\n" +"POT-Creation-Date: 2023-12-27 02:08+0000\n" +"PO-Revision-Date: 2023-12-28 11:02+0000\n" "Last-Translator: Luna Jernberg \n" -"Language-Team: Swedish \n" +"Language-Team: Swedish \n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,6 +19,47 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.9.1\n" +#. Runs the config phase +#: service/lib/agama/manager.rb:88 +msgid "Probing Storage" +msgstr "Undersöker lagring" + +#: service/lib/agama/manager.rb:89 +msgid "Probing Software" +msgstr "Undersöker programvara" + +#. Runs the install phase +#. rubocop:disable Metrics/AbcSize +#: service/lib/agama/manager.rb:109 +msgid "Partitioning" +msgstr "Partitionerar" + +#. propose software after /mnt is already separated, so it uses proper +#. target +#: service/lib/agama/manager.rb:117 +msgid "Installing Software" +msgstr "Installerar programvara" + +#: service/lib/agama/manager.rb:120 +msgid "Writing Users" +msgstr "Skriver användare" + +#: service/lib/agama/manager.rb:121 +msgid "Writing Network Configuration" +msgstr "Skriver nätverkskonfiguration" + +#: service/lib/agama/manager.rb:122 +msgid "Saving Language Settings" +msgstr "Sparar språkinställningar" + +#: service/lib/agama/manager.rb:123 +msgid "Writing repositories information" +msgstr "Skriver information om förråd" + +#: service/lib/agama/manager.rb:124 +msgid "Finishing storage configuration" +msgstr "Slutför lagringskonfiguration" + #. Callback to handle unsigned files #. #. @param filename [String] File name @@ -112,3 +153,38 @@ msgstr "Produkt måste registreras" #, c-format msgid "Found %s dependency issues." msgstr "Hittade %s beroendeproblem." + +#. Probes storage devices and performs an initial proposal +#: service/lib/agama/storage/manager.rb:111 +msgid "Activating storage devices" +msgstr "Aktiverar lagringsenheter" + +#: service/lib/agama/storage/manager.rb:112 +msgid "Probing storage devices" +msgstr "Undersöker lagringsenheter" + +#: service/lib/agama/storage/manager.rb:113 +msgid "Calculating the storage proposal" +msgstr "Beräknar lagringsförslag" + +#: service/lib/agama/storage/manager.rb:114 +msgid "Selecting Linux Security Modules" +msgstr "Väljer Linux säkerhetsmoduler" + +#. Prepares the partitioning to install the system +#: service/lib/agama/storage/manager.rb:122 +msgid "Preparing bootloader proposal" +msgstr "Förbereder starthanterare förslag" + +#. first make bootloader proposal to be sure that required packages are installed +#: service/lib/agama/storage/manager.rb:127 +msgid "Adding storage-related packages" +msgstr "Lägger till lagrings-relaterade paket" + +#: service/lib/agama/storage/manager.rb:128 +msgid "Preparing the storage devices" +msgstr "Förbereder lagringsenheter" + +#: service/lib/agama/storage/manager.rb:129 +msgid "Writing bootloader sysconfig" +msgstr "Skriver starthanterarens sysconfig"