// 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