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