• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
108             .add_match_no_cb(&mr.match_str())
109             .await
110             .expect("Unable to add match to D-Bus for monitoring service owner change");
111         let service_name = self.service_name.clone();
112         let context = self.context.clone();
113         self.conn.start_receive(
114             mr,
115             Box::new(move |msg, _conn| {
116                 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with
117                 // `match_all` = true,  so we should always return true from this closure to
118                 // indicate that we have handled the message and not let other handlers handle this
119                 // signal.
120                 if let (Some(name), Some(old_owner), Some(new_owner)) =
121                     msg.get3::<String, String, String>()
122                 {
123                     // Appearance/disappearance of unrelated service, ignore since we are not
124                     // interested.
125                     if name != service_name {
126                         return true;
127                     }
128 
129                     if old_owner.is_empty() && !new_owner.is_empty() {
130                         context.lock().unwrap().set_owner(new_owner.clone());
131                         on_available();
132                     } else if !old_owner.is_empty() && new_owner.is_empty() {
133                         context.lock().unwrap().unset_owner();
134                         on_unavailable();
135                     } else {
136                         log::warn!(
137                             "Invalid NameOwnerChanged with old_owner = {} and new_owner = {}",
138                             old_owner,
139                             new_owner
140                         );
141                     }
142                 }
143                 true
144             }),
145         );
146     }
147 
148     /// 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>, )149     pub async fn start_watch(
150         &self,
151         on_available: Box<dyn Fn() + Send>,
152         on_unavailable: Box<dyn Fn() + Send>,
153     ) {
154         if let Some(owner) = self.get_dbus_owner_of_service().await {
155             self.context.lock().unwrap().set_owner(owner.clone());
156             on_available();
157         }
158 
159         // Monitor service appearing and disappearing.
160         self.monitor_name_owner_changed(
161             Box::new(move || {
162                 on_available();
163             }),
164             Box::new(move || {
165                 on_unavailable();
166             }),
167         )
168         .await;
169     }
170 
171     /// Watches the appearance of an interface of a service, and the disappearance of the service.
172     ///
173     /// Doesn't take into account the disappearance of the interface itself. At the moment assuming
174     /// 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>, )175     pub async fn start_watch_interface(
176         &mut self,
177         interface: String,
178         on_available: Box<dyn Fn(dbus::Path<'static>) + Send>,
179         on_unavailable: Box<dyn Fn() + Send>,
180     ) {
181         if let Some(owner) = self.get_dbus_owner_of_service().await {
182             self.context.lock().unwrap().set_owner(owner.clone());
183             if let Some(path) = self.get_path_of_interface(interface.clone()).await {
184                 on_available(path);
185             }
186         }
187 
188         // Monitor service disappearing.
189         self.monitor_name_owner_changed(
190             Box::new(move || {
191                 // Don't trigger on_available() yet because we rely on interface added.
192             }),
193             Box::new(move || {
194                 on_unavailable();
195             }),
196         )
197         .await;
198 
199         // Monitor interface appearing.
200         let mr = MatchRule::new_signal(DBUS_OBJMGR_INTERFACE, DBUS_INTERFACES_ADDED);
201         self.conn
202             .add_match_no_cb(&mr.match_str())
203             .await
204             .expect("Unable to add match to D-Bus for monitoring interface changes");
205         let context = self.context.clone();
206         self.conn.start_receive(
207             mr,
208             Box::new(move |msg, _conn| {
209                 // We use [`dbus::nonblock::SyncConnection::set_signal_match_mode`] with
210                 // `match_all` = true,  so we should always return true from this closure to
211                 // indicate that we have handled the message and not let other handlers handle this
212                 // signal.
213                 let (object_path, interfaces) =
214                     msg.get2::<dbus::Path, dbus::arg::Dict<String, dbus::arg::PropMap, _>>();
215                 let interfaces: Vec<String> = interfaces.unwrap().map(|e| e.0).collect();
216                 if interfaces.contains(&interface) {
217                     if let (Some(sender), Some(owner)) =
218                         (msg.sender(), context.lock().unwrap().service_owner.as_ref())
219                     {
220                         // The signal does not come from the service we are watching, ignore.
221                         if &sender.to_string() != owner {
222                             log::debug!(
223                                 "Detected interface {} added but sender is {}, expected {}.",
224                                 interface,
225                                 sender,
226                                 owner
227                             );
228                             return true;
229                         }
230                         on_available(object_path.unwrap().into_static());
231                     }
232                 }
233 
234                 true
235             }),
236         );
237     }
238 }
239