From dec964eece1391e6954053673a25652210f0fe50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 20 Dec 2023 12:13:56 +0000 Subject: [PATCH 01/26] Add an 'Active' property to the Connection interface --- .../src/network/dbus/interfaces.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index 3cd5fb8e3d..7ca4e1c3d9 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -275,6 +275,23 @@ impl Connection { connection.mac_address = MacAddress::from_str(mac_address)?; self.update_connection(connection).await } + + #[dbus_interface(property)] + pub async fn active(&self) -> bool { + let connection = self.get_connection().await; + connection.is_up() + } + + #[dbus_interface(property)] + pub async fn set_active(&mut self, active: bool) -> zbus::fdo::Result<()> { + let mut connection = self.get_connection().await; + if active { + connection.set_up(); + } else { + connection.set_down(); + } + self.update_connection(connection).await + } } /// D-Bus interface for Match settings From 45d2ba75a7aefaf78e6d1f01f6438f09c61f5b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 20 Dec 2023 14:49:03 +0000 Subject: [PATCH 02/26] AddConnection returns the D-Bus path of the new connection --- rust/agama-dbus-server/src/network/action.rs | 7 +++++- .../src/network/dbus/interfaces.rs | 12 ++++++--- .../src/network/dbus/tree.rs | 8 +++--- rust/agama-dbus-server/src/network/system.rs | 25 ++++++++++++++++--- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index 3ddf6687d9..137cd717a5 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,7 +16,11 @@ 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 controller connection diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index 7ca4e1c3d9..123e3382a7 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -129,12 +129,18 @@ impl Connections { /// /// * `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<()> { + pub async fn add_connection( + &mut self, + id: String, + ty: u8, + ) -> zbus::fdo::Result { let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); actions - .send(Action::AddConnection(id.clone(), ty.try_into()?)) + .send(Action::AddConnection(id.clone(), ty.try_into()?, tx)) .unwrap(); - Ok(()) + let result = rx.await.unwrap()?; + Ok(result) } /// Returns the D-Bus path of the network connection. diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 45568fcf42..1c2c5612eb 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -76,7 +76,7 @@ impl Tree { 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. /// * `notify`: whether to notify the added connection @@ -84,7 +84,7 @@ impl Tree { &self, conn: &mut Connection, notify: bool, - ) -> Result<(), ServiceError> { + ) -> Result { let mut objects = self.objects.lock().await; let orig_id = conn.id.to_owned(); @@ -98,7 +98,7 @@ impl Tree { 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?; @@ -131,7 +131,7 @@ impl Tree { self.notify_connection_added(&orig_id, &path).await?; } - Ok(()) + Ok(path.into()) } /// Removes a connection from the tree diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index 4500c3be43..af23d9e8d7 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -1,8 +1,10 @@ use super::error::NetworkStateError; use crate::network::{dbus::Tree, model::Connection, Action, Adapter, NetworkState}; +use agama_lib::network::types::DeviceType; use std::error::Error; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; 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 { @@ -67,10 +69,9 @@ 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) => { let conn = self.state.get_connection_by_uuid(uuid); @@ -104,6 +105,22 @@ impl NetworkSystem { 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 path = self + .tree + .add_connection(&mut conn, true) + .await + .expect("Could not update the D-Bus tree"); + self.state.add_connection(conn)?; + Ok(path) + } + fn set_ports_action( &mut self, uuid: Uuid, From e1929899e7f0c68793a99d2315acb7d307d7dace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 20 Dec 2023 14:49:48 +0000 Subject: [PATCH 03/26] Better name for a channel variable --- rust/agama-dbus-server/src/network/system.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index af23d9e8d7..9e8de3e18d 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -73,17 +73,17 @@ impl NetworkSystem { 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::GetController(uuid, tx) => { let result = self.get_controller_action(uuid); - rx.send(result).unwrap() + tx.send(result).unwrap() } - Action::SetPorts(uuid, ports, rx) => { + Action::SetPorts(uuid, ports, tx) => { let result = self.set_ports_action(uuid, ports); - rx.send(result).unwrap(); + tx.send(result).unwrap(); } Action::UpdateConnection(conn) => { self.state.update_connection(conn)?; From 6aad653329ff832baa7a18f915bcc84e7c68799f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 20 Dec 2023 14:50:42 +0000 Subject: [PATCH 04/26] Use channels in the *.Network.Connection interface --- .../src/network/dbus/interfaces.rs | 95 ++++++++++--------- 1 file changed, 52 insertions(+), 43 deletions(-) diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index 123e3382a7..b7ba282f88 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -188,7 +188,7 @@ impl Connections { /// It offers an API to query a connection. pub struct Connection { actions: Arc>>, - connection: Arc>, + uuid: Uuid, } impl Connection { @@ -196,32 +196,34 @@ impl Connection { /// /// * `actions`: sending-half of a channel to send actions. /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { + 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 + 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())) } /// Updates the connection data in the NetworkSystem. /// /// * `connection`: Updated connection. - async fn update_connection<'a>( - &self, - connection: MutexGuard<'a, NetworkConnection>, - ) -> zbus::fdo::Result<()> { + pub async fn update_connection(&self, func: F) -> Result<(), NetworkStateError> + where + F: FnOnce(&mut NetworkConnection), + { + let mut connection = self.get_connection().await?; + func(&mut connection); let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(connection.clone())) - .unwrap(); + actions.send(Action::UpdateConnection(connection)).unwrap(); Ok(()) } } @@ -234,8 +236,9 @@ impl Connection { /// 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() + pub async fn id(&self) -> zbus::fdo::Result { + let connection = self.get_connection().await?; + Ok(connection.id) } /// Connection UUID. @@ -244,59 +247,65 @@ impl Connection { /// backend. #[dbus_interface(property)] pub async fn uuid(&self) -> String { - self.get_connection().await.uuid.to_string() + self.uuid.to_string() } #[dbus_interface(property)] - pub async fn controller(&self) -> String { - let connection = self.get_connection().await; - match connection.controller { + 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) -> String { - let connection = self.get_connection().await; - connection.interface.as_deref().unwrap_or("").to_string() + 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 mut connection = self.get_connection().await; - connection.interface = Some(name.to_string()); - self.update_connection(connection).await + 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) -> String { - self.get_connection().await.mac_address.to_string() + 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 mut connection = self.get_connection().await; - connection.mac_address = MacAddress::from_str(mac_address)?; - self.update_connection(connection).await + 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) -> bool { - let connection = self.get_connection().await; - connection.is_up() + 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<()> { - let mut connection = self.get_connection().await; - if active { - connection.set_up(); - } else { - connection.set_down(); - } - self.update_connection(connection).await + self.update_connection(|c| { + if active { + c.set_up(); + } else { + c.set_down(); + } + }) + .await?; + Ok(()) } } From c2d906edc9b589f5f9092ba60316dbde8f10615a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 20 Dec 2023 17:17:00 +0000 Subject: [PATCH 05/26] Split interfaces.rs into smaller files --- .../src/network/dbus/interfaces.rs | 647 +----------------- .../dbus/interfaces/connection_configs.rs | 253 +++++++ .../network/dbus/interfaces/connections.rs | 330 +++++++++ .../src/network/dbus/interfaces/devices.rs | 70 ++ 4 files changed, 659 insertions(+), 641 deletions(-) create mode 100644 rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs create mode 100644 rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs create mode 100644 rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index b7ba282f88..c803941132 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -3,646 +3,11 @@ //! 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 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; - let (tx, rx) = oneshot::channel(); - actions - .send(Action::AddConnection(id.clone(), ty.try_into()?, tx)) - .unwrap(); - let result = rx.await.unwrap()?; - Ok(result) - } - - /// 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>>, - uuid: Uuid, -} - -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, uuid: Uuid) -> Self { - Self { - actions: Arc::new(Mutex::new(actions)), - uuid, - } - } - - /// Returns the underlying 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())) - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - pub async fn update_connection(&self, func: F) -> Result<(), NetworkStateError> - where - F: FnOnce(&mut NetworkConnection), - { - let mut connection = self.get_connection().await?; - func(&mut connection); - let actions = self.actions.lock().await; - actions.send(Action::UpdateConnection(connection)).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) -> 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(()) - } -} - -/// 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/connection_configs.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs new file mode 100644 index 0000000000..48f2da1b77 --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs @@ -0,0 +1,253 @@ +use agama_lib::network::types::SSID; +use std::sync::Arc; +use tokio::sync::{mpsc::UnboundedSender, oneshot, MappedMutexGuard, Mutex, MutexGuard}; +use uuid::Uuid; +use zbus::dbus_interface; + +use crate::network::{ + action::Action, + error::NetworkStateError, + model::{ + BondConfig, Connection as NetworkConnection, ConnectionConfig, SecurityProtocol, + WirelessConfig, WirelessMode, + }, +}; + +/// 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?) + } +} + +/// 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/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs new file mode 100644 index 0000000000..029c10365c --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -0,0 +1,330 @@ +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 crate::network::{ + dbus::ObjectsRegistry, + error::NetworkStateError, + model::{Connection as NetworkConnection, MacAddress}, + Action, +}; + +/// 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; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::AddConnection(id.clone(), ty.try_into()?, tx)) + .unwrap(); + let result = rx.await.unwrap()?; + Ok(result) + } + + /// 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>>, + uuid: Uuid, +} + +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, uuid: Uuid) -> Self { + Self { + actions: Arc::new(Mutex::new(actions)), + uuid, + } + } + + /// Returns the underlying 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())) + } + + /// Updates the connection data in the NetworkSystem. + /// + /// * `connection`: Updated connection. + pub async fn update_connection(&self, func: F) -> Result<(), NetworkStateError> + where + F: FnOnce(&mut NetworkConnection), + { + let mut connection = self.get_connection().await?; + func(&mut connection); + let actions = self.actions.lock().await; + actions.send(Action::UpdateConnection(connection)).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) -> 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(()) + } +} + +/// 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(()) + } +} + +#[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 + } +} 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..41339e01e5 --- /dev/null +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs @@ -0,0 +1,70 @@ +use crate::network::{dbus::ObjectsRegistry, model::Device as NetworkDevice}; +use std::sync::Arc; +use tokio::sync::Mutex; +use zbus::{dbus_interface, zvariant::ObjectPath}; + +/// 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 + } +} From 932de660ea5b485ec9ade0f43c2020221fd9ebc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 20 Dec 2023 17:31:48 +0000 Subject: [PATCH 06/26] Use channels in the *.Connection.Wireless D-Bus interface --- .../dbus/interfaces/connection_configs.rs | 66 ++++++++++--------- .../src/network/dbus/tree.rs | 7 +- 2 files changed, 38 insertions(+), 35 deletions(-) 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 index 48f2da1b77..76e4bf2c77 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs @@ -1,6 +1,6 @@ use agama_lib::network::types::SSID; use std::sync::Arc; -use tokio::sync::{mpsc::UnboundedSender, oneshot, MappedMutexGuard, Mutex, MutexGuard}; +use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex}; use uuid::Uuid; use zbus::dbus_interface; @@ -129,32 +129,38 @@ impl Bond { /// D-Bus interface for wireless settings pub struct Wireless { actions: Arc>>, - connection: Arc>, + uuid: Uuid, } 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 { + /// * `uuid`: connection UUID. + pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { Self { actions: Arc::new(Mutex::new(actions)), - connection, + uuid, } } - /// 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."), - }) + /// 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 wireless configuration. + pub async fn get_config(&self) -> Result { + let connection = self.get_connection().await?; + match connection.config { + ConnectionConfig::Wireless(wireless) => Ok(wireless), + _ => panic!("Not a bond connection. This is most probably a bug."), + } } /// Updates the wireless configuration. @@ -164,10 +170,10 @@ impl Wireless { where F: FnOnce(&mut WirelessConfig), { - let mut connection = self.connection.lock().await; + let mut connection = self.get_connection().await?; match &mut connection.config { ConnectionConfig::Wireless(wireless) => func(wireless), - _ => panic!("Not a wireless connection. This is most probably a bug."), + _ => panic!("Not a bond connection. This is most probably a bug."), } let actions = self.actions.lock().await; actions @@ -181,9 +187,9 @@ impl 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() + pub async fn ssid(&self) -> zbus::fdo::Result> { + let config = self.get_config().await?; + Ok(config.ssid.into()) } #[dbus_interface(property, name = "SSID")] @@ -198,9 +204,9 @@ impl Wireless { /// /// See [crate::network::model::WirelessMode]. #[dbus_interface(property)] - pub async fn mode(&self) -> String { - let wireless = self.get_wireless().await; - wireless.mode.to_string() + pub async fn mode(&self) -> zbus::fdo::Result { + let config = self.get_config().await?; + Ok(config.mode.to_string()) } #[dbus_interface(property)] @@ -212,9 +218,9 @@ impl Wireless { /// 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()) + pub async fn password(&self) -> zbus::fdo::Result { + let config = self.get_config().await?; + Ok(config.password.unwrap_or_default()) } #[dbus_interface(property)] @@ -237,9 +243,9 @@ impl Wireless { /// /// See [crate::network::model::SecurityProtocol]. #[dbus_interface(property)] - pub async fn security(&self) -> String { - let wireless = self.get_wireless().await; - wireless.security.to_string() + pub async fn security(&self) -> zbus::fdo::Result { + let config = self.get_config().await?; + Ok(config.security.to_string()) } #[dbus_interface(property)] diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 1c2c5612eb..7c84188b36 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -120,11 +120,8 @@ impl Tree { } if let ConnectionConfig::Wireless(_) = conn.config { - self.add_interface( - &path, - interfaces::Wireless::new(self.actions.clone(), Arc::clone(&cloned)), - ) - .await?; + self.add_interface(&path, interfaces::Wireless::new(self.actions.clone(), uuid)) + .await?; } if notify { From 116aaa9a40f9aa9095ec10a3471c10dcf2e2b161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 20 Dec 2023 16:27:58 +0000 Subject: [PATCH 07/26] Reduce the size of the Action enum variants * See https://rust-lang.github.io/rust-clippy/master/index.html#/large_enum_variant --- rust/agama-dbus-server/src/network/action.rs | 8 ++++++-- .../src/network/dbus/interfaces/connection_configs.rs | 6 +++--- .../src/network/dbus/interfaces/connections.rs | 6 ++++-- .../src/network/dbus/interfaces/ip_config.rs | 2 +- rust/agama-dbus-server/src/network/system.rs | 8 ++++---- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index 137cd717a5..568410d579 100644 --- a/rust/agama-dbus-server/src/network/action.rs +++ b/rust/agama-dbus-server/src/network/action.rs @@ -30,9 +30,13 @@ pub enum Action { ), /// 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/dbus/interfaces/connection_configs.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs index 76e4bf2c77..5a2edad0f9 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs @@ -64,7 +64,7 @@ impl Bond { } let actions = self.actions.lock().await; actions - .send(Action::UpdateConnection(connection.clone())) + .send(Action::UpdateConnection(Box::new(connection.clone()))) .unwrap(); Ok(()) } @@ -119,7 +119,7 @@ impl Bond { let actions = self.actions.lock().await; let (tx, rx) = oneshot::channel(); actions - .send(Action::SetPorts(self.uuid, ports, tx)) + .send(Action::SetPorts(self.uuid, Box::new(ports), tx)) .unwrap(); let result = rx.await.unwrap(); Ok(result?) @@ -177,7 +177,7 @@ impl Wireless { } let actions = self.actions.lock().await; actions - .send(Action::UpdateConnection(connection.clone())) + .send(Action::UpdateConnection(Box::new(connection.clone()))) .unwrap(); Ok(()) } diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs index 029c10365c..2e04320fb0 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -144,7 +144,9 @@ impl Connection { let mut connection = self.get_connection().await?; func(&mut connection); let actions = self.actions.lock().await; - actions.send(Action::UpdateConnection(connection)).unwrap(); + actions + .send(Action::UpdateConnection(Box::new(connection))) + .unwrap(); Ok(()) } } @@ -265,7 +267,7 @@ impl Match { ) -> zbus::fdo::Result<()> { let actions = self.actions.lock().await; actions - .send(Action::UpdateConnection(connection.clone())) + .send(Action::UpdateConnection(Box::new(connection.clone()))) .unwrap(); Ok(()) } 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..3b18337473 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 @@ -49,7 +49,7 @@ impl Ip { ) -> zbus::fdo::Result<()> { let actions = self.actions.lock().await; actions - .send(Action::UpdateConnection(connection.clone())) + .send(Action::UpdateConnection(Box::new(connection.clone()))) .unwrap(); Ok(()) } diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index 9e8de3e18d..ad44137fc7 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -81,12 +81,12 @@ impl NetworkSystem { let result = self.get_controller_action(uuid); tx.send(result).unwrap() } - Action::SetPorts(uuid, ports, tx) => { - let result = self.set_ports_action(uuid, ports); - tx.send(result).unwrap(); + Action::SetPorts(uuid, ports, rx) => { + 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?; From ba90e0eb3c07d56c189fcf3e796d3b662a9bacf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 21 Dec 2023 13:33:30 +0000 Subject: [PATCH 08/26] Add some basic rustfmt configuration --- rust/rustfmt.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 rust/rustfmt.toml 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" From ac9b7915eb37b7a7d5a1a87d636f05748ff3b9d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 26 Dec 2023 08:53:29 +0000 Subject: [PATCH 09/26] Use channels in the *.Connection.IPConfig interface --- .../src/network/dbus/interfaces/ip_config.rs | 97 +++++++++---------- .../src/network/dbus/tree.rs | 7 +- 2 files changed, 48 insertions(+), 56 deletions(-) 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 3b18337473..cc9688f60b 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,18 +6,20 @@ //! to the `Ip` struct. use crate::network::{ action::Action, + error::NetworkStateError, model::{Connection as NetworkConnection, IpConfig, Ipv4Method, Ipv6Method}, }; use cidr::IpInet; use std::{net::IpAddr, sync::Arc}; -use tokio::sync::mpsc::UnboundedSender; -use tokio::sync::{MappedMutexGuard, Mutex, MutexGuard}; +use tokio::sync::Mutex; +use tokio::sync::{mpsc::UnboundedSender, oneshot}; +use uuid::Uuid; use zbus::dbus_interface; /// D-Bus interface for IPv4 and IPv6 settings pub struct Ip { actions: Arc>>, - connection: Arc>, + uuid: Uuid, } impl Ip { @@ -25,40 +27,26 @@ impl Ip { /// /// * `actions`: sending-half of a channel to send actions. /// * `connection`: connection to expose over D-Bus. - pub fn new( - actions: UnboundedSender, - connection: Arc>, - ) -> Self { + 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<()> { + /// Gets the connection. + async fn get_connection(&self) -> Result { let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(Box::new(connection.clone()))) - .unwrap(); - Ok(()) + let (tx, rx) = oneshot::channel(); + actions.send(Action::GetConnection(self.uuid, tx)).unwrap(); + rx.await + .unwrap() + .ok_or(NetworkStateError::UnknownConnection(self.uuid.to_string())) } -} -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. @@ -68,9 +56,12 @@ impl Ip { where F: Fn(&mut IpConfig), { - let mut connection = self.get_connection().await; + let mut connection = self.get_connection().await?; func(&mut connection.ip_config); - self.update_connection(connection).await?; + let actions = self.actions.lock().await; + actions + .send(Action::UpdateConnection(Box::new(connection.clone()))) + .unwrap(); Ok(()) } } @@ -81,9 +72,10 @@ 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)] @@ -99,9 +91,9 @@ 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)] @@ -116,9 +108,9 @@ 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)] @@ -129,13 +121,14 @@ impl Ip { /// 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)] @@ -149,12 +142,13 @@ 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)] @@ -167,12 +161,13 @@ impl Ip { /// /// 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)] diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 7c84188b36..3407b2cdb9 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -102,11 +102,8 @@ impl Tree { ) .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, From 3804859de5aceaa6c48f2dc87cacd85ef913cc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 26 Dec 2023 10:04:33 +0000 Subject: [PATCH 10/26] Fix the ConnectionAdded signal emission --- .../network/dbus/interfaces/connections.rs | 6 ++++-- .../src/network/dbus/tree.rs | 20 +------------------ rust/agama-dbus-server/src/network/system.rs | 2 +- rust/agama-lib/src/network/proxies.rs | 2 +- 4 files changed, 7 insertions(+), 23 deletions(-) diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs index 2e04320fb0..24e28f3af4 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -54,14 +54,16 @@ impl Connections { &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 result = rx.await.unwrap()?; - Ok(result) + let path = rx.await.unwrap()?; + Self::connection_added(&ctxt, &id, &path).await?; + Ok(path) } /// Returns the D-Bus path of the network connection. diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 3407b2cdb9..f76599a8f2 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -83,7 +83,6 @@ impl Tree { pub async fn add_connection( &self, conn: &mut Connection, - notify: bool, ) -> Result { let mut objects = self.objects.lock().await; @@ -121,10 +120,6 @@ impl Tree { .await?; } - if notify { - self.notify_connection_added(&orig_id, &path).await?; - } - Ok(path.into()) } @@ -146,7 +141,7 @@ impl Tree { /// * `connections`: list of connections. async fn add_connections(&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( @@ -202,19 +197,6 @@ impl Tree { 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 diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index ad44137fc7..b0850064e5 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -114,7 +114,7 @@ impl NetworkSystem { // TODO: handle tree handling problems let path = self .tree - .add_connection(&mut conn, true) + .add_connection(&mut conn) .await .expect("Could not update the D-Bus tree"); self.state.add_connection(conn)?; 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<()>; From 16cada3275f2055dee67015b1e8f94b35b711aec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 26 Dec 2023 11:17:44 +0000 Subject: [PATCH 11/26] Drop an unneeded variable --- rust/agama-dbus-server/src/network/dbus/tree.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index f76599a8f2..568eb11cbf 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -86,7 +86,6 @@ impl Tree { ) -> Result { let mut objects = self.objects.lock().await; - let orig_id = conn.id.to_owned(); let uuid = conn.uuid; let (id, path) = objects.register_connection(conn); if id != conn.id { From be094a5fe2fc7660843eaf5b7452881a2f790807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 26 Dec 2023 14:27:18 +0000 Subject: [PATCH 12/26] Implement conversions to/from ConnectionConfig --- rust/agama-dbus-server/src/network/error.rs | 2 ++ rust/agama-dbus-server/src/network/model.rs | 34 +++++++++++++++++++++ 2 files changed, 36 insertions(+) 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), + } + } +} From 615d4a1bb909bf710400180fd35ac16c7ad5a8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 27 Dec 2023 11:38:48 +0000 Subject: [PATCH 13/26] Reduce D-Bus network interface code by using traits --- rust/Cargo.lock | 5 +- rust/agama-dbus-server/Cargo.toml | 1 + .../src/network/dbus/interfaces.rs | 1 + .../src/network/dbus/interfaces/common.rs | 81 ++++++++++ .../dbus/interfaces/connection_configs.rs | 141 ++++++------------ .../network/dbus/interfaces/connections.rs | 137 +++++++---------- .../src/network/dbus/interfaces/ip_config.rs | 54 +++---- .../src/network/dbus/tree.rs | 8 +- 8 files changed, 217 insertions(+), 211 deletions(-) create mode 100644 rust/agama-dbus-server/src/network/dbus/interfaces/common.rs 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/agama-dbus-server/Cargo.toml b/rust/agama-dbus-server/Cargo.toml index e6e95c67a0..4079ee01ca 100644 --- a/rust/agama-dbus-server/Cargo.toml +++ b/rust/agama-dbus-server/Cargo.toml @@ -25,3 +25,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/network/dbus/interfaces.rs b/rust/agama-dbus-server/src/network/dbus/interfaces.rs index c803941132..be287a5776 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces.rs @@ -3,6 +3,7 @@ //! This module contains the set of D-Bus interfaces that are exposed by [D-Bus network //! service](crate::NetworkService). +mod common; mod connection_configs; mod connections; mod devices; 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..41275c0388 --- /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. + /// + /// * `connection`: Updated 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 index 5a2edad0f9..c0967bb93e 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs @@ -1,18 +1,18 @@ use agama_lib::network::types::SSID; +use async_trait::async_trait; use std::sync::Arc; -use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex}; +use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex, MutexGuard}; use uuid::Uuid; use zbus::dbus_interface; use crate::network::{ action::Action, error::NetworkStateError, - model::{ - BondConfig, Connection as NetworkConnection, ConnectionConfig, SecurityProtocol, - WirelessConfig, WirelessMode, - }, + model::{BondConfig, SecurityProtocol, WirelessConfig, WirelessMode}, }; +use super::common::{ConnectionConfigInterface, ConnectionInterface}; + /// D-Bus interface for Bond settings. pub struct Bond { actions: Arc>>, @@ -30,44 +30,6 @@ impl Bond { 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(Box::new(connection.clone()))) - .unwrap(); - Ok(()) - } } #[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Bond")] @@ -75,28 +37,30 @@ impl Bond { /// Bonding mode. #[dbus_interface(property)] pub async fn mode(&self) -> zbus::fdo::Result { - let config = self.get_config().await?; + 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?; + 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?; + 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?; + self.update_config::(|c| c.options = opts) + .await?; Ok(()) } @@ -126,6 +90,19 @@ impl Bond { } } +#[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>>, @@ -143,44 +120,6 @@ impl Wireless { 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 wireless configuration. - pub async fn get_config(&self) -> Result { - let connection = self.get_connection().await?; - match connection.config { - ConnectionConfig::Wireless(wireless) => Ok(wireless), - _ => panic!("Not a bond 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.get_connection().await?; - match &mut connection.config { - ConnectionConfig::Wireless(wireless) => func(wireless), - _ => panic!("Not a bond connection. This is most probably a bug."), - } - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(Box::new(connection.clone()))) - .unwrap(); - Ok(()) - } } #[dbus_interface(name = "org.opensuse.Agama1.Network.Connection.Wireless")] @@ -188,13 +127,14 @@ impl Wireless { /// Network SSID. #[dbus_interface(property, name = "SSID")] pub async fn ssid(&self) -> zbus::fdo::Result> { - let config = self.get_config().await?; + 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?; + self.update_config::(|c| c.ssid = SSID(ssid)) + .await?; Ok(()) } @@ -205,27 +145,28 @@ impl Wireless { /// See [crate::network::model::WirelessMode]. #[dbus_interface(property)] pub async fn mode(&self) -> zbus::fdo::Result { - let config = self.get_config().await?; + 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?; + 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?; + 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| { + self.update_config::(|c| { c.password = if password.is_empty() { None } else { @@ -244,7 +185,7 @@ impl Wireless { /// See [crate::network::model::SecurityProtocol]. #[dbus_interface(property)] pub async fn security(&self) -> zbus::fdo::Result { - let config = self.get_config().await?; + let config = self.get_config::().await?; Ok(config.security.to_string()) } @@ -253,7 +194,21 @@ impl Wireless { let security: SecurityProtocol = security .try_into() .map_err(|_| NetworkStateError::InvalidSecurityProtocol(security.to_string()))?; - self.update_config(|c| c.security = security).await?; + 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 index 24e28f3af4..e8df645ec5 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use std::{str::FromStr, sync::Arc}; use tokio::sync::{mpsc::UnboundedSender, oneshot, Mutex, MutexGuard}; use uuid::Uuid; @@ -7,12 +8,8 @@ use zbus::{ SignalContext, }; -use crate::network::{ - dbus::ObjectsRegistry, - error::NetworkStateError, - model::{Connection as NetworkConnection, MacAddress}, - Action, -}; +use super::common::ConnectionInterface; +use crate::network::{dbus::ObjectsRegistry, error::NetworkStateError, model::MacAddress, Action}; /// D-Bus interface for the set of connections. /// @@ -118,39 +115,13 @@ impl Connection { /// Creates a Connection interface object. /// /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. + /// * `uuid`: network connection's UUID. pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { Self { actions: Arc::new(Mutex::new(actions)), uuid, } } - - /// Returns the underlying 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())) - } - - /// Updates the connection data in the NetworkSystem. - /// - /// * `connection`: Updated connection. - pub async fn update_connection(&self, func: F) -> Result<(), NetworkStateError> - where - F: FnOnce(&mut NetworkConnection), - { - let mut connection = self.get_connection().await?; - func(&mut connection); - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(Box::new(connection))) - .unwrap(); - Ok(()) - } } #[dbus_interface(name = "org.opensuse.Agama1.Network.Connection")] @@ -234,101 +205,101 @@ impl Connection { } } +#[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>>, - connection: Arc>, + uuid: Uuid, } 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 { + /// * `uuid`: nework connection's 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(Box::new(connection.clone()))) - .unwrap(); - Ok(()) - } } #[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() + 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<()> { - let mut connection = self.get_connection().await; - connection.match_config.driver = driver; - self.update_connection(connection).await + 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) -> Vec { - let connection = self.get_connection().await; - connection.match_config.path.clone() + 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<()> { - let mut connection = self.get_connection().await; - connection.match_config.path = path; - self.update_connection(connection).await + 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) -> Vec { - let connection = self.get_connection().await; - connection.match_config.interface.clone() + 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<()> { - let mut connection = self.get_connection().await; - connection.match_config.interface = interface; - self.update_connection(connection).await + 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) -> Vec { - let connection = self.get_connection().await; - connection.match_config.kernel.clone() + 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<()> { - let mut connection = self.get_connection().await; - connection.match_config.kernel = kernel; - self.update_connection(connection).await + 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/ip_config.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs index cc9688f60b..662b56f2df 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 @@ -7,15 +7,18 @@ use crate::network::{ action::Action, error::NetworkStateError, - model::{Connection as NetworkConnection, IpConfig, Ipv4Method, Ipv6Method}, + model::{IpConfig, Ipv4Method, Ipv6Method}, }; +use async_trait::async_trait; use cidr::IpInet; use std::{net::IpAddr, sync::Arc}; -use tokio::sync::Mutex; -use tokio::sync::{mpsc::UnboundedSender, oneshot}; +use tokio::sync::mpsc::UnboundedSender; +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>>, @@ -34,16 +37,6 @@ impl Ip { } } - /// 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())) - } - /// Returns the IpConfig struct. async fn get_ip_config(&self) -> Result { self.get_connection().await.map(|c| c.ip_config) @@ -52,16 +45,12 @@ impl Ip { /// 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); - let actions = self.actions.lock().await; - actions - .send(Action::UpdateConnection(Box::new(connection.clone()))) - .unwrap(); + self.update_connection(move |c| func(&mut c.ip_config)) + .await?; Ok(()) } } @@ -81,7 +70,7 @@ impl Ip { #[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,7 +88,7 @@ impl Ip { #[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,7 +105,7 @@ impl Ip { #[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. @@ -134,7 +123,7 @@ impl Ip { #[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 } @@ -154,7 +143,7 @@ impl Ip { #[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. @@ -173,7 +162,7 @@ impl Ip { #[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 } } @@ -224,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/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 568eb11cbf..ba1f561ea3 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -93,7 +93,6 @@ impl Tree { } log::info!("Publishing network connection '{}'", id); - let cloned = Arc::new(Mutex::new(conn.clone())); self.add_interface( &path, interfaces::Connection::new(self.actions.clone(), uuid), @@ -103,11 +102,8 @@ impl Tree { 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)) From 3eb41cd681e03a1fa6c58d7878e59a3b7da4fa44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 27 Dec 2023 12:48:23 +0000 Subject: [PATCH 14/26] Documentation fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Knut Alejandro Anderssen González --- rust/agama-dbus-server/src/network/dbus/interfaces/common.rs | 2 +- rust/agama-dbus-server/src/network/dbus/interfaces/ip_config.rs | 2 +- rust/agama-dbus-server/src/network/dbus/tree.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs index 41275c0388..e5a49f122c 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/common.rs @@ -36,7 +36,7 @@ pub trait ConnectionInterface { /// Updates the connection data in the NetworkSystem. /// - /// * `connection`: Updated connection. + /// * `func`: function to update the connection. async fn update_connection(&self, func: F) -> Result<(), NetworkStateError> where F: FnOnce(&mut NetworkConnection) + std::marker::Send, 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 662b56f2df..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 @@ -29,7 +29,7 @@ impl Ip { /// Creates an IP interface object. /// /// * `actions`: sending-half of a channel to send actions. - /// * `connection`: connection to expose over D-Bus. + /// * `uuid`: connection UUID.. pub fn new(actions: UnboundedSender, uuid: Uuid) -> Self { Self { actions: Arc::new(Mutex::new(actions)), diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index ba1f561ea3..d6080508d9 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -78,7 +78,7 @@ impl 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, From 7b00e7ed1f247ae22873c01f73a5c6337d72586d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 27 Dec 2023 12:38:52 +0000 Subject: [PATCH 15/26] Fix several issues detected by Clippy --- rust/agama-cli/src/logs.rs | 7 ++----- rust/agama-dbus-server/src/locale/timezone.rs | 8 ++------ rust/agama-dbus-server/src/network/dbus/service.rs | 1 - rust/agama-dbus-server/src/network/nm/dbus.rs | 5 +---- rust/agama-dbus-server/tests/common/mod.rs | 2 +- rust/agama-dbus-server/tests/network.rs | 4 ++-- rust/agama-lib/src/storage/store.rs | 2 -- rust/agama-locale-data/src/lib.rs | 4 ++-- 8 files changed, 10 insertions(+), 23 deletions(-) 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/src/locale/timezone.rs b/rust/agama-dbus-server/src/locale/timezone.rs index d032122d66..bfa7ddae8f 100644 --- a/rust/agama-dbus-server/src/locale/timezone.rs +++ b/rust/agama-dbus-server/src/locale/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/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/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/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..71bbf8f638 100644 --- a/rust/agama-dbus-server/tests/network.rs +++ b/rust/agama-dbus-server/tests/network.rs @@ -102,7 +102,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 +128,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/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-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 From 4a76c9eac15973190cc4ec3394d3d9a04f41a311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 27 Dec 2023 12:48:53 +0000 Subject: [PATCH 16/26] Rename agama-dbus-server "locale" to "l10n" --- rust/agama-dbus-server/src/{locale.rs => l10n.rs} | 0 rust/agama-dbus-server/src/{locale => l10n}/helpers.rs | 0 rust/agama-dbus-server/src/{locale => l10n}/keyboard.rs | 0 rust/agama-dbus-server/src/{locale => l10n}/locale.rs | 0 rust/agama-dbus-server/src/{locale => l10n}/timezone.rs | 0 rust/agama-dbus-server/src/lib.rs | 2 +- rust/agama-dbus-server/src/main.rs | 7 +++++-- 7 files changed, 6 insertions(+), 3 deletions(-) rename rust/agama-dbus-server/src/{locale.rs => l10n.rs} (100%) rename rust/agama-dbus-server/src/{locale => l10n}/helpers.rs (100%) rename rust/agama-dbus-server/src/{locale => l10n}/keyboard.rs (100%) rename rust/agama-dbus-server/src/{locale => l10n}/locale.rs (100%) rename rust/agama-dbus-server/src/{locale => l10n}/timezone.rs (100%) 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 100% rename from rust/agama-dbus-server/src/locale/timezone.rs rename to rust/agama-dbus-server/src/l10n/timezone.rs 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"); From c7d43c88a813ac83174204d3017c11059f0d8059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 27 Dec 2023 13:20:03 +0000 Subject: [PATCH 17/26] Define some 'types' for D-Bus proxies --- .../src/network/nm/proxies.rs | 12 +----- rust/agama-lib/src/software/proxies.rs | 39 ++++++++++++------- rust/agama-lib/src/users/client.rs | 12 +----- rust/agama-lib/src/users/proxies.rs | 27 ++++++++----- 4 files changed, 48 insertions(+), 42 deletions(-) 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-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/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)] From d5860fabccfa85232a42efffede0039fedf06c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 27 Dec 2023 13:29:37 +0000 Subject: [PATCH 18/26] Run Clippy in CI --- .github/workflows/ci-rust.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 From 9bbf3a4cc8705bb6bad9952609d46f01de0e54b5 Mon Sep 17 00:00:00 2001 From: YaST Bot Date: Thu, 28 Dec 2023 08:21:33 +0000 Subject: [PATCH 19/26] Update service PO files Agama-weblate commit: 2ad365d9d13ef872b40b5aa4fb5bd4e99f104def --- service/po/cs.po | 81 ++++++++++++++++++++++++++++++++++++++++- service/po/fr.po | 93 ++++++++++++++++++++++++++++++++++++++++++++---- service/po/id.po | 81 ++++++++++++++++++++++++++++++++++++++++- service/po/ja.po | 84 ++++++++++++++++++++++++++++++++++++++++--- service/po/sv.po | 81 ++++++++++++++++++++++++++++++++++++++++- 5 files changed, 406 insertions(+), 14 deletions(-) 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..39ba9bba52 100644 --- a/service/po/sv.po +++ b/service/po/sv.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-17 18:02+0000\n" "Last-Translator: Luna Jernberg \n" "Language-Team: Swedish Date: Thu, 28 Dec 2023 12:03:04 +0000 Subject: [PATCH 20/26] Update service PO files Agama-weblate commit: d0322d40d617c6790d5a2767fd47ca98ff25f3e0 --- service/po/sv.po | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/service/po/sv.po b/service/po/sv.po index 39ba9bba52..d2dd76f325 100644 --- a/service/po/sv.po +++ b/service/po/sv.po @@ -8,10 +8,10 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-12-27 02:08+0000\n" -"PO-Revision-Date: 2023-12-17 18:02+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" @@ -22,45 +22,43 @@ msgstr "" #. Runs the config phase #: service/lib/agama/manager.rb:88 msgid "Probing Storage" -msgstr "" +msgstr "Undersöker lagring" #: service/lib/agama/manager.rb:89 msgid "Probing Software" -msgstr "" +msgstr "Undersöker programvara" #. Runs the install phase #. rubocop:disable Metrics/AbcSize #: service/lib/agama/manager.rb:109 msgid "Partitioning" -msgstr "" +msgstr "Partitionerar" #. propose software after /mnt is already separated, so it uses proper #. target #: service/lib/agama/manager.rb:117 -#, fuzzy msgid "Installing Software" -msgstr "Initierar källor" +msgstr "Installerar programvara" #: service/lib/agama/manager.rb:120 msgid "Writing Users" -msgstr "" +msgstr "Skriver användare" #: service/lib/agama/manager.rb:121 msgid "Writing Network Configuration" -msgstr "" +msgstr "Skriver nätverkskonfiguration" #: service/lib/agama/manager.rb:122 msgid "Saving Language Settings" -msgstr "" +msgstr "Sparar språkinställningar" #: service/lib/agama/manager.rb:123 -#, fuzzy msgid "Writing repositories information" -msgstr "Skriver förråd till målsystemet" +msgstr "Skriver information om förråd" #: service/lib/agama/manager.rb:124 msgid "Finishing storage configuration" -msgstr "" +msgstr "Slutför lagringskonfiguration" #. Callback to handle unsigned files #. @@ -159,35 +157,34 @@ msgstr "Hittade %s beroendeproblem." #. Probes storage devices and performs an initial proposal #: service/lib/agama/storage/manager.rb:111 msgid "Activating storage devices" -msgstr "" +msgstr "Aktiverar lagringsenheter" #: service/lib/agama/storage/manager.rb:112 msgid "Probing storage devices" -msgstr "" +msgstr "Undersöker lagringsenheter" #: service/lib/agama/storage/manager.rb:113 -#, fuzzy msgid "Calculating the storage proposal" -msgstr "Beräknar programvaruförslaget" +msgstr "Beräknar lagringsförslag" #: service/lib/agama/storage/manager.rb:114 msgid "Selecting Linux Security Modules" -msgstr "" +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 "" +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 "" +msgstr "Lägger till lagrings-relaterade paket" #: service/lib/agama/storage/manager.rb:128 msgid "Preparing the storage devices" -msgstr "" +msgstr "Förbereder lagringsenheter" #: service/lib/agama/storage/manager.rb:129 msgid "Writing bootloader sysconfig" -msgstr "" +msgstr "Skriver starthanterarens sysconfig" From 96036d48294b6e50cd7e4a524b03b6bf020f0f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 28 Dec 2023 21:44:48 +0000 Subject: [PATCH 21/26] Drop the ObjectsRegistry mutex --- rust/agama-dbus-server/src/network/action.rs | 6 ++ rust/agama-dbus-server/src/network/dbus.rs | 2 +- .../network/dbus/interfaces/connections.rs | 34 ++++---- .../src/network/dbus/interfaces/devices.rs | 27 +++---- .../src/network/dbus/tree.rs | 78 ++++++++++--------- rust/agama-dbus-server/src/network/system.rs | 6 ++ 6 files changed, 88 insertions(+), 65 deletions(-) diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index 568410d579..0b3d00b5c2 100644 --- a/rust/agama-dbus-server/src/network/action.rs +++ b/rust/agama-dbus-server/src/network/action.rs @@ -23,11 +23,17 @@ pub enum Action { ), /// 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( 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/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs index e8df645ec5..9fa57d7e6f 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -9,23 +9,21 @@ use zbus::{ }; use super::common::ConnectionInterface; -use crate::network::{dbus::ObjectsRegistry, error::NetworkStateError, model::MacAddress, Action}; +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>>, - objects: Arc>, } impl Connections { /// Creates a Connections interface object. /// /// * `objects`: Objects paths registry. - pub fn new(objects: Arc>, actions: UnboundedSender) -> Self { + pub fn new(actions: UnboundedSender) -> Self { Self { - objects, actions: Arc::new(Mutex::new(actions)), } } @@ -34,13 +32,12 @@ impl Connections { #[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() + 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. @@ -67,11 +64,16 @@ impl Connections { /// /// * `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()), - } + 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. diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs index 41339e01e5..8f29d84f35 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/devices.rs @@ -1,34 +1,35 @@ -use crate::network::{dbus::ObjectsRegistry, model::Device as NetworkDevice}; +use crate::network::{model::Device as NetworkDevice, Action}; use std::sync::Arc; -use tokio::sync::Mutex; -use zbus::{dbus_interface, zvariant::ObjectPath}; +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 { - objects: Arc>, + actions: Arc>>, } impl Devices { /// Creates a Devices interface object. /// /// * `objects`: Objects paths registry. - pub fn new(objects: Arc>) -> Self { - Self { objects } + 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) -> Vec { - let objects = self.objects.lock().await; - objects - .devices_paths() - .iter() - .filter_map(|c| ObjectPath::try_from(c.clone()).ok()) - .collect() + 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) } } diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index d6080508d9..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,15 +62,11 @@ 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(()) } @@ -81,17 +76,16 @@ impl Tree { /// * `conn`: connection to add. /// * `notify`: whether to notify the added connection pub async fn add_connection( - &self, + &mut self, conn: &mut Connection, ) -> Result { - let mut objects = self.objects.lock().await; - 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); self.add_interface( &path, @@ -115,33 +109,49 @@ impl Tree { .await?; } - Ok(path.into()) + 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).await?; } self.add_interface( CONNECTIONS_PATH, - interfaces::Connections::new(Arc::clone(&self.objects), self.actions.clone()), + interfaces::Connections::new(self.actions.clone()), ) .await?; @@ -149,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(()) } @@ -185,7 +193,7 @@ 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, { @@ -198,7 +206,7 @@ impl Tree { /// /// 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 @@ -246,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/system.rs b/rust/agama-dbus-server/src/network/system.rs index b0850064e5..7387ad6a6d 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -77,10 +77,16 @@ impl NetworkSystem { let conn = self.state.get_connection_by_uuid(uuid); tx.send(conn.cloned()).unwrap(); } + Action::GetConnectionPath(id, tx) => { + let path = self.tree.connection_path(&id); + tx.send(path).unwrap(); + } Action::GetController(uuid, tx) => { let result = self.get_controller_action(uuid); tx.send(result).unwrap() } + Action::GetDevicesPaths(tx) => tx.send(self.tree.devices_paths()).unwrap(), + Action::GetConnectionsPaths(tx) => tx.send(self.tree.connections_paths()).unwrap(), Action::SetPorts(uuid, ports, rx) => { let result = self.set_ports_action(uuid, *ports); rx.send(result).unwrap(); From d83b01ec5324afd64bc2b3a0f6d30dbaaabe7dde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 28 Dec 2023 21:41:37 +0000 Subject: [PATCH 22/26] Use async_trait in the network Adapter trait --- rust/agama-dbus-server/src/network/adapter.rs | 6 ++- .../src/network/nm/adapter.rs | 54 ++++++++----------- rust/agama-dbus-server/src/network/system.rs | 6 +-- rust/agama-dbus-server/tests/network.rs | 9 +++- 4 files changed, 37 insertions(+), 38 deletions(-) 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/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/system.rs b/rust/agama-dbus-server/src/network/system.rs index 7387ad6a6d..416c9e261f 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -33,8 +33,8 @@ impl NetworkSystem { /// 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(()) } @@ -47,7 +47,7 @@ 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.state = self.adapter.read().await?; self.tree .set_connections(&mut self.state.connections) .await?; diff --git a/rust/agama-dbus-server/tests/network.rs b/rust/agama-dbus-server/tests/network.rs index 71bbf8f638..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"); } } From 7d84f6d30cc8a9f1c5d459092cba362752ccb0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 29 Dec 2023 09:35:10 +0000 Subject: [PATCH 23/26] Set minimal Rust version to 1.74 * It prevents a "future cannot be sent between threads safely" error. --- rust/Cargo.toml | 6 +++++- rust/agama-dbus-server/Cargo.toml | 5 +++-- 2 files changed, 8 insertions(+), 3 deletions(-) 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-dbus-server/Cargo.toml b/rust/agama-dbus-server/Cargo.toml index 4079ee01ca..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" From f652948da29b33635d5326a0a4eaaca433f1d198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 28 Dec 2023 11:20:56 +0000 Subject: [PATCH 24/26] Improve ServiceError reporting --- rust/agama-lib/src/error.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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), } From 1c9ec669e2ac627eecae2fd7afefbebb726e2a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 29 Dec 2023 08:34:09 +0000 Subject: [PATCH 25/26] Avoid a deadlock updating the network D-Bus tree Updating the tree must be done in a separate task. Otherwise, it could fight with an incoming request for the ObjectServer mutex, blocking the actions dispatching. --- rust/agama-dbus-server/src/network/system.rs | 50 +++++++++++++------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index 416c9e261f..097c0fd5e0 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -1,8 +1,11 @@ use super::error::NetworkStateError; use crate::network::{dbus::Tree, model::Connection, Action, Adapter, NetworkState}; use agama_lib::network::types::DeviceType; -use std::error::Error; -use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; +use std::{error::Error, sync::Arc}; +use tokio::sync::{ + mpsc::{self, UnboundedReceiver, UnboundedSender}, + Mutex, +}; use uuid::Uuid; use zbus::zvariant::OwnedObjectPath; @@ -13,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, } @@ -26,7 +29,7 @@ impl NetworkSystem { state: NetworkState::default(), actions_tx, actions_rx, - tree, + tree: Arc::new(Mutex::new(tree)), adapter, } } @@ -48,10 +51,9 @@ 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().await?; - self.tree - .set_connections(&mut self.state.connections) - .await?; - self.tree.set_devices(&self.state.devices).await?; + let mut tree = self.tree.lock().await; + tree.set_connections(&mut self.state.connections).await?; + tree.set_devices(&self.state.devices).await?; Ok(()) } @@ -78,15 +80,22 @@ impl NetworkSystem { tx.send(conn.cloned()).unwrap(); } Action::GetConnectionPath(id, tx) => { - let path = self.tree.connection_path(&id); + 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); tx.send(result).unwrap() } - Action::GetDevicesPaths(tx) => tx.send(self.tree.devices_paths()).unwrap(), - Action::GetConnectionsPaths(tx) => tx.send(self.tree.connections_paths()).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); rx.send(result).unwrap(); @@ -95,16 +104,23 @@ impl NetworkSystem { 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; + _ = tree.set_connections(&mut connections).await; + }); } } @@ -118,8 +134,8 @@ impl NetworkSystem { ) -> Result { let mut conn = Connection::new(name, ty); // TODO: handle tree handling problems - let path = self - .tree + let mut tree = self.tree.lock().await; + let path = tree .add_connection(&mut conn) .await .expect("Could not update the D-Bus tree"); From c40e25168b8b8b2fe318a26abc384d9c4bf9657f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 29 Dec 2023 10:35:55 +0000 Subject: [PATCH 26/26] Log an error when the D-Bus tree cannot be updated --- rust/agama-dbus-server/src/network/system.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index 097c0fd5e0..5272d58596 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -119,7 +119,9 @@ impl NetworkSystem { let tree = Arc::clone(&self.tree); tokio::spawn(async move { let mut tree = tree.lock().await; - _ = tree.set_connections(&mut connections).await; + if let Err(e) = tree.set_connections(&mut connections).await { + log::error!("Could not update the D-Bus tree: {}", e); + } }); } }