// Copyright (c) 2012 The Chromium 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 "dbus/exported_object.h"
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/metrics/histogram.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
#include "dbus/scoped_dbus_error.h"
#include "dbus/util.h"
namespace dbus {
namespace {
// Used for success ratio histograms. 1 for success, 0 for failure.
const int kSuccessRatioHistogramMaxValue = 2;
} // namespace
ExportedObject::ExportedObject(Bus* bus,
const ObjectPath& object_path)
: bus_(bus),
object_path_(object_path),
object_is_registered_(false) {
}
ExportedObject::~ExportedObject() {
DCHECK(!object_is_registered_);
}
bool ExportedObject::ExportMethodAndBlock(
const std::string& interface_name,
const std::string& method_name,
MethodCallCallback method_call_callback) {
bus_->AssertOnDBusThread();
// Check if the method is already exported.
const std::string absolute_method_name =
GetAbsoluteMemberName(interface_name, method_name);
if (method_table_.find(absolute_method_name) != method_table_.end()) {
LOG(ERROR) << absolute_method_name << " is already exported";
return false;
}
if (!bus_->Connect())
return false;
if (!bus_->SetUpAsyncOperations())
return false;
if (!Register())
return false;
// Add the method callback to the method table.
method_table_[absolute_method_name] = method_call_callback;
return true;
}
void ExportedObject::ExportMethod(const std::string& interface_name,
const std::string& method_name,
MethodCallCallback method_call_callback,
OnExportedCallback on_exported_calback) {
bus_->AssertOnOriginThread();
base::Closure task = base::Bind(&ExportedObject::ExportMethodInternal,
this,
interface_name,
method_name,
method_call_callback,
on_exported_calback);
bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
}
void ExportedObject::SendSignal(Signal* signal) {
// For signals, the object path should be set to the path to the sender
// object, which is this exported object here.
CHECK(signal->SetPath(object_path_));
// Increment the reference count so we can safely reference the
// underlying signal message until the signal sending is complete. This
// will be unref'ed in SendSignalInternal().
DBusMessage* signal_message = signal->raw_message();
dbus_message_ref(signal_message);
const base::TimeTicks start_time = base::TimeTicks::Now();
if (bus_->GetDBusTaskRunner()->RunsTasksOnCurrentThread()) {
// The Chrome OS power manager doesn't use a dedicated TaskRunner for
// sending DBus messages. Sending signals asynchronously can cause an
// inversion in the message order if the power manager calls
// ObjectProxy::CallMethodAndBlock() before going back to the top level of
// the MessageLoop: crbug.com/472361.
SendSignalInternal(start_time, signal_message);
} else {
bus_->GetDBusTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ExportedObject::SendSignalInternal,
this,
start_time,
signal_message));
}
}
void ExportedObject::Unregister() {
bus_->AssertOnDBusThread();
if (!object_is_registered_)
return;
bus_->UnregisterObjectPath(object_path_);
object_is_registered_ = false;
}
void ExportedObject::ExportMethodInternal(
const std::string& interface_name,
const std::string& method_name,
MethodCallCallback method_call_callback,
OnExportedCallback on_exported_calback) {
bus_->AssertOnDBusThread();
const bool success = ExportMethodAndBlock(interface_name,
method_name,
method_call_callback);
bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
base::Bind(&ExportedObject::OnExported,
this,
on_exported_calback,
interface_name,
method_name,
success));
}
void ExportedObject::OnExported(OnExportedCallback on_exported_callback,
const std::string& interface_name,
const std::string& method_name,
bool success) {
bus_->AssertOnOriginThread();
on_exported_callback.Run(interface_name, method_name, success);
}
void ExportedObject::SendSignalInternal(base::TimeTicks start_time,
DBusMessage* signal_message) {
uint32_t serial = 0;
bus_->Send(signal_message, &serial);
dbus_message_unref(signal_message);
// Record time spent to send the the signal. This is not accurate as the
// signal will actually be sent from the next run of the message loop,
// but we can at least tell the number of signals sent.
UMA_HISTOGRAM_TIMES("DBus.SignalSendTime",
base::TimeTicks::Now() - start_time);
}
bool ExportedObject::Register() {
bus_->AssertOnDBusThread();
if (object_is_registered_)
return true;
ScopedDBusError error;
DBusObjectPathVTable vtable;
memset(&vtable, 0, sizeof(vtable));
vtable.message_function = &ExportedObject::HandleMessageThunk;
vtable.unregister_function = &ExportedObject::OnUnregisteredThunk;
const bool success = bus_->TryRegisterObjectPath(object_path_,
&vtable,
this,
error.get());
if (!success) {
LOG(ERROR) << "Failed to register the object: " << object_path_.value()
<< ": " << (error.is_set() ? error.message() : "");
return false;
}
object_is_registered_ = true;
return true;
}
DBusHandlerResult ExportedObject::HandleMessage(
DBusConnection* /* connection */,
DBusMessage* raw_message) {
bus_->AssertOnDBusThread();
DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message));
// raw_message will be unrefed on exit of the function. Increment the
// reference so we can use it in MethodCall.
dbus_message_ref(raw_message);
scoped_ptr<MethodCall> method_call(
MethodCall::FromRawMessage(raw_message));
const std::string interface = method_call->GetInterface();
const std::string member = method_call->GetMember();
if (interface.empty()) {
// We don't support method calls without interface.
LOG(WARNING) << "Interface is missing: " << method_call->ToString();
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
// Check if we know about the method.
const std::string absolute_method_name = GetAbsoluteMemberName(
interface, member);
MethodTable::const_iterator iter = method_table_.find(absolute_method_name);
if (iter == method_table_.end()) {
// Don't know about the method.
LOG(WARNING) << "Unknown method: " << method_call->ToString();
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
const base::TimeTicks start_time = base::TimeTicks::Now();
if (bus_->HasDBusThread()) {
// Post a task to run the method in the origin thread.
bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
base::Bind(&ExportedObject::RunMethod,
this,
iter->second,
base::Passed(&method_call),
start_time));
} else {
// If the D-Bus thread is not used, just call the method directly.
MethodCall* method = method_call.get();
iter->second.Run(method,
base::Bind(&ExportedObject::SendResponse,
this,
start_time,
base::Passed(&method_call)));
}
// It's valid to say HANDLED here, and send a method response at a later
// time from OnMethodCompleted() asynchronously.
return DBUS_HANDLER_RESULT_HANDLED;
}
void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
scoped_ptr<MethodCall> method_call,
base::TimeTicks start_time) {
bus_->AssertOnOriginThread();
MethodCall* method = method_call.get();
method_call_callback.Run(method,
base::Bind(&ExportedObject::SendResponse,
this,
start_time,
base::Passed(&method_call)));
}
void ExportedObject::SendResponse(base::TimeTicks start_time,
scoped_ptr<MethodCall> method_call,
scoped_ptr<Response> response) {
DCHECK(method_call);
if (bus_->HasDBusThread()) {
bus_->GetDBusTaskRunner()->PostTask(
FROM_HERE,
base::Bind(&ExportedObject::OnMethodCompleted,
this,
base::Passed(&method_call),
base::Passed(&response),
start_time));
} else {
OnMethodCompleted(std::move(method_call), std::move(response), start_time);
}
}
void ExportedObject::OnMethodCompleted(scoped_ptr<MethodCall> method_call,
scoped_ptr<Response> response,
base::TimeTicks start_time) {
bus_->AssertOnDBusThread();
// Record if the method call is successful, or not. 1 if successful.
UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
response ? 1 : 0,
kSuccessRatioHistogramMaxValue);
// Check if the bus is still connected. If the method takes long to
// complete, the bus may be shut down meanwhile.
if (!bus_->is_connected())
return;
if (!response) {
// Something bad happened in the method call.
scoped_ptr<ErrorResponse> error_response(
ErrorResponse::FromMethodCall(
method_call.get(),
DBUS_ERROR_FAILED,
"error occurred in " + method_call->GetMember()));
bus_->Send(error_response->raw_message(), NULL);
return;
}
// The method call was successful.
bus_->Send(response->raw_message(), NULL);
// Record time spent to handle the the method call. Don't include failures.
UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
base::TimeTicks::Now() - start_time);
}
void ExportedObject::OnUnregistered(DBusConnection* /* connection */) {
}
DBusHandlerResult ExportedObject::HandleMessageThunk(
DBusConnection* connection,
DBusMessage* raw_message,
void* user_data) {
ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
return self->HandleMessage(connection, raw_message);
}
void ExportedObject::OnUnregisteredThunk(DBusConnection *connection,
void* user_data) {
ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
return self->OnUnregistered(connection);
}
} // namespace dbus