1// Copyright (c) 2009 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "brillo/glib/dbus.h"
6
7#include <dbus/dbus.h>
8#include <dbus/dbus-glib-bindings.h>
9#include <dbus/dbus-glib-lowlevel.h>
10
11#include <base/logging.h>
12#include <base/strings/stringprintf.h>
13
14namespace brillo {
15namespace dbus {
16
17bool CallPtrArray(const Proxy& proxy,
18                  const char* method,
19                  glib::ScopedPtrArray<const char*>* result) {
20  glib::ScopedError error;
21
22  ::GType g_type_array = ::dbus_g_type_get_collection("GPtrArray",
23                                                       DBUS_TYPE_G_OBJECT_PATH);
24
25
26  if (!::dbus_g_proxy_call(proxy.gproxy(), method, &Resetter(&error).lvalue(),
27                           G_TYPE_INVALID, g_type_array,
28                           &Resetter(result).lvalue(), G_TYPE_INVALID)) {
29    LOG(WARNING) << "CallPtrArray failed: "
30        << (error->message ? error->message : "Unknown Error.");
31    return false;
32  }
33
34  return true;
35}
36
37BusConnection GetSystemBusConnection() {
38  glib::ScopedError error;
39  ::DBusGConnection* result = ::dbus_g_bus_get(DBUS_BUS_SYSTEM,
40                                               &Resetter(&error).lvalue());
41  if (!result) {
42    LOG(ERROR) << "dbus_g_bus_get(DBUS_BUS_SYSTEM) failed: "
43               << ((error.get() && error->message) ?
44                   error->message : "Unknown Error");
45    return BusConnection(nullptr);
46  }
47  // Set to not exit when system bus is disconnected.
48  // This fixes the problem where when the dbus daemon is stopped, exit is
49  // called which kills Chrome.
50  ::dbus_connection_set_exit_on_disconnect(
51      ::dbus_g_connection_get_connection(result), FALSE);
52  return BusConnection(result);
53}
54
55BusConnection GetPrivateBusConnection(const char* address) {
56  // Since dbus-glib does not have an API like dbus_g_connection_open_private(),
57  // we have to implement our own.
58
59  // We have to call _dbus_g_value_types_init() to register standard marshalers
60  // just like as dbus_g_bus_get() and dbus_g_connection_open() do, but the
61  // function is not exported. So we call GetPrivateBusConnection() which calls
62  // dbus_g_bus_get() here instead. Note that if we don't call
63  // _dbus_g_value_types_init(), we might get "WARNING **: No demarshaller
64  // registered for type xxxxx" error and might not be able to handle incoming
65  // signals nor method calls.
66  {
67    BusConnection system_bus_connection = GetSystemBusConnection();
68    if (!system_bus_connection.HasConnection()) {
69      return system_bus_connection;  // returns NULL connection.
70    }
71  }
72
73  ::DBusError error;
74  ::dbus_error_init(&error);
75
76  ::DBusGConnection* result = nullptr;
77  ::DBusConnection* raw_connection
78        = ::dbus_connection_open_private(address, &error);
79  if (!raw_connection) {
80    LOG(WARNING) << "dbus_connection_open_private failed: " << address;
81    return BusConnection(nullptr);
82  }
83
84  if (!::dbus_bus_register(raw_connection, &error)) {
85    LOG(ERROR) << "dbus_bus_register failed: "
86               << (error.message ? error.message : "Unknown Error.");
87    ::dbus_error_free(&error);
88    // TODO(yusukes): We don't call dbus_connection_close() nor g_object_unref()
89    // here for now since these calls might interfere with IBusBus connections
90    // in libcros and Chrome. See the comment in ~InputMethodStatusConnection()
91    // function in platform/cros/chromeos_input_method.cc for details.
92    return BusConnection(nullptr);
93  }
94
95  ::dbus_connection_setup_with_g_main(
96      raw_connection, nullptr /* default context */);
97
98  // A reference count of |raw_connection| is transferred to |result|. You don't
99  // have to (and should not) unref the |raw_connection|.
100  result = ::dbus_connection_get_g_connection(raw_connection);
101  CHECK(result);
102
103  ::dbus_connection_set_exit_on_disconnect(
104      ::dbus_g_connection_get_connection(result), FALSE);
105
106  return BusConnection(result);
107}
108
109bool RetrieveProperties(const Proxy& proxy,
110                        const char* interface,
111                        glib::ScopedHashTable* result) {
112  glib::ScopedError error;
113
114  if (!::dbus_g_proxy_call(proxy.gproxy(), "GetAll", &Resetter(&error).lvalue(),
115                           G_TYPE_STRING, interface, G_TYPE_INVALID,
116                           ::dbus_g_type_get_map("GHashTable", G_TYPE_STRING,
117                                                 G_TYPE_VALUE),
118                           &Resetter(result).lvalue(), G_TYPE_INVALID)) {
119    LOG(WARNING) << "RetrieveProperties failed: "
120        << (error->message ? error->message : "Unknown Error.");
121    return false;
122  }
123  return true;
124}
125
126Proxy::Proxy()
127    : object_(nullptr) {
128}
129
130// Set |connect_to_name_owner| true if you'd like to use
131// dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name().
132Proxy::Proxy(const BusConnection& connection,
133             const char* name,
134             const char* path,
135             const char* interface,
136             bool connect_to_name_owner)
137    : object_(GetGProxy(
138        connection, name, path, interface, connect_to_name_owner)) {
139}
140
141// Equivalent to Proxy(connection, name, path, interface, false).
142Proxy::Proxy(const BusConnection& connection,
143             const char* name,
144             const char* path,
145             const char* interface)
146    : object_(GetGProxy(connection, name, path, interface, false)) {
147}
148
149// Creates a peer proxy using dbus_g_proxy_new_for_peer.
150Proxy::Proxy(const BusConnection& connection,
151             const char* path,
152             const char* interface)
153    : object_(GetGPeerProxy(connection, path, interface)) {
154}
155
156Proxy::Proxy(const Proxy& x)
157    : object_(x.object_) {
158  if (object_)
159    ::g_object_ref(object_);
160}
161
162Proxy::~Proxy() {
163  if (object_)
164    ::g_object_unref(object_);
165}
166
167/* static */
168Proxy::value_type Proxy::GetGProxy(const BusConnection& connection,
169                                   const char* name,
170                                   const char* path,
171                                   const char* interface,
172                                   bool connect_to_name_owner) {
173  value_type result = nullptr;
174  if (connect_to_name_owner) {
175    glib::ScopedError error;
176    result = ::dbus_g_proxy_new_for_name_owner(connection.object_,
177                                               name,
178                                               path,
179                                               interface,
180                                               &Resetter(&error).lvalue());
181    if (!result) {
182      DLOG(ERROR) << "Failed to construct proxy: "
183                  << (error->message ? error->message : "Unknown Error")
184                  << ": " << path;
185    }
186  } else {
187    result = ::dbus_g_proxy_new_for_name(connection.object_,
188                                         name,
189                                         path,
190                                         interface);
191    if (!result) {
192      LOG(ERROR) << "Failed to construct proxy: " << path;
193    }
194  }
195  return result;
196}
197
198/* static */
199Proxy::value_type Proxy::GetGPeerProxy(const BusConnection& connection,
200                                       const char* path,
201                                       const char* interface) {
202  value_type result = ::dbus_g_proxy_new_for_peer(connection.object_,
203                                                  path,
204                                                  interface);
205  if (!result)
206    LOG(ERROR) << "Failed to construct peer proxy: " << path;
207
208  return result;
209}
210
211bool RegisterExclusiveService(const BusConnection& connection,
212                              const char* interface_name,
213                              const char* service_name,
214                              const char* service_path,
215                              GObject* object) {
216  CHECK(object);
217  CHECK(interface_name);
218  CHECK(service_name);
219  // Create a proxy to DBus itself so that we can request to become a
220  // service name owner and then register an object at the related service path.
221  Proxy proxy = brillo::dbus::Proxy(connection,
222                                      DBUS_SERVICE_DBUS,
223                                      DBUS_PATH_DBUS,
224                                      DBUS_INTERFACE_DBUS);
225  // Exclusivity is determined by replacing any existing
226  // service, not queuing, and ensuring we are the primary
227  // owner after the name is ours.
228  glib::ScopedError err;
229  guint result = 0;
230  // TODO(wad) determine if we are moving away from using generated functions
231  if (!org_freedesktop_DBus_request_name(proxy.gproxy(),
232                                         service_name,
233                                         0,
234                                         &result,
235                                         &Resetter(&err).lvalue())) {
236    LOG(ERROR) << "Unable to request service name: "
237               << (err->message ? err->message : "Unknown Error.");
238    return false;
239  }
240
241  // Handle the error codes, releasing the name if exclusivity conditions
242  // are not met.
243  bool needs_release = false;
244  if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
245    LOG(ERROR) << "Failed to become the primary owner. Releasing . . .";
246    needs_release = true;
247  }
248  if (result == DBUS_REQUEST_NAME_REPLY_EXISTS) {
249    LOG(ERROR) << "Service name exists: " << service_name;
250    return false;
251  } else if (result == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) {
252    LOG(ERROR) << "Service name request enqueued despite our flags. Releasing";
253    needs_release = true;
254  }
255  LOG_IF(WARNING, result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
256    << "Service name already owned by this process";
257  if (needs_release) {
258    if (!org_freedesktop_DBus_release_name(
259           proxy.gproxy(),
260           service_name,
261           &result,
262           &Resetter(&err).lvalue())) {
263      LOG(ERROR) << "Unabled to release service name: "
264                 << (err->message ? err->message : "Unknown Error.");
265    }
266    DLOG(INFO) << "ReleaseName returned code " << result;
267    return false;
268  }
269
270  // Determine a path from the service name and register the object.
271  dbus_g_connection_register_g_object(connection.g_connection(),
272                                      service_path,
273                                      object);
274  return true;
275}
276
277void CallMethodWithNoArguments(const char* service_name,
278                               const char* path,
279                               const char* interface_name,
280                               const char* method_name) {
281  Proxy proxy(dbus::GetSystemBusConnection(),
282              service_name,
283              path,
284              interface_name);
285  ::dbus_g_proxy_call_no_reply(proxy.gproxy(), method_name, G_TYPE_INVALID);
286}
287
288void SignalWatcher::StartMonitoring(const std::string& interface,
289                                    const std::string& signal) {
290  DCHECK(interface_.empty()) << "StartMonitoring() must be called only once";
291  interface_ = interface;
292  signal_ = signal;
293
294  // Snoop on D-Bus messages so we can get notified about signals.
295  DBusConnection* dbus_conn = dbus_g_connection_get_connection(
296      GetSystemBusConnection().g_connection());
297  DCHECK(dbus_conn);
298
299  DBusError error;
300  dbus_error_init(&error);
301  dbus_bus_add_match(dbus_conn, GetDBusMatchString().c_str(), &error);
302  if (dbus_error_is_set(&error)) {
303    LOG(DFATAL) << "Got error while adding D-Bus match rule: " << error.name
304                << " (" << error.message << ")";
305  }
306
307  if (!dbus_connection_add_filter(dbus_conn,
308                                  &SignalWatcher::FilterDBusMessage,
309                                  this,        // user_data
310                                  nullptr)) {  // free_data_function
311    LOG(DFATAL) << "Unable to add D-Bus filter";
312  }
313}
314
315SignalWatcher::~SignalWatcher() {
316  if (interface_.empty())
317    return;
318
319  DBusConnection* dbus_conn = dbus_g_connection_get_connection(
320      dbus::GetSystemBusConnection().g_connection());
321  DCHECK(dbus_conn);
322
323  dbus_connection_remove_filter(dbus_conn,
324                                &SignalWatcher::FilterDBusMessage,
325                                this);
326
327  DBusError error;
328  dbus_error_init(&error);
329  dbus_bus_remove_match(dbus_conn, GetDBusMatchString().c_str(), &error);
330  if (dbus_error_is_set(&error)) {
331    LOG(DFATAL) << "Got error while removing D-Bus match rule: " << error.name
332                << " (" << error.message << ")";
333  }
334}
335
336std::string SignalWatcher::GetDBusMatchString() const {
337  return base::StringPrintf("type='signal', interface='%s', member='%s'",
338                            interface_.c_str(), signal_.c_str());
339}
340
341/* static */
342DBusHandlerResult SignalWatcher::FilterDBusMessage(DBusConnection* dbus_conn,
343                                                   DBusMessage* message,
344                                                   void* data) {
345  SignalWatcher* self = static_cast<SignalWatcher*>(data);
346  if (dbus_message_is_signal(
347          message, self->interface_.c_str(), self->signal_.c_str())) {
348    self->OnSignal(message);
349    return DBUS_HANDLER_RESULT_HANDLED;
350  } else {
351    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
352  }
353}
354
355}  // namespace dbus
356}  // namespace brillo
357