use enumflags2::BitFlags;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::collections::HashMap;
use zvariant::{derive::Type, ObjectPath, OwnedObjectPath, OwnedValue, Value};
use crate::{dbus_interface, dbus_proxy, object_server::LOCAL_NODE, DBusError};
#[dbus_proxy(interface = "org.freedesktop.DBus.Introspectable", default_path = "/")]
trait Introspectable {
fn introspect(&self) -> Result<String>;
}
pub(crate) struct Introspectable;
#[dbus_interface(name = "org.freedesktop.DBus.Introspectable")]
impl Introspectable {
fn introspect(&self) -> String {
LOCAL_NODE.with(|node| node.introspect())
}
}
#[dbus_proxy(interface = "org.freedesktop.DBus.Properties")]
trait Properties {
fn get(&self, interface_name: &str, property_name: &str) -> Result<OwnedValue>;
fn set(&self, interface_name: &str, property_name: &str, value: &Value<'_>) -> Result<()>;
fn get_all(&self, interface_name: &str) -> Result<HashMap<String, OwnedValue>>;
#[dbus_proxy(signal)]
fn properties_changed(
&self,
interface_name: &str,
changed_properties: HashMap<&str, Value<'_>>,
invalidated_properties: Vec<&str>,
) -> Result<()>;
}
pub struct Properties;
#[dbus_interface(name = "org.freedesktop.DBus.Properties")]
impl Properties {
fn get(&self, interface_name: &str, property_name: &str) -> Result<OwnedValue> {
LOCAL_NODE.with(|node| {
let iface = node.get_interface(interface_name).ok_or_else(|| {
Error::UnknownInterface(format!("Unknown interface '{}'", interface_name))
})?;
let res = iface.borrow().get(property_name);
res.ok_or_else(|| {
Error::UnknownProperty(format!("Unknown property '{}'", property_name))
})?
})
}
fn set(&mut self, interface_name: &str, property_name: &str, value: OwnedValue) -> Result<()> {
LOCAL_NODE.with(|node| {
let iface = node.get_interface(interface_name).ok_or_else(|| {
Error::UnknownInterface(format!("Unknown interface '{}'", interface_name))
})?;
let res = iface.borrow_mut().set(property_name, &value);
res.ok_or_else(|| {
Error::UnknownProperty(format!("Unknown property '{}'", property_name))
})?
})
}
fn get_all(&self, interface_name: &str) -> Result<HashMap<String, OwnedValue>> {
LOCAL_NODE.with(|node| {
let iface = node.get_interface(interface_name).ok_or_else(|| {
Error::UnknownInterface(format!("Unknown interface '{}'", interface_name))
})?;
let res = iface.borrow().get_all();
Ok(res)
})
}
#[dbus_interface(signal)]
#[rustfmt::skip]
pub fn properties_changed(
&self,
interface_name: &str,
changed_properties: &HashMap<&str, &Value<'_>>,
invalidated_properties: &[&str],
) -> zbus::Result<()>;
}
type ManagedObjects = HashMap<OwnedObjectPath, HashMap<String, HashMap<String, OwnedValue>>>;
#[dbus_proxy(interface = "org.freedesktop.DBus.ObjectManager")]
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<()>;
}
#[dbus_proxy(interface = "org.freedesktop.DBus.Peer")]
trait Peer {
fn ping(&self) -> Result<()>;
fn get_machine_id(&self) -> Result<String>;
}
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)
}
}
#[dbus_proxy(interface = "org.freedesktop.DBus.Monitoring")]
trait Monitoring {
fn become_monitor(&self, n1: &[&str], n2: u32) -> Result<()>;
}
#[dbus_proxy(interface = "org.freedesktop.DBus.Debug.Stats")]
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>>>>;
}
#[repr(u32)]
#[derive(Type, BitFlags, Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum RequestNameFlags {
AllowReplacement = 0x01,
ReplaceExisting = 0x02,
DoNotQueue = 0x04,
}
#[repr(u32)]
#[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq)]
pub enum RequestNameReply {
PrimaryOwner = 0x01,
InQueue = 0x02,
Exists = 0x03,
AlreadyOwner = 0x04,
}
#[repr(u32)]
#[derive(Deserialize_repr, Serialize_repr, Type, Debug, PartialEq)]
pub enum ReleaseNameReply {
Released = 0x01,
NonExistent = 0x02,
NotOwner = 0x03,
}
#[dbus_proxy(interface = "org.freedesktop.DBus")]
trait DBus {
fn add_match(&self, rule: &str) -> Result<()>;
fn get_adt_audit_session_data(&self, bus_name: &str) -> Result<Vec<u8>>;
fn get_connection_credentials(&self, bus_name: &str) -> Result<HashMap<String, OwnedValue>>;
#[dbus_proxy(name = "GetConnectionSELinuxSecurityContext")]
fn get_connection_selinux_security_context(&self, bus_name: &str) -> Result<Vec<u8>>;
#[dbus_proxy(name = "GetConnectionUnixProcessID")]
fn get_connection_unix_process_id(&self, bus_name: &str) -> Result<u32>;
fn get_connection_unix_user(&self, bus_name: &str) -> Result<u32>;
fn get_id(&self) -> Result<String>;
fn get_name_owner(&self, name: &str) -> Result<String>;
fn hello(&self) -> Result<String>;
fn list_activatable_names(&self) -> Result<Vec<String>>;
fn list_names(&self) -> Result<Vec<String>>;
fn list_queued_owners(&self, name: &str) -> Result<Vec<String>>;
fn name_has_owner(&self, name: &str) -> Result<bool>;
fn release_name(&self, name: &str) -> Result<ReleaseNameReply>;
fn reload_config(&self) -> Result<()>;
fn remove_match(&self, rule: &str) -> Result<()>;
fn request_name(
&self,
name: &str,
flags: BitFlags<RequestNameFlags>,
) -> Result<RequestNameReply>;
fn start_service_by_name(&self, name: &str, flags: u32) -> Result<u32>;
fn update_activation_environment(&self, environment: HashMap<&str, &str>) -> Result<()>;
#[dbus_proxy(signal)]
fn name_owner_changed(&self, name: &str, old_owner: &str, new_owner: &str);
#[dbus_proxy(signal)]
fn name_lost(&self, name: &str);
#[dbus_proxy(signal)]
fn name_acquired(&self, name: &str);
#[dbus_proxy(property)]
fn features(&self) -> Result<Vec<String>>;
#[dbus_proxy(property)]
fn interfaces(&self) -> Result<Vec<String>>;
}
#[derive(Debug, DBusError, PartialEq)]
#[dbus_error(prefix = "org.freedesktop.DBus.Error")]
pub enum 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),
}
pub type Result<T> = std::result::Result<T, Error>;
impl From<zbus::MessageError> for Error {
fn from(val: zbus::MessageError) -> Self {
match val {
zbus::MessageError::InsufficientData => {
Self::InconsistentMessage("insufficient data".to_string())
}
zbus::MessageError::ExcessData => Self::InconsistentMessage("excess data".to_string()),
zbus::MessageError::IncorrectEndian => {
Self::InconsistentMessage("incorrect endian".to_string())
}
zbus::MessageError::Io(e) => Self::IOError(e.to_string()),
zbus::MessageError::UnmatchedBodySignature => {
Self::InvalidArgs("incorrect body signature".to_string())
}
zbus::MessageError::NoBodySignature => {
Self::InvalidSignature("missing body signature".to_string())
}
zbus::MessageError::InvalidField => {
Self::InconsistentMessage("invalid message field".to_string())
}
zbus::MessageError::Variant(e) => Self::InconsistentMessage(e.to_string()),
zbus::MessageError::MissingField => {
Self::InconsistentMessage("Required message field missing".to_string())
}
zbus::MessageError::Infallible => Self::ZBus(zbus::Error::Infallible),
}
}
}
#[cfg(test)]
mod tests {
use crate::{fdo, Error, Message};
use std::{
convert::TryInto,
sync::{Arc, Mutex},
};
#[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.try_into().unwrap();
assert_eq!(e, fdo::Error::TimedOut("so long".to_string()));
}
#[test]
fn signal() {
let conn = crate::Connection::new_session().unwrap();
let owner_change_signaled = Arc::new(Mutex::new(false));
let name_acquired_signaled = Arc::new(Mutex::new(false));
let proxy = fdo::DBusProxy::new(&conn).unwrap();
let well_known = "org.freedesktop.zbus.FdoSignalTest";
let unique_name = conn.unique_name().unwrap().to_string();
{
let well_known = well_known.clone();
let signaled = owner_change_signaled.clone();
proxy
.connect_name_owner_changed(move |name, _, new_owner| {
if name != well_known {
return Ok(());
}
assert_eq!(new_owner, unique_name);
*signaled.lock().unwrap() = true;
Ok(())
})
.unwrap();
}
{
let signaled = name_acquired_signaled.clone();
proxy
.connect_name_acquired(move |name| {
if name == well_known {
*signaled.lock().unwrap() = true;
}
Ok(())
})
.unwrap();
}
proxy
.request_name(&well_known, fdo::RequestNameFlags::ReplaceExisting.into())
.unwrap();
loop {
proxy.next_signal().unwrap();
if *owner_change_signaled.lock().unwrap() && *name_acquired_signaled.lock().unwrap() {
break;
}
}
let result = proxy.release_name(&well_known).unwrap();
assert_eq!(result, fdo::ReleaseNameReply::Released);
let result = proxy.release_name(&well_known).unwrap();
assert_eq!(result, fdo::ReleaseNameReply::NonExistent);
}
}