| // Copyright (c) 2009 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "brillo/glib/dbus.h" |
| |
| #include <dbus/dbus.h> |
| #include <dbus/dbus-glib-bindings.h> |
| #include <dbus/dbus-glib-lowlevel.h> |
| |
| #include <base/logging.h> |
| #include <base/strings/stringprintf.h> |
| |
| namespace brillo { |
| namespace dbus { |
| |
| bool CallPtrArray(const Proxy& proxy, |
| const char* method, |
| glib::ScopedPtrArray<const char*>* result) { |
| glib::ScopedError error; |
| |
| ::GType g_type_array = ::dbus_g_type_get_collection("GPtrArray", |
| DBUS_TYPE_G_OBJECT_PATH); |
| |
| |
| if (!::dbus_g_proxy_call(proxy.gproxy(), method, &Resetter(&error).lvalue(), |
| G_TYPE_INVALID, g_type_array, |
| &Resetter(result).lvalue(), G_TYPE_INVALID)) { |
| LOG(WARNING) << "CallPtrArray failed: " |
| << (error->message ? error->message : "Unknown Error."); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| BusConnection GetSystemBusConnection() { |
| glib::ScopedError error; |
| ::DBusGConnection* result = ::dbus_g_bus_get(DBUS_BUS_SYSTEM, |
| &Resetter(&error).lvalue()); |
| if (!result) { |
| LOG(ERROR) << "dbus_g_bus_get(DBUS_BUS_SYSTEM) failed: " |
| << ((error.get() && error->message) ? |
| error->message : "Unknown Error"); |
| return BusConnection(nullptr); |
| } |
| // Set to not exit when system bus is disconnected. |
| // This fixes the problem where when the dbus daemon is stopped, exit is |
| // called which kills Chrome. |
| ::dbus_connection_set_exit_on_disconnect( |
| ::dbus_g_connection_get_connection(result), FALSE); |
| return BusConnection(result); |
| } |
| |
| BusConnection GetPrivateBusConnection(const char* address) { |
| // Since dbus-glib does not have an API like dbus_g_connection_open_private(), |
| // we have to implement our own. |
| |
| // We have to call _dbus_g_value_types_init() to register standard marshalers |
| // just like as dbus_g_bus_get() and dbus_g_connection_open() do, but the |
| // function is not exported. So we call GetPrivateBusConnection() which calls |
| // dbus_g_bus_get() here instead. Note that if we don't call |
| // _dbus_g_value_types_init(), we might get "WARNING **: No demarshaller |
| // registered for type xxxxx" error and might not be able to handle incoming |
| // signals nor method calls. |
| { |
| BusConnection system_bus_connection = GetSystemBusConnection(); |
| if (!system_bus_connection.HasConnection()) { |
| return system_bus_connection; // returns NULL connection. |
| } |
| } |
| |
| ::DBusError error; |
| ::dbus_error_init(&error); |
| |
| ::DBusGConnection* result = nullptr; |
| ::DBusConnection* raw_connection |
| = ::dbus_connection_open_private(address, &error); |
| if (!raw_connection) { |
| LOG(WARNING) << "dbus_connection_open_private failed: " << address; |
| return BusConnection(nullptr); |
| } |
| |
| if (!::dbus_bus_register(raw_connection, &error)) { |
| LOG(ERROR) << "dbus_bus_register failed: " |
| << (error.message ? error.message : "Unknown Error."); |
| ::dbus_error_free(&error); |
| // TODO(yusukes): We don't call dbus_connection_close() nor g_object_unref() |
| // here for now since these calls might interfere with IBusBus connections |
| // in libcros and Chrome. See the comment in ~InputMethodStatusConnection() |
| // function in platform/cros/chromeos_input_method.cc for details. |
| return BusConnection(nullptr); |
| } |
| |
| ::dbus_connection_setup_with_g_main( |
| raw_connection, nullptr /* default context */); |
| |
| // A reference count of |raw_connection| is transferred to |result|. You don't |
| // have to (and should not) unref the |raw_connection|. |
| result = ::dbus_connection_get_g_connection(raw_connection); |
| CHECK(result); |
| |
| ::dbus_connection_set_exit_on_disconnect( |
| ::dbus_g_connection_get_connection(result), FALSE); |
| |
| return BusConnection(result); |
| } |
| |
| bool RetrieveProperties(const Proxy& proxy, |
| const char* interface, |
| glib::ScopedHashTable* result) { |
| glib::ScopedError error; |
| |
| if (!::dbus_g_proxy_call(proxy.gproxy(), "GetAll", &Resetter(&error).lvalue(), |
| G_TYPE_STRING, interface, G_TYPE_INVALID, |
| ::dbus_g_type_get_map("GHashTable", G_TYPE_STRING, |
| G_TYPE_VALUE), |
| &Resetter(result).lvalue(), G_TYPE_INVALID)) { |
| LOG(WARNING) << "RetrieveProperties failed: " |
| << (error->message ? error->message : "Unknown Error."); |
| return false; |
| } |
| return true; |
| } |
| |
| Proxy::Proxy() |
| : object_(nullptr) { |
| } |
| |
| // Set |connect_to_name_owner| true if you'd like to use |
| // dbus_g_proxy_new_for_name_owner() rather than dbus_g_proxy_new_for_name(). |
| Proxy::Proxy(const BusConnection& connection, |
| const char* name, |
| const char* path, |
| const char* interface, |
| bool connect_to_name_owner) |
| : object_(GetGProxy( |
| connection, name, path, interface, connect_to_name_owner)) { |
| } |
| |
| // Equivalent to Proxy(connection, name, path, interface, false). |
| Proxy::Proxy(const BusConnection& connection, |
| const char* name, |
| const char* path, |
| const char* interface) |
| : object_(GetGProxy(connection, name, path, interface, false)) { |
| } |
| |
| // Creates a peer proxy using dbus_g_proxy_new_for_peer. |
| Proxy::Proxy(const BusConnection& connection, |
| const char* path, |
| const char* interface) |
| : object_(GetGPeerProxy(connection, path, interface)) { |
| } |
| |
| Proxy::Proxy(const Proxy& x) |
| : object_(x.object_) { |
| if (object_) |
| ::g_object_ref(object_); |
| } |
| |
| Proxy::~Proxy() { |
| if (object_) |
| ::g_object_unref(object_); |
| } |
| |
| /* static */ |
| Proxy::value_type Proxy::GetGProxy(const BusConnection& connection, |
| const char* name, |
| const char* path, |
| const char* interface, |
| bool connect_to_name_owner) { |
| value_type result = nullptr; |
| if (connect_to_name_owner) { |
| glib::ScopedError error; |
| result = ::dbus_g_proxy_new_for_name_owner(connection.object_, |
| name, |
| path, |
| interface, |
| &Resetter(&error).lvalue()); |
| if (!result) { |
| DLOG(ERROR) << "Failed to construct proxy: " |
| << (error->message ? error->message : "Unknown Error") |
| << ": " << path; |
| } |
| } else { |
| result = ::dbus_g_proxy_new_for_name(connection.object_, |
| name, |
| path, |
| interface); |
| if (!result) { |
| LOG(ERROR) << "Failed to construct proxy: " << path; |
| } |
| } |
| return result; |
| } |
| |
| /* static */ |
| Proxy::value_type Proxy::GetGPeerProxy(const BusConnection& connection, |
| const char* path, |
| const char* interface) { |
| value_type result = ::dbus_g_proxy_new_for_peer(connection.object_, |
| path, |
| interface); |
| if (!result) |
| LOG(ERROR) << "Failed to construct peer proxy: " << path; |
| |
| return result; |
| } |
| |
| bool RegisterExclusiveService(const BusConnection& connection, |
| const char* interface_name, |
| const char* service_name, |
| const char* service_path, |
| GObject* object) { |
| CHECK(object); |
| CHECK(interface_name); |
| CHECK(service_name); |
| // Create a proxy to DBus itself so that we can request to become a |
| // service name owner and then register an object at the related service path. |
| Proxy proxy = brillo::dbus::Proxy(connection, |
| DBUS_SERVICE_DBUS, |
| DBUS_PATH_DBUS, |
| DBUS_INTERFACE_DBUS); |
| // Exclusivity is determined by replacing any existing |
| // service, not queuing, and ensuring we are the primary |
| // owner after the name is ours. |
| glib::ScopedError err; |
| guint result = 0; |
| // TODO(wad) determine if we are moving away from using generated functions |
| if (!org_freedesktop_DBus_request_name(proxy.gproxy(), |
| service_name, |
| 0, |
| &result, |
| &Resetter(&err).lvalue())) { |
| LOG(ERROR) << "Unable to request service name: " |
| << (err->message ? err->message : "Unknown Error."); |
| return false; |
| } |
| |
| // Handle the error codes, releasing the name if exclusivity conditions |
| // are not met. |
| bool needs_release = false; |
| if (result != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { |
| LOG(ERROR) << "Failed to become the primary owner. Releasing . . ."; |
| needs_release = true; |
| } |
| if (result == DBUS_REQUEST_NAME_REPLY_EXISTS) { |
| LOG(ERROR) << "Service name exists: " << service_name; |
| return false; |
| } else if (result == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { |
| LOG(ERROR) << "Service name request enqueued despite our flags. Releasing"; |
| needs_release = true; |
| } |
| LOG_IF(WARNING, result == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER) |
| << "Service name already owned by this process"; |
| if (needs_release) { |
| if (!org_freedesktop_DBus_release_name( |
| proxy.gproxy(), |
| service_name, |
| &result, |
| &Resetter(&err).lvalue())) { |
| LOG(ERROR) << "Unabled to release service name: " |
| << (err->message ? err->message : "Unknown Error."); |
| } |
| DLOG(INFO) << "ReleaseName returned code " << result; |
| return false; |
| } |
| |
| // Determine a path from the service name and register the object. |
| dbus_g_connection_register_g_object(connection.g_connection(), |
| service_path, |
| object); |
| return true; |
| } |
| |
| void CallMethodWithNoArguments(const char* service_name, |
| const char* path, |
| const char* interface_name, |
| const char* method_name) { |
| Proxy proxy(dbus::GetSystemBusConnection(), |
| service_name, |
| path, |
| interface_name); |
| ::dbus_g_proxy_call_no_reply(proxy.gproxy(), method_name, G_TYPE_INVALID); |
| } |
| |
| void SignalWatcher::StartMonitoring(const std::string& interface, |
| const std::string& signal) { |
| DCHECK(interface_.empty()) << "StartMonitoring() must be called only once"; |
| interface_ = interface; |
| signal_ = signal; |
| |
| // Snoop on D-Bus messages so we can get notified about signals. |
| DBusConnection* dbus_conn = dbus_g_connection_get_connection( |
| GetSystemBusConnection().g_connection()); |
| DCHECK(dbus_conn); |
| |
| DBusError error; |
| dbus_error_init(&error); |
| dbus_bus_add_match(dbus_conn, GetDBusMatchString().c_str(), &error); |
| if (dbus_error_is_set(&error)) { |
| LOG(DFATAL) << "Got error while adding D-Bus match rule: " << error.name |
| << " (" << error.message << ")"; |
| } |
| |
| if (!dbus_connection_add_filter(dbus_conn, |
| &SignalWatcher::FilterDBusMessage, |
| this, // user_data |
| nullptr)) { // free_data_function |
| LOG(DFATAL) << "Unable to add D-Bus filter"; |
| } |
| } |
| |
| SignalWatcher::~SignalWatcher() { |
| if (interface_.empty()) |
| return; |
| |
| DBusConnection* dbus_conn = dbus_g_connection_get_connection( |
| dbus::GetSystemBusConnection().g_connection()); |
| DCHECK(dbus_conn); |
| |
| dbus_connection_remove_filter(dbus_conn, |
| &SignalWatcher::FilterDBusMessage, |
| this); |
| |
| DBusError error; |
| dbus_error_init(&error); |
| dbus_bus_remove_match(dbus_conn, GetDBusMatchString().c_str(), &error); |
| if (dbus_error_is_set(&error)) { |
| LOG(DFATAL) << "Got error while removing D-Bus match rule: " << error.name |
| << " (" << error.message << ")"; |
| } |
| } |
| |
| std::string SignalWatcher::GetDBusMatchString() const { |
| return base::StringPrintf("type='signal', interface='%s', member='%s'", |
| interface_.c_str(), signal_.c_str()); |
| } |
| |
| /* static */ |
| DBusHandlerResult SignalWatcher::FilterDBusMessage(DBusConnection* dbus_conn, |
| DBusMessage* message, |
| void* data) { |
| SignalWatcher* self = static_cast<SignalWatcher*>(data); |
| if (dbus_message_is_signal( |
| message, self->interface_.c_str(), self->signal_.c_str())) { |
| self->OnSignal(message); |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } else { |
| return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
| } |
| } |
| |
| } // namespace dbus |
| } // namespace brillo |