use enumflags2::{bitflags, BitFlags};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use static_assertions::assert_impl_all;
use std::collections::HashMap;
use zbus_names::{
BusName, InterfaceName, OwnedBusName, OwnedInterfaceName, OwnedUniqueName, UniqueName,
WellKnownName,
};
use zvariant::{
DeserializeDict, ObjectPath, Optional, OwnedObjectPath, OwnedValue, SerializeDict, Type, Value,
};
use crate::{
dbus_interface, dbus_proxy, DBusError, Guid, MessageHeader, ObjectServer, SignalContext,
};
#[rustfmt::skip]
macro_rules! gen_introspectable_proxy {
($gen_async:literal, $gen_blocking:literal) => {
#[dbus_proxy(
interface = "org.freedesktop.DBus.Introspectable",
default_path = "/",
gen_async = $gen_async,
gen_blocking = $gen_blocking,
)]
trait Introspectable {
fn introspect(&self) -> Result<String>;
}
};
}
gen_introspectable_proxy!(true, false);
assert_impl_all!(IntrospectableProxy<'_>: Send, Sync, Unpin);
pub(crate) struct Introspectable;
#[dbus_interface(name = "org.freedesktop.DBus.Introspectable")]
impl Introspectable {
async fn introspect(
&self,
#[zbus(object_server)] server: &ObjectServer,
#[zbus(header)] header: MessageHeader<'_>,
) -> Result<String> {
let path = header.path()?.ok_or(crate::Error::MissingField)?;
let root = server.root().read().await;
let node = root
.get_child(path)
.ok_or_else(|| Error::UnknownObject(format!("Unknown object '{path}'")))?;
Ok(node.introspect().await)
}
}
#[rustfmt::skip]
macro_rules! gen_properties_proxy {
($gen_async:literal, $gen_blocking:literal) => {
#[dbus_proxy(
interface = "org.freedesktop.DBus.Properties",
assume_defaults = true,
gen_async = $gen_async,
gen_blocking = $gen_blocking,
)]
trait Properties {
async fn get(
&self,
interface_name: InterfaceName<'_>,
property_name: &str,
) -> Result<OwnedValue>;
async fn set(
&self,
interface_name: InterfaceName<'_>,
property_name: &str,
value: &Value<'_>,
) -> Result<()>;
async fn get_all(
&self,
interface_name: InterfaceName<'_>,
) -> Result<HashMap<String, OwnedValue>>;
#[dbus_proxy(signal)]
async fn properties_changed(
&self,
interface_name: InterfaceName<'_>,
changed_properties: HashMap<&str, Value<'_>>,
invalidated_properties: Vec<&str>,
) -> Result<()>;
}
};
}
gen_properties_proxy!(true, false);
assert_impl_all!(PropertiesProxy<'_>: Send, Sync, Unpin);
pub struct Properties;
assert_impl_all!(Properties: Send, Sync, Unpin);
#[dbus_interface(name = "org.freedesktop.DBus.Properties")]
impl Properties {
async fn get(
&self,
interface_name: InterfaceName<'_>,
property_name: &str,
#[zbus(object_server)] server: &ObjectServer,
#[zbus(header)] header: MessageHeader<'_>,
) -> Result<OwnedValue> {
let path = header.path()?.ok_or(crate::Error::MissingField)?;
let root = server.root().read().await;
let iface = root
.get_child(path)
.and_then(|node| node.interface_lock(interface_name.as_ref()))
.ok_or_else(|| {
Error::UnknownInterface(format!("Unknown interface '{interface_name}'"))
})?;
let res = iface.read().await.get(property_name).await;
res.unwrap_or_else(|| {
Err(Error::UnknownProperty(format!(
"Unknown property '{property_name}'"
)))
})
}
async fn set(
&self,
interface_name: InterfaceName<'_>,
property_name: &str,
value: Value<'_>,
#[zbus(object_server)] server: &ObjectServer,
#[zbus(header)] header: MessageHeader<'_>,
#[zbus(signal_context)] ctxt: SignalContext<'_>,
) -> Result<()> {
let path = header.path()?.ok_or(crate::Error::MissingField)?;
let root = server.root().read().await;
let iface = root
.get_child(path)
.and_then(|node| node.interface_lock(interface_name.as_ref()))
.ok_or_else(|| {
Error::UnknownInterface(format!("Unknown interface '{interface_name}'"))
})?;
match iface.read().await.set(property_name, &value, &ctxt) {
zbus::DispatchResult::RequiresMut => {}
zbus::DispatchResult::NotFound => {
return Err(Error::UnknownProperty(format!(
"Unknown property '{property_name}'"
)));
}
zbus::DispatchResult::Async(f) => {
return f.await.map_err(Into::into);
}
}
let res = iface
.write()
.await
.set_mut(property_name, &value, &ctxt)
.await;
res.unwrap_or_else(|| {
Err(Error::UnknownProperty(format!(
"Unknown property '{property_name}'"
)))
})
}
async fn get_all(
&self,
interface_name: InterfaceName<'_>,
#[zbus(object_server)] server: &ObjectServer,
#[zbus(header)] header: MessageHeader<'_>,
) -> Result<HashMap<String, OwnedValue>> {
let path = header.path()?.ok_or(crate::Error::MissingField)?;
let root = server.root().read().await;
let iface = root
.get_child(path)
.and_then(|node| node.interface_lock(interface_name.as_ref()))
.ok_or_else(|| {
Error::UnknownInterface(format!("Unknown interface '{interface_name}'"))
})?;
let res = iface.read().await.get_all().await;
Ok(res)
}
#[dbus_interface(signal)]
#[rustfmt::skip]
pub async fn properties_changed(
ctxt: &SignalContext<'_>,
interface_name: InterfaceName<'_>,
changed_properties: &HashMap<&str, &Value<'_>>,
invalidated_properties: &[&str],
) -> zbus::Result<()>;
}
pub type ManagedObjects =
HashMap<OwnedObjectPath, HashMap<OwnedInterfaceName, HashMap<String, OwnedValue>>>;
#[rustfmt::skip]
macro_rules! gen_object_manager_proxy {
($gen_async:literal, $gen_blocking:literal) => {
#[dbus_proxy(
interface = "org.freedesktop.DBus.ObjectManager",
assume_defaults = true,
gen_async = $gen_async,
gen_blocking = $gen_blocking,
)]
trait ObjectManager {
fn get_managed_objects(&self) -> Result<ManagedObjects>;
#[dbus_proxy(signal)]
fn interfaces_added(
&self,
object_path: ObjectPath<'_>,
interfaces_and_properties: HashMap<&str, HashMap<&str, Value<'_>>>,
) -> Result<()>;
#[dbus_proxy(signal)]
fn interfaces_removed(
&self,
object_path: ObjectPath<'_>,
interfaces: Vec<&str>,
) -> Result<()>;
}
};
}
gen_object_manager_proxy!(true, false);
assert_impl_all!(ObjectManagerProxy<'_>: Send, Sync, Unpin);
#[derive(Debug, Clone)]
pub struct ObjectManager;
#[dbus_interface(name = "org.freedesktop.DBus.ObjectManager")]
impl ObjectManager {
async fn get_managed_objects(
&self,
#[zbus(object_server)] server: &ObjectServer,
#[zbus(header)] header: MessageHeader<'_>,
) -> Result<ManagedObjects> {
let path = header.path()?.ok_or(crate::Error::MissingField)?;
let root = server.root().read().await;
let node = root
.get_child(path)
.ok_or_else(|| Error::UnknownObject(format!("Unknown object '{path}'")))?;
Ok(node.get_managed_objects().await)
}
#[dbus_interface(signal)]
pub async fn interfaces_added(
ctxt: &SignalContext<'_>,
object_path: &ObjectPath<'_>,
interfaces_and_properties: &HashMap<InterfaceName<'_>, HashMap<&str, Value<'_>>>,
) -> zbus::Result<()>;
#[dbus_interface(signal)]
pub async fn interfaces_removed(
ctxt: &SignalContext<'_>,
object_path: &ObjectPath<'_>,
interfaces: &[InterfaceName<'_>],
) -> zbus::Result<()>;
}
#[rustfmt::skip]
macro_rules! gen_peer_proxy {
($gen_async:literal, $gen_blocking:literal) => {
#[dbus_proxy(
interface = "org.freedesktop.DBus.Peer",
assume_defaults = true,
gen_async = $gen_async,
gen_blocking = $gen_blocking,
)]
trait Peer {
fn ping(&self) -> Result<()>;
fn get_machine_id(&self) -> Result<String>;
}
};
}
gen_peer_proxy!(true, false);
assert_impl_all!(PeerProxy<'_>: Send, Sync, Unpin);
pub(crate) struct Peer;
#[dbus_interface(name = "org.freedesktop.DBus.Peer")]
impl Peer {
fn ping(&self) {}
fn get_machine_id(&self) -> Result<String> {
let mut id = match std::fs::read_to_string("/var/lib/dbus/machine-id") {
Ok(id) => id,
Err(e) => {
if let Ok(id) = std::fs::read_to_string("/etc/machine-id") {
id
} else {
return Err(Error::IOError(format!(
"Failed to read from /var/lib/dbus/machine-id or /etc/machine-id: {e}"
)));
}
}
};
let len = id.trim_end().len();
id.truncate(len);
Ok(id)
}
}
#[rustfmt::skip]
macro_rules! gen_monitoring_proxy {
($gen_async:literal, $gen_blocking:literal) => {
#[dbus_proxy(
interface = "org.freedesktop.DBus.Monitoring",
default_service = "org.freedesktop.DBus",
default_path = "/org/freedesktop/DBus",
assume_defaults = true,
gen_async = $gen_async,
gen_blocking = $gen_blocking,
)]
trait Monitoring {
fn become_monitor(&self, n1: &[&str], n2: u32) -> Result<()>;
}
};
}
gen_monitoring_proxy!(true, false);
assert_impl_all!(MonitoringProxy<'_>: Send, Sync, Unpin);
#[rustfmt::skip]
macro_rules! gen_stats_proxy {
($gen_async:literal, $gen_blocking:literal) => {
#[dbus_proxy(
interface = "org.freedesktop.DBus.Debug.Stats",
default_service = "org.freedesktop.DBus",
default_path = "/org/freedesktop/DBus",
assume_defaults = true,
gen_async = $gen_async,
gen_blocking = $gen_blocking,
)]
trait Stats {
fn get_stats(&self) -> Result<Vec<HashMap<String, OwnedValue>>>;
fn get_connection_stats(&self, n1: &str) -> Result<Vec<HashMap<String, OwnedValue>>>;
fn get_all_match_rules(&self) -> Result<Vec<HashMap<String, Vec<String>>>>;
}
};
}
gen_stats_proxy!(true, false);
assert_impl_all!(StatsProxy<'_>: Send, Sync, Unpin);
#[bitflags]
#[repr(u32)]
#[derive(Type, Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
pub enum RequestNameFlags {
AllowReplacement = 0x01,
ReplaceExisting = 0x02,
DoNotQueue = 0x04,
}
assert_impl_all!(RequestNameFlags: Send, Sync, Unpin);
#[repr(u32)]
#[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq, Eq)]
pub enum RequestNameReply {
PrimaryOwner = 0x01,
InQueue = 0x02,
Exists = 0x03,
AlreadyOwner = 0x04,
}
assert_impl_all!(RequestNameReply: Send, Sync, Unpin);
#[repr(u32)]
#[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq, Eq)]
pub enum ReleaseNameReply {
Released = 0x01,
NonExistent = 0x02,
NotOwner = 0x03,
}
assert_impl_all!(ReleaseNameReply: Send, Sync, Unpin);
#[derive(Debug, Default, DeserializeDict, PartialEq, Eq, SerializeDict, Type)]
#[zvariant(signature = "a{sv}")]
pub struct ConnectionCredentials {
#[zvariant(rename = "UnixUserID")]
#[deprecated(since = "3.13.0", note = "Use `unix_user_id` method")]
pub unix_user_id: Option<u32>,
#[zvariant(rename = "UnixGroupIDs")]
#[deprecated(since = "3.13.0", note = "Use `unix_group_ids` method")]
pub unix_group_ids: Option<Vec<u32>>,
#[zvariant(rename = "ProcessID")]
#[deprecated(since = "3.13.0", note = "Use `process_id` method")]
pub process_id: Option<u32>,
#[zvariant(rename = "WindowsSID")]
#[deprecated(since = "3.13.0", note = "Use `windows_sid` method")]
pub windows_sid: Option<String>,
#[zvariant(rename = "LinuxSecurityLabel")]
#[deprecated(since = "3.13.0", note = "Use `linux_security_label` method")]
pub linux_security_label: Option<Vec<u8>>,
}
#[allow(deprecated)]
impl ConnectionCredentials {
pub fn unix_user_id(&self) -> Option<u32> {
self.unix_user_id
}
pub fn unix_group_ids(&self) -> Option<&Vec<u32>> {
self.unix_group_ids.as_ref()
}
pub fn into_unix_group_ids(self) -> Option<Vec<u32>> {
self.unix_group_ids
}
pub fn process_id(&self) -> Option<u32> {
self.process_id
}
pub fn windows_sid(&self) -> Option<&String> {
self.windows_sid.as_ref()
}
pub fn into_windows_sid(self) -> Option<String> {
self.windows_sid
}
pub fn linux_security_label(&self) -> Option<&Vec<u8>> {
self.linux_security_label.as_ref()
}
pub fn into_linux_security_label(self) -> Option<Vec<u8>> {
self.linux_security_label
}
pub fn set_unix_user_id(mut self, unix_user_id: u32) -> Self {
self.unix_user_id = Some(unix_user_id);
self
}
pub fn add_unix_group_id(mut self, unix_group_id: u32) -> Self {
self.unix_group_ids
.get_or_insert_with(Vec::new)
.push(unix_group_id);
self
}
pub fn set_process_id(mut self, process_id: u32) -> Self {
self.process_id = Some(process_id);
self
}
pub fn set_windows_sid(mut self, windows_sid: String) -> Self {
self.windows_sid = Some(windows_sid);
self
}
pub fn set_linux_security_label(mut self, linux_security_label: Vec<u8>) -> Self {
self.linux_security_label = Some(linux_security_label);
self
}
}
#[rustfmt::skip]
macro_rules! gen_dbus_proxy {
($gen_async:literal, $gen_blocking:literal) => {
#[dbus_proxy(
assume_defaults = true,
interface = "org.freedesktop.DBus",
gen_async = $gen_async,
gen_blocking = $gen_blocking,
)]
trait DBus {
#[deprecated(since = "3.5.0", note = "Use `add_match_rule` instead")]
fn add_match(&self, rule: &str) -> Result<()>;
#[dbus_proxy(name = "AddMatch")]
fn add_match_rule(&self, rule: crate::MatchRule<'_>) -> Result<()>;
fn get_adt_audit_session_data(&self, bus_name: BusName<'_>) -> Result<Vec<u8>>;
fn get_connection_credentials(
&self,
bus_name: BusName<'_>,
) -> Result<ConnectionCredentials>;
#[dbus_proxy(name = "GetConnectionSELinuxSecurityContext")]
fn get_connection_selinux_security_context(
&self,
bus_name: BusName<'_>,
) -> Result<Vec<u8>>;
#[dbus_proxy(name = "GetConnectionUnixProcessID")]
fn get_connection_unix_process_id(&self, bus_name: BusName<'_>) -> Result<u32>;
fn get_connection_unix_user(&self, bus_name: BusName<'_>) -> Result<u32>;
fn get_id(&self) -> Result<Guid>;
fn get_name_owner(&self, name: BusName<'_>) -> Result<OwnedUniqueName>;
fn hello(&self) -> Result<OwnedUniqueName>;
fn list_activatable_names(&self) -> Result<Vec<OwnedBusName>>;
fn list_names(&self) -> Result<Vec<OwnedBusName>>;
fn list_queued_owners(&self, name: WellKnownName<'_>) -> Result<Vec<OwnedUniqueName>>;
fn name_has_owner(&self, name: BusName<'_>) -> Result<bool>;
fn release_name(&self, name: WellKnownName<'_>) -> Result<ReleaseNameReply>;
fn reload_config(&self) -> Result<()>;
#[deprecated(since = "3.5.0", note = "Use `remove_match_rule` instead")]
fn remove_match(&self, rule: &str) -> Result<()>;
#[dbus_proxy(name = "RemoveMatch")]
fn remove_match_rule(&self, rule: crate::MatchRule<'_>) -> Result<()>;
fn request_name(
&self,
name: WellKnownName<'_>,
flags: BitFlags<RequestNameFlags>,
) -> Result<RequestNameReply>;
fn start_service_by_name(&self, name: WellKnownName<'_>, flags: u32) -> Result<u32>;
fn update_activation_environment(&self, environment: HashMap<&str, &str>)
-> Result<()>;
#[dbus_proxy(signal)]
fn name_owner_changed(
&self,
name: BusName<'_>,
old_owner: Optional<UniqueName<'_>>,
new_owner: Optional<UniqueName<'_>>,
);
#[dbus_proxy(signal)]
fn name_lost(&self, name: BusName<'_>);
#[dbus_proxy(signal)]
fn name_acquired(&self, name: BusName<'_>);
#[dbus_proxy(property)]
fn features(&self) -> Result<Vec<String>>;
#[dbus_proxy(property)]
fn interfaces(&self) -> Result<Vec<OwnedInterfaceName>>;
}
};
}
gen_dbus_proxy!(true, false);
assert_impl_all!(DBusProxy<'_>: Send, Sync, Unpin);
#[derive(Clone, Debug, DBusError, PartialEq)]
#[dbus_error(prefix = "org.freedesktop.DBus.Error", impl_display = true)]
#[allow(clippy::upper_case_acronyms)]
pub enum Error {
#[dbus_error(zbus_error)]
ZBus(zbus::Error),
Failed(String),
NoMemory(String),
ServiceUnknown(String),
NameHasNoOwner(String),
NoReply(String),
IOError(String),
BadAddress(String),
NotSupported(String),
LimitsExceeded(String),
AccessDenied(String),
AuthFailed(String),
NoServer(String),
Timeout(String),
NoNetwork(String),
AddressInUse(String),
Disconnected(String),
InvalidArgs(String),
FileNotFound(String),
FileExists(String),
UnknownMethod(String),
UnknownObject(String),
UnknownInterface(String),
UnknownProperty(String),
PropertyReadOnly(String),
TimedOut(String),
MatchRuleNotFound(String),
MatchRuleInvalid(String),
#[dbus_error(name = "Spawn.ExecFailed")]
SpawnExecFailed(String),
#[dbus_error(name = "Spawn.ForkFailed")]
SpawnForkFailed(String),
#[dbus_error(name = "Spawn.ChildExited")]
SpawnChildExited(String),
#[dbus_error(name = "Spawn.ChildSignaled")]
SpawnChildSignaled(String),
#[dbus_error(name = "Spawn.Failed")]
SpawnFailed(String),
#[dbus_error(name = "Spawn.FailedToSetup")]
SpawnFailedToSetup(String),
#[dbus_error(name = "Spawn.ConfigInvalid")]
SpawnConfigInvalid(String),
#[dbus_error(name = "Spawn.ServiceNotValid")]
SpawnServiceNotValid(String),
#[dbus_error(name = "Spawn.ServiceNotFound")]
SpawnServiceNotFound(String),
#[dbus_error(name = "Spawn.PermissionsInvalid")]
SpawnPermissionsInvalid(String),
#[dbus_error(name = "Spawn.FileInvalid")]
SpawnFileInvalid(String),
#[dbus_error(name = "Spawn.NoMemory")]
SpawnNoMemory(String),
UnixProcessIdUnknown(String),
InvalidSignature(String),
InvalidFileContent(String),
SELinuxSecurityContextUnknown(String),
AdtAuditDataUnknown(String),
ObjectPathInUse(String),
InconsistentMessage(String),
InteractiveAuthorizationRequired(String),
NotContainer(String),
}
assert_impl_all!(Error: Send, Sync, Unpin);
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use crate::{fdo, DBusError, Error, Message};
use futures_util::StreamExt;
use ntest::timeout;
use std::convert::TryInto;
use test_log::test;
use tokio::runtime;
use zbus_names::WellKnownName;
#[test]
fn error_from_zerror() {
let m = Message::method(Some(":1.2"), None::<()>, "/", None::<()>, "foo", &()).unwrap();
let m = Message::method_error(
None::<()>,
&m,
"org.freedesktop.DBus.Error.TimedOut",
&("so long"),
)
.unwrap();
let e: Error = m.into();
let e: fdo::Error = e.into();
assert_eq!(e, fdo::Error::TimedOut("so long".to_string()),);
assert_eq!(e.name(), "org.freedesktop.DBus.Error.TimedOut");
assert_eq!(e.description(), Some("so long"));
}
#[test]
#[timeout(15000)]
fn signal() {
runtime::Runtime::new().unwrap().block_on(test_signal());
runtime::Builder::new_current_thread()
.enable_io()
.build()
.unwrap()
.block_on(test_signal());
}
async fn test_signal() {
let conn = crate::Connection::session().await.unwrap();
let proxy = fdo::DBusProxy::new(&conn).await.unwrap();
let well_known = "org.freedesktop.zbus.FdoSignalStreamTest";
let unique_name = conn.unique_name().unwrap();
let owner_change_stream = proxy
.receive_name_owner_changed_with_args(&[(0, well_known), (2, unique_name.as_str())])
.await
.unwrap();
let name_acquired_stream = proxy
.receive_name_acquired_with_args(&[(0, well_known)])
.await
.unwrap();
let mut stream = owner_change_stream.zip(name_acquired_stream);
let well_known: WellKnownName<'static> = well_known.try_into().unwrap();
proxy
.request_name(
well_known.as_ref(),
fdo::RequestNameFlags::ReplaceExisting.into(),
)
.await
.unwrap();
let (name_owner_changed, name_acquired) = stream.next().await.unwrap();
assert_eq!(name_owner_changed.args().unwrap().name(), &well_known);
assert_eq!(
*name_owner_changed
.args()
.unwrap()
.new_owner()
.as_ref()
.unwrap(),
*unique_name
);
assert_eq!(name_acquired.args().unwrap().name(), &well_known);
let result = proxy.release_name(well_known.as_ref()).await.unwrap();
assert_eq!(result, fdo::ReleaseNameReply::Released);
let result = proxy.release_name(well_known).await.unwrap();
assert_eq!(result, fdo::ReleaseNameReply::NonExistent);
let _stream = proxy
.receive_features_changed()
.await
.filter_map(|changed| async move {
let v = changed.get().await.ok();
dbg!(v)
});
}
}