You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
759 lines
28 KiB
759 lines
28 KiB
// 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/object_proxy.h" |
|
|
|
#include <stddef.h> |
|
#include <utility> |
|
|
|
#include "base/bind.h" |
|
#include "base/bind_helpers.h" |
|
#include "base/debug/leak_annotations.h" |
|
#include "base/logging.h" |
|
#include "base/metrics/histogram_macros.h" |
|
#include "base/strings/string_piece.h" |
|
#include "base/strings/stringprintf.h" |
|
#include "base/task_runner.h" |
|
#include "base/task_runner_util.h" |
|
#include "base/threading/thread.h" |
|
#include "base/threading/thread_restrictions.h" |
|
#include "dbus/bus.h" |
|
#include "dbus/dbus_statistics.h" |
|
#include "dbus/message.h" |
|
#include "dbus/object_path.h" |
|
#include "dbus/scoped_dbus_error.h" |
|
#include "dbus/util.h" |
|
|
|
namespace dbus { |
|
|
|
namespace { |
|
|
|
constexpr char kErrorServiceUnknown[] = |
|
"org.freedesktop.DBus.Error.ServiceUnknown"; |
|
constexpr char kErrorObjectUnknown[] = |
|
"org.freedesktop.DBus.Error.UnknownObject"; |
|
|
|
// Used for success ratio histograms. 1 for success, 0 for failure. |
|
constexpr int kSuccessRatioHistogramMaxValue = 2; |
|
|
|
// The path of D-Bus Object sending NameOwnerChanged signal. |
|
constexpr char kDBusSystemObjectPath[] = "/org/freedesktop/DBus"; |
|
|
|
// The D-Bus Object interface. |
|
constexpr char kDBusSystemObjectInterface[] = "org.freedesktop.DBus"; |
|
|
|
// The D-Bus Object address. |
|
constexpr char kDBusSystemObjectAddress[] = "org.freedesktop.DBus"; |
|
|
|
// The NameOwnerChanged member in |kDBusSystemObjectInterface|. |
|
constexpr char kNameOwnerChangedMember[] = "NameOwnerChanged"; |
|
|
|
} // namespace |
|
|
|
ObjectProxy::ReplyCallbackHolder::ReplyCallbackHolder( |
|
scoped_refptr<base::TaskRunner> origin_task_runner, |
|
ResponseOrErrorCallback callback) |
|
: origin_task_runner_(origin_task_runner), callback_(std::move(callback)) { |
|
DCHECK(origin_task_runner_.get()); |
|
DCHECK(!callback_.is_null()); |
|
} |
|
|
|
ObjectProxy::ReplyCallbackHolder::ReplyCallbackHolder( |
|
ReplyCallbackHolder&& other) = default; |
|
|
|
ObjectProxy::ReplyCallbackHolder::~ReplyCallbackHolder() { |
|
if (callback_.is_null()) { |
|
// This is the regular case. |
|
// CallMethod and its family creates this object on the origin thread, |
|
// PostTask()s to the D-Bus thread for actual D-Bus communication, |
|
// then PostTask()s back to the origin thread to invoke the |callback_|. |
|
// At that timing, the ownership of callback should be released via |
|
// ReleaseCallback(). |
|
// Otherwise, this instance was moved to another one. Do nothing in |
|
// either case. |
|
return; |
|
} |
|
|
|
// The only case where |origin_task_runner_| becomes nullptr is that |
|
// this is moved. In such a case, |callback_| should be nullptr, too, so it |
|
// should be handled above. Thus, here |origin_task_runner_| must not be |
|
// nullptr. |
|
DCHECK(origin_task_runner_.get()); |
|
|
|
if (origin_task_runner_->RunsTasksInCurrentSequence()) { |
|
// Destroyed on the origin thread. This happens when PostTask()ing to |
|
// the D-Bus thread fails. The |callback_| can be destroyed on the |
|
// current thread safely. Do nothing here, and let member destruction |
|
// destroy the callback. |
|
return; |
|
} |
|
|
|
// Here is on D-Bus thread, so try to PostTask() to destroy the callback. |
|
// to the origin thread. |
|
// The |origin_task_runner_| may already have stopped. E.g., on Chrome's |
|
// shutdown the message loop of the UI thread (= the origin thread) stops |
|
// before D-Bus threaed's. In such a case, PostTask() fails. Because we |
|
// cannot do much thing here, instead, simply leak the callback rather than |
|
// destroying it on the D-Bus thread, which could be unexpected from the |
|
// direct or indirect caller of CallMethod. |
|
auto* callback_to_be_deleted = |
|
new ResponseOrErrorCallback(std::move(callback_)); |
|
ANNOTATE_LEAKING_OBJECT_PTR(callback_to_be_deleted); |
|
origin_task_runner_->PostTask( |
|
FROM_HERE, base::BindOnce(&base::DeletePointer<ResponseOrErrorCallback>, |
|
callback_to_be_deleted)); |
|
} |
|
|
|
ObjectProxy::ResponseOrErrorCallback |
|
ObjectProxy::ReplyCallbackHolder::ReleaseCallback() { |
|
DCHECK(origin_task_runner_->RunsTasksInCurrentSequence()); |
|
return std::move(callback_); |
|
} |
|
|
|
ObjectProxy::ObjectProxy(Bus* bus, |
|
const std::string& service_name, |
|
const ObjectPath& object_path, |
|
int options) |
|
: bus_(bus), |
|
service_name_(service_name), |
|
object_path_(object_path), |
|
ignore_service_unknown_errors_( |
|
options & IGNORE_SERVICE_UNKNOWN_ERRORS) { |
|
LOG_IF(FATAL, !object_path_.IsValid()) << object_path_.value(); |
|
} |
|
|
|
ObjectProxy::~ObjectProxy() { |
|
DCHECK(pending_calls_.empty()); |
|
} |
|
|
|
// Originally we tried to make |method_call| a const reference, but we |
|
// gave up as dbus_connection_send_with_reply_and_block() takes a |
|
// non-const pointer of DBusMessage as the second parameter. |
|
std::unique_ptr<Response> ObjectProxy::CallMethodAndBlockWithErrorDetails( |
|
MethodCall* method_call, |
|
int timeout_ms, |
|
ScopedDBusError* error) { |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (!bus_->Connect() || |
|
!method_call->SetDestination(service_name_) || |
|
!method_call->SetPath(object_path_)) |
|
return std::unique_ptr<Response>(); |
|
|
|
DBusMessage* request_message = method_call->raw_message(); |
|
|
|
// Send the message synchronously. |
|
const base::TimeTicks start_time = base::TimeTicks::Now(); |
|
DBusMessage* response_message = |
|
bus_->SendWithReplyAndBlock(request_message, timeout_ms, error->get()); |
|
// Record if the method call is successful, or not. 1 if successful. |
|
UMA_HISTOGRAM_ENUMERATION("DBus.SyncMethodCallSuccess", |
|
response_message ? 1 : 0, |
|
kSuccessRatioHistogramMaxValue); |
|
statistics::AddBlockingSentMethodCall(service_name_, |
|
method_call->GetInterface(), |
|
method_call->GetMember()); |
|
|
|
if (!response_message) { |
|
LogMethodCallFailure(method_call->GetInterface(), |
|
method_call->GetMember(), |
|
error->is_set() ? error->name() : "unknown error type", |
|
error->is_set() ? error->message() : ""); |
|
return std::unique_ptr<Response>(); |
|
} |
|
// Record time spent for the method call. Don't include failures. |
|
UMA_HISTOGRAM_TIMES("DBus.SyncMethodCallTime", |
|
base::TimeTicks::Now() - start_time); |
|
|
|
return Response::FromRawMessage(response_message); |
|
} |
|
|
|
std::unique_ptr<Response> ObjectProxy::CallMethodAndBlock( |
|
MethodCall* method_call, |
|
int timeout_ms) { |
|
ScopedDBusError error; |
|
return CallMethodAndBlockWithErrorDetails(method_call, timeout_ms, &error); |
|
} |
|
|
|
void ObjectProxy::CallMethod(MethodCall* method_call, |
|
int timeout_ms, |
|
ResponseCallback callback) { |
|
auto internal_callback = base::BindOnce( |
|
&ObjectProxy::OnCallMethod, this, method_call->GetInterface(), |
|
method_call->GetMember(), std::move(callback)); |
|
|
|
CallMethodWithErrorResponse(method_call, timeout_ms, |
|
std::move(internal_callback)); |
|
} |
|
|
|
void ObjectProxy::CallMethodWithErrorResponse( |
|
MethodCall* method_call, |
|
int timeout_ms, |
|
ResponseOrErrorCallback callback) { |
|
bus_->AssertOnOriginThread(); |
|
|
|
const base::TimeTicks start_time = base::TimeTicks::Now(); |
|
|
|
ReplyCallbackHolder callback_holder(bus_->GetOriginTaskRunner(), |
|
std::move(callback)); |
|
|
|
if (!method_call->SetDestination(service_name_) || |
|
!method_call->SetPath(object_path_)) { |
|
// In case of a failure, run the error callback with nullptr. |
|
base::OnceClosure task = |
|
base::BindOnce(&ObjectProxy::RunResponseOrErrorCallback, this, |
|
std::move(callback_holder), start_time, |
|
nullptr /* response */, nullptr /* error_response */); |
|
bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, std::move(task)); |
|
return; |
|
} |
|
|
|
// Increment the reference count so we can safely reference the |
|
// underlying request message until the method call is complete. This |
|
// will be unref'ed in StartAsyncMethodCall(). |
|
DBusMessage* request_message = method_call->raw_message(); |
|
dbus_message_ref(request_message); |
|
|
|
statistics::AddSentMethodCall(service_name_, |
|
method_call->GetInterface(), |
|
method_call->GetMember()); |
|
|
|
// Wait for the response in the D-Bus thread. |
|
base::OnceClosure task = |
|
base::BindOnce(&ObjectProxy::StartAsyncMethodCall, this, timeout_ms, |
|
request_message, std::move(callback_holder), start_time); |
|
bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, std::move(task)); |
|
} |
|
|
|
void ObjectProxy::CallMethodWithErrorCallback(MethodCall* method_call, |
|
int timeout_ms, |
|
ResponseCallback callback, |
|
ErrorCallback error_callback) { |
|
auto internal_callback = base::BindOnce( |
|
[](ResponseCallback callback, ErrorCallback error_callback, |
|
Response* response, ErrorResponse* error_response) { |
|
if (response) { |
|
std::move(callback).Run(response); |
|
} else { |
|
std::move(error_callback).Run(error_response); |
|
} |
|
}, |
|
std::move(callback), std::move(error_callback)); |
|
|
|
CallMethodWithErrorResponse(method_call, timeout_ms, |
|
std::move(internal_callback)); |
|
} |
|
|
|
void ObjectProxy::ConnectToSignal(const std::string& interface_name, |
|
const std::string& signal_name, |
|
SignalCallback signal_callback, |
|
OnConnectedCallback on_connected_callback) { |
|
bus_->AssertOnOriginThread(); |
|
|
|
if (bus_->HasDBusThread()) { |
|
base::PostTaskAndReplyWithResult( |
|
bus_->GetDBusTaskRunner(), FROM_HERE, |
|
base::BindOnce(&ObjectProxy::ConnectToSignalInternal, this, |
|
interface_name, signal_name, signal_callback), |
|
base::BindOnce(std::move(on_connected_callback), interface_name, |
|
signal_name)); |
|
} else { |
|
// If the bus doesn't have a dedicated dbus thread we need to call |
|
// ConnectToSignalInternal directly otherwise we might miss a signal |
|
// that is currently queued if we do a PostTask. |
|
const bool success = |
|
ConnectToSignalInternal(interface_name, signal_name, signal_callback); |
|
std::move(on_connected_callback).Run(interface_name, signal_name, success); |
|
} |
|
} |
|
|
|
void ObjectProxy::SetNameOwnerChangedCallback( |
|
NameOwnerChangedCallback callback) { |
|
bus_->AssertOnOriginThread(); |
|
|
|
name_owner_changed_callback_ = callback; |
|
} |
|
|
|
void ObjectProxy::WaitForServiceToBeAvailable( |
|
WaitForServiceToBeAvailableCallback callback) { |
|
bus_->AssertOnOriginThread(); |
|
|
|
wait_for_service_to_be_available_callbacks_.push_back(std::move(callback)); |
|
bus_->GetDBusTaskRunner()->PostTask( |
|
FROM_HERE, |
|
base::BindOnce(&ObjectProxy::WaitForServiceToBeAvailableInternal, this)); |
|
} |
|
|
|
void ObjectProxy::Detach() { |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (bus_->is_connected()) |
|
bus_->RemoveFilterFunction(&ObjectProxy::HandleMessageThunk, this); |
|
|
|
for (const auto& match_rule : match_rules_) { |
|
ScopedDBusError error; |
|
bus_->RemoveMatch(match_rule, error.get()); |
|
if (error.is_set()) { |
|
// There is nothing we can do to recover, so just print the error. |
|
LOG(ERROR) << "Failed to remove match rule: " << match_rule; |
|
} |
|
} |
|
match_rules_.clear(); |
|
|
|
for (auto* pending_call : pending_calls_) { |
|
dbus_pending_call_cancel(pending_call); |
|
dbus_pending_call_unref(pending_call); |
|
} |
|
pending_calls_.clear(); |
|
} |
|
|
|
void ObjectProxy::StartAsyncMethodCall(int timeout_ms, |
|
DBusMessage* request_message, |
|
ReplyCallbackHolder callback_holder, |
|
base::TimeTicks start_time) { |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (!bus_->Connect() || !bus_->SetUpAsyncOperations()) { |
|
// In case of a failure, run the error callback with nullptr. |
|
base::OnceClosure task = |
|
base::BindOnce(&ObjectProxy::RunResponseOrErrorCallback, this, |
|
std::move(callback_holder), start_time, |
|
nullptr /* response */, nullptr /* error_response */); |
|
bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, std::move(task)); |
|
|
|
dbus_message_unref(request_message); |
|
return; |
|
} |
|
|
|
DBusPendingCall* dbus_pending_call = nullptr; |
|
bus_->SendWithReply(request_message, &dbus_pending_call, timeout_ms); |
|
|
|
using PendingCallback = |
|
base::OnceCallback<void(DBusPendingCall * pending_call)>; |
|
// This returns false only when unable to allocate memory. |
|
const bool success = dbus_pending_call_set_notify( |
|
dbus_pending_call, |
|
[](DBusPendingCall* pending_call, void* user_data) { |
|
std::move(*static_cast<PendingCallback*>(user_data)).Run(pending_call); |
|
}, |
|
// PendingCallback instance is owned by libdbus. |
|
new PendingCallback(base::BindOnce(&ObjectProxy::OnPendingCallIsComplete, |
|
this, std::move(callback_holder), |
|
start_time)), |
|
[](void* user_data) { delete static_cast<PendingCallback*>(user_data); }); |
|
CHECK(success) << "Unable to allocate memory"; |
|
pending_calls_.insert(dbus_pending_call); |
|
|
|
// It's now safe to unref the request message. |
|
dbus_message_unref(request_message); |
|
} |
|
|
|
void ObjectProxy::OnPendingCallIsComplete(ReplyCallbackHolder callback_holder, |
|
base::TimeTicks start_time, |
|
DBusPendingCall* pending_call) { |
|
bus_->AssertOnDBusThread(); |
|
|
|
DBusMessage* response_message = dbus_pending_call_steal_reply(pending_call); |
|
|
|
// Either |response| or |error_response| takes ownership of the |
|
// |response_message|. |
|
std::unique_ptr<Response> response; |
|
std::unique_ptr<ErrorResponse> error_response; |
|
if (dbus_message_get_type(response_message) == DBUS_MESSAGE_TYPE_ERROR) { |
|
error_response = ErrorResponse::FromRawMessage(response_message); |
|
} else { |
|
response = Response::FromRawMessage(response_message); |
|
} |
|
|
|
base::OnceClosure task = |
|
base::BindOnce(&ObjectProxy::RunResponseOrErrorCallback, this, |
|
std::move(callback_holder), start_time, response.get(), |
|
error_response.get()); |
|
|
|
// The message should be deleted on the D-Bus thread for a complicated |
|
// reason: |
|
// |
|
// libdbus keeps track of the number of bytes in the incoming message |
|
// queue to ensure that the data size in the queue is manageable. The |
|
// bookkeeping is partly done via dbus_message_unref(), and immediately |
|
// asks the client code (Chrome) to stop monitoring the underlying |
|
// socket, if the number of bytes exceeds a certian number, which is set |
|
// to 63MB, per dbus-transport.cc: |
|
// |
|
// /* Try to default to something that won't totally hose the system, |
|
// * but doesn't impose too much of a limitation. |
|
// */ |
|
// transport->max_live_messages_size = _DBUS_ONE_MEGABYTE * 63; |
|
// |
|
// The monitoring of the socket is done on the D-Bus thread (see Watch |
|
// class in bus.cc), hence we should stop the monitoring on D-Bus thread. |
|
bus_->GetOriginTaskRunner()->PostTaskAndReply( |
|
FROM_HERE, std::move(task), |
|
base::BindOnce( |
|
[](Response* response, ErrorResponse* error_response) { |
|
// Do nothing. |
|
}, |
|
base::Owned(response.release()), |
|
base::Owned(error_response.release()))); |
|
|
|
// Remove the pending call from the set. |
|
pending_calls_.erase(pending_call); |
|
dbus_pending_call_unref(pending_call); |
|
} |
|
|
|
void ObjectProxy::RunResponseOrErrorCallback( |
|
ReplyCallbackHolder callback_holder, |
|
base::TimeTicks start_time, |
|
Response* response, |
|
ErrorResponse* error_response) { |
|
bus_->AssertOnOriginThread(); |
|
callback_holder.ReleaseCallback().Run(response, error_response); |
|
|
|
if (response) { |
|
// Record time spent for the method call. Don't include failures. |
|
UMA_HISTOGRAM_TIMES("DBus.AsyncMethodCallTime", |
|
base::TimeTicks::Now() - start_time); |
|
} |
|
// Record if the method call is successful, or not. 1 if successful. |
|
UMA_HISTOGRAM_ENUMERATION("DBus.AsyncMethodCallSuccess", response ? 1 : 0, |
|
kSuccessRatioHistogramMaxValue); |
|
} |
|
|
|
bool ObjectProxy::ConnectToNameOwnerChangedSignal() { |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (!bus_->Connect() || !bus_->SetUpAsyncOperations()) |
|
return false; |
|
|
|
bus_->AddFilterFunction(&ObjectProxy::HandleMessageThunk, this); |
|
|
|
// Add a match_rule listening NameOwnerChanged for the well-known name |
|
// |service_name_|. |
|
const std::string name_owner_changed_match_rule = |
|
base::StringPrintf( |
|
"type='signal',interface='org.freedesktop.DBus'," |
|
"member='NameOwnerChanged',path='/org/freedesktop/DBus'," |
|
"sender='org.freedesktop.DBus',arg0='%s'", |
|
service_name_.c_str()); |
|
|
|
const bool success = |
|
AddMatchRuleWithoutCallback(name_owner_changed_match_rule, |
|
"org.freedesktop.DBus.NameOwnerChanged"); |
|
|
|
// Try getting the current name owner. It's not guaranteed that we can get |
|
// the name owner at this moment, as the service may not yet be started. If |
|
// that's the case, we'll get the name owner via NameOwnerChanged signal, |
|
// as soon as the service is started. |
|
UpdateNameOwnerAndBlock(); |
|
|
|
return success; |
|
} |
|
|
|
bool ObjectProxy::ConnectToSignalInternal(const std::string& interface_name, |
|
const std::string& signal_name, |
|
SignalCallback signal_callback) { |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (!ConnectToNameOwnerChangedSignal()) |
|
return false; |
|
|
|
const std::string absolute_signal_name = |
|
GetAbsoluteMemberName(interface_name, signal_name); |
|
|
|
// Add a match rule so the signal goes through HandleMessage(). |
|
const std::string match_rule = base::StringPrintf( |
|
"type='signal', sender='%s', interface='%s', path='%s'", |
|
service_name_.c_str(), interface_name.c_str(), |
|
object_path_.value().c_str()); |
|
return AddMatchRuleWithCallback(match_rule, |
|
absolute_signal_name, |
|
signal_callback); |
|
} |
|
|
|
void ObjectProxy::WaitForServiceToBeAvailableInternal() { |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (!ConnectToNameOwnerChangedSignal()) { // Failed to connect to the signal. |
|
const bool service_is_ready = false; |
|
bus_->GetOriginTaskRunner()->PostTask( |
|
FROM_HERE, |
|
base::BindOnce(&ObjectProxy::RunWaitForServiceToBeAvailableCallbacks, |
|
this, service_is_ready)); |
|
return; |
|
} |
|
|
|
const bool service_is_available = !service_name_owner_.empty(); |
|
if (service_is_available) { // Service is already available. |
|
bus_->GetOriginTaskRunner()->PostTask( |
|
FROM_HERE, |
|
base::BindOnce(&ObjectProxy::RunWaitForServiceToBeAvailableCallbacks, |
|
this, service_is_available)); |
|
return; |
|
} |
|
} |
|
|
|
DBusHandlerResult ObjectProxy::HandleMessage( |
|
DBusConnection* connection, |
|
DBusMessage* raw_message) { |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_SIGNAL) |
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
|
|
|
// raw_message will be unrefed on exit of the function. Increment the |
|
// reference so we can use it in Signal. |
|
dbus_message_ref(raw_message); |
|
std::unique_ptr<Signal> signal(Signal::FromRawMessage(raw_message)); |
|
|
|
// Verify the signal comes from the object we're proxying for, this is |
|
// our last chance to return DBUS_HANDLER_RESULT_NOT_YET_HANDLED and |
|
// allow other object proxies to handle instead. |
|
const ObjectPath path = signal->GetPath(); |
|
if (path != object_path_) { |
|
if (path.value() == kDBusSystemObjectPath && |
|
signal->GetMember() == kNameOwnerChangedMember) { |
|
// Handle NameOwnerChanged separately |
|
return HandleNameOwnerChanged(std::move(signal)); |
|
} |
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
|
} |
|
|
|
const std::string interface = signal->GetInterface(); |
|
const std::string member = signal->GetMember(); |
|
|
|
statistics::AddReceivedSignal(service_name_, interface, member); |
|
|
|
// Check if we know about the signal. |
|
const std::string absolute_signal_name = GetAbsoluteMemberName( |
|
interface, member); |
|
MethodTable::const_iterator iter = method_table_.find(absolute_signal_name); |
|
if (iter == method_table_.end()) { |
|
// Don't know about the signal. |
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
|
} |
|
VLOG(1) << "Signal received: " << signal->ToString(); |
|
|
|
std::string sender = signal->GetSender(); |
|
if (service_name_owner_ != sender) { |
|
LOG(ERROR) << "Rejecting a message from a wrong sender."; |
|
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. |
|
// Transfer the ownership of |signal| to RunMethod(). |
|
// |released_signal| will be deleted in RunMethod(). |
|
Signal* released_signal = signal.release(); |
|
bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, |
|
base::Bind(&ObjectProxy::RunMethod, |
|
this, |
|
start_time, |
|
iter->second, |
|
released_signal)); |
|
} else { |
|
const base::TimeTicks start_time = base::TimeTicks::Now(); |
|
// If the D-Bus thread is not used, just call the callback on the |
|
// current thread. Transfer the ownership of |signal| to RunMethod(). |
|
Signal* released_signal = signal.release(); |
|
RunMethod(start_time, iter->second, released_signal); |
|
} |
|
|
|
// We don't return DBUS_HANDLER_RESULT_HANDLED for signals because other |
|
// objects may be interested in them. (e.g. Signals from org.freedesktop.DBus) |
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
|
} |
|
|
|
void ObjectProxy::RunMethod(base::TimeTicks start_time, |
|
std::vector<SignalCallback> signal_callbacks, |
|
Signal* signal) { |
|
bus_->AssertOnOriginThread(); |
|
|
|
for (std::vector<SignalCallback>::iterator iter = signal_callbacks.begin(); |
|
iter != signal_callbacks.end(); ++iter) |
|
iter->Run(signal); |
|
|
|
// Delete the message on the D-Bus thread. See comments in |
|
// RunResponseOrErrorCallback(). |
|
bus_->GetDBusTaskRunner()->PostTask( |
|
FROM_HERE, |
|
base::Bind(&base::DeletePointer<Signal>, signal)); |
|
|
|
// Record time spent for handling the signal. |
|
UMA_HISTOGRAM_TIMES("DBus.SignalHandleTime", |
|
base::TimeTicks::Now() - start_time); |
|
} |
|
|
|
DBusHandlerResult ObjectProxy::HandleMessageThunk( |
|
DBusConnection* connection, |
|
DBusMessage* raw_message, |
|
void* user_data) { |
|
ObjectProxy* self = reinterpret_cast<ObjectProxy*>(user_data); |
|
return self->HandleMessage(connection, raw_message); |
|
} |
|
|
|
void ObjectProxy::LogMethodCallFailure( |
|
const base::StringPiece& interface_name, |
|
const base::StringPiece& method_name, |
|
const base::StringPiece& error_name, |
|
const base::StringPiece& error_message) const { |
|
if (ignore_service_unknown_errors_ && |
|
(error_name == kErrorServiceUnknown || error_name == kErrorObjectUnknown)) |
|
return; |
|
|
|
std::ostringstream msg; |
|
msg << "Failed to call method: " << interface_name << "." << method_name |
|
<< ": object_path= " << object_path_.value() |
|
<< ": " << error_name << ": " << error_message; |
|
|
|
// "UnknownObject" indicates that an object or service is no longer available, |
|
// e.g. a Shill network service has gone out of range. Treat these as warnings |
|
// not errors. |
|
if (error_name == kErrorObjectUnknown) |
|
LOG(WARNING) << msg.str(); |
|
else |
|
LOG(ERROR) << msg.str(); |
|
} |
|
|
|
void ObjectProxy::OnCallMethod(const std::string& interface_name, |
|
const std::string& method_name, |
|
ResponseCallback response_callback, |
|
Response* response, |
|
ErrorResponse* error_response) { |
|
if (response) { |
|
// Method call was successful. |
|
std::move(response_callback).Run(response); |
|
return; |
|
} |
|
// Method call failed. |
|
std::string error_name; |
|
std::string error_message; |
|
if (error_response) { |
|
// Error message may contain the error message as string. |
|
error_name = error_response->GetErrorName(); |
|
MessageReader reader(error_response); |
|
reader.PopString(&error_message); |
|
} else { |
|
error_name = "unknown error type"; |
|
} |
|
LogMethodCallFailure(interface_name, method_name, error_name, error_message); |
|
|
|
std::move(response_callback).Run(nullptr); |
|
} |
|
|
|
bool ObjectProxy::AddMatchRuleWithCallback( |
|
const std::string& match_rule, |
|
const std::string& absolute_signal_name, |
|
SignalCallback signal_callback) { |
|
DCHECK(!match_rule.empty()); |
|
DCHECK(!absolute_signal_name.empty()); |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (match_rules_.find(match_rule) == match_rules_.end()) { |
|
ScopedDBusError error; |
|
bus_->AddMatch(match_rule, error.get()); |
|
if (error.is_set()) { |
|
LOG(ERROR) << "Failed to add match rule \"" << match_rule << "\". Got " |
|
<< error.name() << ": " << error.message(); |
|
return false; |
|
} else { |
|
// Store the match rule, so that we can remove this in Detach(). |
|
match_rules_.insert(match_rule); |
|
// Add the signal callback to the method table. |
|
method_table_[absolute_signal_name].push_back(signal_callback); |
|
return true; |
|
} |
|
} else { |
|
// We already have the match rule. |
|
method_table_[absolute_signal_name].push_back(signal_callback); |
|
return true; |
|
} |
|
} |
|
|
|
bool ObjectProxy::AddMatchRuleWithoutCallback( |
|
const std::string& match_rule, |
|
const std::string& absolute_signal_name) { |
|
DCHECK(!match_rule.empty()); |
|
DCHECK(!absolute_signal_name.empty()); |
|
bus_->AssertOnDBusThread(); |
|
|
|
if (match_rules_.find(match_rule) != match_rules_.end()) |
|
return true; |
|
|
|
ScopedDBusError error; |
|
bus_->AddMatch(match_rule, error.get()); |
|
if (error.is_set()) { |
|
LOG(ERROR) << "Failed to add match rule \"" << match_rule << "\". Got " |
|
<< error.name() << ": " << error.message(); |
|
return false; |
|
} |
|
// Store the match rule, so that we can remove this in Detach(). |
|
match_rules_.insert(match_rule); |
|
return true; |
|
} |
|
|
|
void ObjectProxy::UpdateNameOwnerAndBlock() { |
|
bus_->AssertOnDBusThread(); |
|
// Errors should be suppressed here, as the service may not be yet running |
|
// when connecting to signals of the service, which is just fine. |
|
// The ObjectProxy will be notified when the service is launched via |
|
// NameOwnerChanged signal. See also comments in ConnectToSignalInternal(). |
|
service_name_owner_ = |
|
bus_->GetServiceOwnerAndBlock(service_name_, Bus::SUPPRESS_ERRORS); |
|
} |
|
|
|
DBusHandlerResult ObjectProxy::HandleNameOwnerChanged( |
|
std::unique_ptr<Signal> signal) { |
|
DCHECK(signal); |
|
bus_->AssertOnDBusThread(); |
|
|
|
// Confirm the validity of the NameOwnerChanged signal. |
|
if (signal->GetMember() == kNameOwnerChangedMember && |
|
signal->GetInterface() == kDBusSystemObjectInterface && |
|
signal->GetSender() == kDBusSystemObjectAddress) { |
|
MessageReader reader(signal.get()); |
|
std::string name, old_owner, new_owner; |
|
if (reader.PopString(&name) && |
|
reader.PopString(&old_owner) && |
|
reader.PopString(&new_owner) && |
|
name == service_name_) { |
|
service_name_owner_ = new_owner; |
|
bus_->GetOriginTaskRunner()->PostTask( |
|
FROM_HERE, |
|
base::Bind(&ObjectProxy::RunNameOwnerChangedCallback, |
|
this, old_owner, new_owner)); |
|
|
|
const bool service_is_available = !service_name_owner_.empty(); |
|
if (service_is_available) { |
|
bus_->GetOriginTaskRunner()->PostTask( |
|
FROM_HERE, |
|
base::Bind(&ObjectProxy::RunWaitForServiceToBeAvailableCallbacks, |
|
this, service_is_available)); |
|
} |
|
} |
|
} |
|
|
|
// Always return unhandled to let other object proxies handle the same |
|
// signal. |
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; |
|
} |
|
|
|
void ObjectProxy::RunNameOwnerChangedCallback(const std::string& old_owner, |
|
const std::string& new_owner) { |
|
bus_->AssertOnOriginThread(); |
|
if (!name_owner_changed_callback_.is_null()) |
|
name_owner_changed_callback_.Run(old_owner, new_owner); |
|
} |
|
|
|
void ObjectProxy::RunWaitForServiceToBeAvailableCallbacks( |
|
bool service_is_available) { |
|
bus_->AssertOnOriginThread(); |
|
|
|
std::vector<WaitForServiceToBeAvailableCallback> callbacks; |
|
callbacks.swap(wait_for_service_to_be_available_callbacks_); |
|
for (size_t i = 0; i < callbacks.size(); ++i) |
|
std::move(callbacks[i]).Run(service_is_available); |
|
} |
|
|
|
} // namespace dbus
|
|
|