1 /// Utility to watch the presence of a D-Bus services and interfaces.
2 use dbus::channel::MatchingReceiver;
3 use dbus::message::MatchRule;
4 use dbus::nonblock::stdintf::org_freedesktop_dbus::ObjectManager;
5 use dbus::nonblock::SyncConnection;
6 use std::sync::{Arc, Mutex};
7 use std::time::Duration;
8 
9 const DBUS_SERVICE: &str = "org.freedesktop.DBus";
10 const DBUS_INTERFACE: &str = "org.freedesktop.DBus";
11 const DBUS_OBJMGR_INTERFACE: &str = "org.freedesktop.DBus.ObjectManager";
12 const DBUS_GET_NAME_OWNER: &str = "GetNameOwner";
13 const DBUS_NAME_OWNER_CHANGED: &str = "NameOwnerChanged";
14 const DBUS_INTERFACES_ADDED: &str = "InterfacesAdded";
15 const DBUS_PATH: &str = "/org/freedesktop/DBus";
16 const DBUS_TIMEOUT: Duration = Duration::from_secs(2);
17 
18 struct ServiceWatcherContext {
19     // The service name to watch.
20     service_name: String,
21     // The owner D-Bus address if exists.
22     service_owner: Option<String>,
23 }
24 
25 impl ServiceWatcherContext {
new(service_name: String) -> Self26     fn new(service_name: String) -> Self {
27         Self { service_name, service_owner: None }
28     }
29 
set_owner(&mut self, owner: String)30     fn set_owner(&mut self, owner: String) {
31         log::debug!("setting owner of {} to {}", self.service_name, owner);
32         self.service_owner = Some(owner);
33     }
34 
unset_owner(&mut self)35     fn unset_owner(&mut self) {
36         log::debug!("unsetting owner of {}", self.service_name);
37         self.service_owner = None;
38     }
39 }
40 
41 pub struct ServiceWatcher {
42     conn: Arc<SyncConnection>,
43     service_name: String,
44     context: Arc<Mutex<ServiceWatcherContext>>,
45 }
46 
47 impl ServiceWatcher {
new(conn: Arc<SyncConnection>, service_name: String) -> Self48     pub fn new(conn: Arc<SyncConnection>, service_name: String) -> Self {
49         ServiceWatcher {
50             conn,
51             service_name: service_name.clone(),
52             context: Arc::new(Mutex::new(ServiceWatcherContext::new(service_name))),
53         }
54     }
55 
56     // Returns the owner D-Bus address of the named service.
get_dbus_owner_of_service(&self) -> Option<String>57     async fn get_dbus_owner_of_service(&self) -> Option<String> {
58         let dbus_proxy =
59             dbus::nonblock::Proxy::new(DBUS_SERVICE, DBUS_PATH, DBUS_TIMEOUT, self.conn.clone());
60 
61         let service_owner: Result<(String,), dbus::Error> = dbus_proxy
62             .method_call(DBUS_INTERFACE, DBUS_GET_NAME_OWNER, (self.service_name.clone(),))
63             .await;
64 
65         match service_owner {
66             Err(e) => {
67                 log::debug!("Getting service owner failed: {}", e);
68                 None
69             }
70             Ok((owner,)) => {
71                 log::debug!("Got service owner {} = {}", self.service_name, owner);
72                 Some(owner)
73             }
74         }
75     }
76 
77     // Returns the object path if the service exports an object having the specified interface.
get_path_of_interface(&self, interface: String) -> Option<dbus::Path<'static>>78     async fn get_path_of_interface(&self, interface: String) -> Option<dbus::Path<'static>> {
79         let service_proxy = dbus::nonblock::Proxy::new(
80             self.service_name.clone(),
81             "/",
82             DBUS_TIMEOUT,
83             self.conn.clone(),
84         );
85 
86         let objects = service_proxy.get_managed_objects().await;
87 
88         match objects {
89             Err(e) => {
90                 log::debug!("Failed getting managed objects: {}", e);
91                 None
92             }
93             Ok(objects) => objects
94                 .into_iter()
95                 .find(|(_key, value)| value.contains_key(&interface))
96                 .map(|(key, _value)| key),
97         }
98     }
99 
100     // Monitors the service appearance or disappearance and keeps the owner address updated.
monitor_name_owner_changed( &self, on_available: Box<dyn Fn() + Send>, on_unavailable: Box<dyn Fn() + Send>, )101     async fn monitor_name_owner_changed(
102         &self,
103         on_available: Box<dyn Fn() + Send>,
104         on_unavailable: Box<dyn Fn() + Send>,
105     ) {
106         let mr = MatchRule::new_signal(DBUS_INTERFACE, DBUS_NAME_OWNER_CHANGED);
107         self.conn.add_match_no_cb(&mr.match_str()).await.unwrap();
108         let service_name = self.service_name.clone();
109         let context = self.context.clone();
110         self.conn.start_receive(
111             mr,
112             Box::new(move |msg, _conn| {
113                 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with
114                 // `match_all` = true,  so we should always return true from this closure to
115                 // indicate that we have handled the message and not let other handlers handle this
116                 // signal.
117                 if let (Some(name), Some(old_owner), Some(new_owner)) =
118                     msg.get3::<String, String, String>()
119                 {
120                     // Appearance/disappearance of unrelated service, ignore since we are not
121                     // interested.
122                     if name != service_name {
123                         return true;
124                     }
125 
126                     if old_owner == "" && new_owner != "" {
127                         context.lock().unwrap().set_owner(new_owner.clone());
128                         on_available();
129                     } else if old_owner != "" && new_owner == "" {
130                         context.lock().unwrap().unset_owner();
131                         on_unavailable();
132                     } else {
133                         log::warn!(
134                             "Invalid NameOwnerChanged with old_owner = {} and new_owner = {}",
135                             old_owner,
136                             new_owner
137                         );
138                     }
139                 }
140                 true
141             }),
142         );
143     }
144 
145     /// Watches appearance and disappearance of a D-Bus service by the name.
start_watch( &self, on_available: Box<dyn Fn() + Send>, on_unavailable: Box<dyn Fn() + Send>, )146     pub async fn start_watch(
147         &self,
148         on_available: Box<dyn Fn() + Send>,
149         on_unavailable: Box<dyn Fn() + Send>,
150     ) {
151         if let Some(owner) = self.get_dbus_owner_of_service().await {
152             self.context.lock().unwrap().set_owner(owner.clone());
153             on_available();
154         }
155 
156         // Monitor service appearing and disappearing.
157         self.monitor_name_owner_changed(
158             Box::new(move || {
159                 on_available();
160             }),
161             Box::new(move || {
162                 on_unavailable();
163             }),
164         )
165         .await;
166     }
167 
168     /// Watches the appearance of an interface of a service, and the disappearance of the service.
169     ///
170     /// Doesn't take into account the disappearance of the interface itself. At the moment assuming
171     /// interfaces do not disappear as long as the service is alive.
start_watch_interface( &mut self, interface: String, on_available: Box<dyn Fn(dbus::Path<'static>) + Send>, on_unavailable: Box<dyn Fn() + Send>, )172     pub async fn start_watch_interface(
173         &mut self,
174         interface: String,
175         on_available: Box<dyn Fn(dbus::Path<'static>) + Send>,
176         on_unavailable: Box<dyn Fn() + Send>,
177     ) {
178         if let Some(owner) = self.get_dbus_owner_of_service().await {
179             self.context.lock().unwrap().set_owner(owner.clone());
180             if let Some(path) = self.get_path_of_interface(interface.clone()).await {
181                 on_available(path);
182             }
183         }
184 
185         // Monitor service disappearing.
186         self.monitor_name_owner_changed(
187             Box::new(move || {
188                 // Don't trigger on_available() yet because we rely on interface added.
189             }),
190             Box::new(move || {
191                 on_unavailable();
192             }),
193         )
194         .await;
195 
196         // Monitor interface appearing.
197         let mr = MatchRule::new_signal(DBUS_OBJMGR_INTERFACE, DBUS_INTERFACES_ADDED);
198         self.conn.add_match_no_cb(&mr.match_str()).await.unwrap();
199         let context = self.context.clone();
200         self.conn.start_receive(
201             mr,
202             Box::new(move |msg, _conn| {
203                 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with
204                 // `match_all` = true,  so we should always return true from this closure to
205                 // indicate that we have handled the message and not let other handlers handle this
206                 // signal.
207                 let (object_path, interfaces) =
208                     msg.get2::<dbus::Path, dbus::arg::Dict<String, dbus::arg::PropMap, _>>();
209                 let interfaces: Vec<String> = interfaces.unwrap().map(|e| e.0).collect();
210                 if interfaces.contains(&interface) {
211                     if let (Some(sender), Some(owner)) =
212                         (msg.sender(), context.lock().unwrap().service_owner.as_ref())
213                     {
214                         // The signal does not come from the service we are watching, ignore.
215                         if &sender.to_string() != owner {
216                             log::debug!(
217                                 "Detected interface {} added but sender is {}, expected {}.",
218                                 interface,
219                                 sender,
220                                 owner
221                             );
222                             return true;
223                         }
224                         on_available(object_path.unwrap().into_static());
225                     }
226                 }
227 
228                 true
229             }),
230         );
231     }
232 }
233