object_proxy.cc revision b2df76ea8fec9e32f6f3718986dba0d95315b29c
1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "dbus/bus.h" 6 7#include "base/bind.h" 8#include "base/logging.h" 9#include "base/message_loop.h" 10#include "base/metrics/histogram.h" 11#include "base/stringprintf.h" 12#include "base/strings/string_piece.h" 13#include "base/threading/thread.h" 14#include "base/threading/thread_restrictions.h" 15#include "dbus/dbus_statistics.h" 16#include "dbus/message.h" 17#include "dbus/object_path.h" 18#include "dbus/object_proxy.h" 19#include "dbus/scoped_dbus_error.h" 20 21namespace dbus { 22 23namespace { 24 25const char kErrorServiceUnknown[] = "org.freedesktop.DBus.Error.ServiceUnknown"; 26 27// Used for success ratio histograms. 1 for success, 0 for failure. 28const int kSuccessRatioHistogramMaxValue = 2; 29 30// The path of D-Bus Object sending NameOwnerChanged signal. 31const char kDBusSystemObjectPath[] = "/org/freedesktop/DBus"; 32 33// The D-Bus Object interface. 34const char kDBusSystemObjectInterface[] = "org.freedesktop.DBus"; 35 36// The D-Bus Object address. 37const char kDBusSystemObjectAddress[] = "org.freedesktop.DBus"; 38 39// The NameOwnerChanged member in |kDBusSystemObjectInterface|. 40const char kNameOwnerChangedMember[] = "NameOwnerChanged"; 41 42// Gets the absolute signal name by concatenating the interface name and 43// the signal name. Used for building keys for method_table_ in 44// ObjectProxy. 45std::string GetAbsoluteSignalName( 46 const std::string& interface_name, 47 const std::string& signal_name) { 48 return interface_name + "." + signal_name; 49} 50 51// An empty function used for ObjectProxy::EmptyResponseCallback(). 52void EmptyResponseCallbackBody(Response* /*response*/) { 53} 54 55} // namespace 56 57ObjectProxy::ObjectProxy(Bus* bus, 58 const std::string& service_name, 59 const ObjectPath& object_path, 60 int options) 61 : bus_(bus), 62 service_name_(service_name), 63 object_path_(object_path), 64 filter_added_(false), 65 ignore_service_unknown_errors_( 66 options & IGNORE_SERVICE_UNKNOWN_ERRORS) { 67} 68 69ObjectProxy::~ObjectProxy() { 70} 71 72// Originally we tried to make |method_call| a const reference, but we 73// gave up as dbus_connection_send_with_reply_and_block() takes a 74// non-const pointer of DBusMessage as the second parameter. 75scoped_ptr<Response> ObjectProxy::CallMethodAndBlock(MethodCall* method_call, 76 int timeout_ms) { 77 bus_->AssertOnDBusThread(); 78 79 if (!bus_->Connect() || 80 !method_call->SetDestination(service_name_) || 81 !method_call->SetPath(object_path_)) 82 return scoped_ptr<Response>(); 83 84 DBusMessage* request_message = method_call->raw_message(); 85 86 ScopedDBusError error; 87 88 // Send the message synchronously. 89 const base::TimeTicks start_time = base::TimeTicks::Now(); 90 DBusMessage* response_message = 91 bus_->SendWithReplyAndBlock(request_message, timeout_ms, error.get()); 92 // Record if the method call is successful, or not. 1 if successful. 93 UMA_HISTOGRAM_ENUMERATION("DBus.SyncMethodCallSuccess", 94 response_message ? 1 : 0, 95 kSuccessRatioHistogramMaxValue); 96 statistics::AddBlockingSentMethodCall(service_name_, 97 method_call->GetInterface(), 98 method_call->GetMember()); 99 100 if (!response_message) { 101 LogMethodCallFailure(method_call->GetInterface(), 102 method_call->GetMember(), 103 error.is_set() ? error.name() : "unknown error type", 104 error.is_set() ? error.message() : ""); 105 return scoped_ptr<Response>(); 106 } 107 // Record time spent for the method call. Don't include failures. 108 UMA_HISTOGRAM_TIMES("DBus.SyncMethodCallTime", 109 base::TimeTicks::Now() - start_time); 110 111 return Response::FromRawMessage(response_message); 112} 113 114void ObjectProxy::CallMethod(MethodCall* method_call, 115 int timeout_ms, 116 ResponseCallback callback) { 117 CallMethodWithErrorCallback(method_call, timeout_ms, callback, 118 base::Bind(&ObjectProxy::OnCallMethodError, 119 this, 120 method_call->GetInterface(), 121 method_call->GetMember(), 122 callback)); 123} 124 125void ObjectProxy::CallMethodWithErrorCallback(MethodCall* method_call, 126 int timeout_ms, 127 ResponseCallback callback, 128 ErrorCallback error_callback) { 129 bus_->AssertOnOriginThread(); 130 131 const base::TimeTicks start_time = base::TimeTicks::Now(); 132 133 if (!method_call->SetDestination(service_name_) || 134 !method_call->SetPath(object_path_)) { 135 // In case of a failure, run the error callback with NULL. 136 DBusMessage* response_message = NULL; 137 base::Closure task = base::Bind(&ObjectProxy::RunResponseCallback, 138 this, 139 callback, 140 error_callback, 141 start_time, 142 response_message); 143 bus_->PostTaskToOriginThread(FROM_HERE, task); 144 return; 145 } 146 147 // Increment the reference count so we can safely reference the 148 // underlying request message until the method call is complete. This 149 // will be unref'ed in StartAsyncMethodCall(). 150 DBusMessage* request_message = method_call->raw_message(); 151 dbus_message_ref(request_message); 152 153 base::Closure task = base::Bind(&ObjectProxy::StartAsyncMethodCall, 154 this, 155 timeout_ms, 156 request_message, 157 callback, 158 error_callback, 159 start_time); 160 statistics::AddSentMethodCall(service_name_, 161 method_call->GetInterface(), 162 method_call->GetMember()); 163 164 // Wait for the response in the D-Bus thread. 165 bus_->PostTaskToDBusThread(FROM_HERE, task); 166} 167 168void ObjectProxy::ConnectToSignal(const std::string& interface_name, 169 const std::string& signal_name, 170 SignalCallback signal_callback, 171 OnConnectedCallback on_connected_callback) { 172 bus_->AssertOnOriginThread(); 173 174 bus_->PostTaskToDBusThread(FROM_HERE, 175 base::Bind(&ObjectProxy::ConnectToSignalInternal, 176 this, 177 interface_name, 178 signal_name, 179 signal_callback, 180 on_connected_callback)); 181} 182 183void ObjectProxy::Detach() { 184 bus_->AssertOnDBusThread(); 185 186 if (filter_added_) { 187 if (!bus_->RemoveFilterFunction(&ObjectProxy::HandleMessageThunk, this)) { 188 LOG(ERROR) << "Failed to remove filter function"; 189 } 190 } 191 192 for (std::set<std::string>::iterator iter = match_rules_.begin(); 193 iter != match_rules_.end(); ++iter) { 194 ScopedDBusError error; 195 bus_->RemoveMatch(*iter, error.get()); 196 if (error.is_set()) { 197 // There is nothing we can do to recover, so just print the error. 198 LOG(ERROR) << "Failed to remove match rule: " << *iter; 199 } 200 } 201 match_rules_.clear(); 202} 203 204// static 205ObjectProxy::ResponseCallback ObjectProxy::EmptyResponseCallback() { 206 return base::Bind(&EmptyResponseCallbackBody); 207} 208 209ObjectProxy::OnPendingCallIsCompleteData::OnPendingCallIsCompleteData( 210 ObjectProxy* in_object_proxy, 211 ResponseCallback in_response_callback, 212 ErrorCallback in_error_callback, 213 base::TimeTicks in_start_time) 214 : object_proxy(in_object_proxy), 215 response_callback(in_response_callback), 216 error_callback(in_error_callback), 217 start_time(in_start_time) { 218} 219 220ObjectProxy::OnPendingCallIsCompleteData::~OnPendingCallIsCompleteData() { 221} 222 223void ObjectProxy::StartAsyncMethodCall(int timeout_ms, 224 DBusMessage* request_message, 225 ResponseCallback response_callback, 226 ErrorCallback error_callback, 227 base::TimeTicks start_time) { 228 bus_->AssertOnDBusThread(); 229 230 if (!bus_->Connect() || !bus_->SetUpAsyncOperations()) { 231 // In case of a failure, run the error callback with NULL. 232 DBusMessage* response_message = NULL; 233 base::Closure task = base::Bind(&ObjectProxy::RunResponseCallback, 234 this, 235 response_callback, 236 error_callback, 237 start_time, 238 response_message); 239 bus_->PostTaskToOriginThread(FROM_HERE, task); 240 241 dbus_message_unref(request_message); 242 return; 243 } 244 245 DBusPendingCall* pending_call = NULL; 246 247 bus_->SendWithReply(request_message, &pending_call, timeout_ms); 248 249 // Prepare the data we'll be passing to OnPendingCallIsCompleteThunk(). 250 // The data will be deleted in OnPendingCallIsCompleteThunk(). 251 OnPendingCallIsCompleteData* data = 252 new OnPendingCallIsCompleteData(this, response_callback, error_callback, 253 start_time); 254 255 // This returns false only when unable to allocate memory. 256 const bool success = dbus_pending_call_set_notify( 257 pending_call, 258 &ObjectProxy::OnPendingCallIsCompleteThunk, 259 data, 260 NULL); 261 CHECK(success) << "Unable to allocate memory"; 262 dbus_pending_call_unref(pending_call); 263 264 // It's now safe to unref the request message. 265 dbus_message_unref(request_message); 266} 267 268void ObjectProxy::OnPendingCallIsComplete(DBusPendingCall* pending_call, 269 ResponseCallback response_callback, 270 ErrorCallback error_callback, 271 base::TimeTicks start_time) { 272 bus_->AssertOnDBusThread(); 273 274 DBusMessage* response_message = dbus_pending_call_steal_reply(pending_call); 275 base::Closure task = base::Bind(&ObjectProxy::RunResponseCallback, 276 this, 277 response_callback, 278 error_callback, 279 start_time, 280 response_message); 281 bus_->PostTaskToOriginThread(FROM_HERE, task); 282} 283 284void ObjectProxy::RunResponseCallback(ResponseCallback response_callback, 285 ErrorCallback error_callback, 286 base::TimeTicks start_time, 287 DBusMessage* response_message) { 288 bus_->AssertOnOriginThread(); 289 290 bool method_call_successful = false; 291 if (!response_message) { 292 // The response is not received. 293 error_callback.Run(NULL); 294 } else if (dbus_message_get_type(response_message) == 295 DBUS_MESSAGE_TYPE_ERROR) { 296 // This will take |response_message| and release (unref) it. 297 scoped_ptr<ErrorResponse> error_response( 298 ErrorResponse::FromRawMessage(response_message)); 299 error_callback.Run(error_response.get()); 300 // Delete the message on the D-Bus thread. See below for why. 301 bus_->PostTaskToDBusThread( 302 FROM_HERE, 303 base::Bind(&base::DeletePointer<ErrorResponse>, 304 error_response.release())); 305 } else { 306 // This will take |response_message| and release (unref) it. 307 scoped_ptr<Response> response(Response::FromRawMessage(response_message)); 308 // The response is successfully received. 309 response_callback.Run(response.get()); 310 // The message should be deleted on the D-Bus thread for a complicated 311 // reason: 312 // 313 // libdbus keeps track of the number of bytes in the incoming message 314 // queue to ensure that the data size in the queue is manageable. The 315 // bookkeeping is partly done via dbus_message_unref(), and immediately 316 // asks the client code (Chrome) to stop monitoring the underlying 317 // socket, if the number of bytes exceeds a certian number, which is set 318 // to 63MB, per dbus-transport.cc: 319 // 320 // /* Try to default to something that won't totally hose the system, 321 // * but doesn't impose too much of a limitation. 322 // */ 323 // transport->max_live_messages_size = _DBUS_ONE_MEGABYTE * 63; 324 // 325 // The monitoring of the socket is done on the D-Bus thread (see Watch 326 // class in bus.cc), hence we should stop the monitoring from D-Bus 327 // thread, not from the current thread here, which is likely UI thread. 328 bus_->PostTaskToDBusThread( 329 FROM_HERE, 330 base::Bind(&base::DeletePointer<Response>, response.release())); 331 332 method_call_successful = true; 333 // Record time spent for the method call. Don't include failures. 334 UMA_HISTOGRAM_TIMES("DBus.AsyncMethodCallTime", 335 base::TimeTicks::Now() - start_time); 336 } 337 // Record if the method call is successful, or not. 1 if successful. 338 UMA_HISTOGRAM_ENUMERATION("DBus.AsyncMethodCallSuccess", 339 method_call_successful, 340 kSuccessRatioHistogramMaxValue); 341} 342 343void ObjectProxy::OnPendingCallIsCompleteThunk(DBusPendingCall* pending_call, 344 void* user_data) { 345 OnPendingCallIsCompleteData* data = 346 reinterpret_cast<OnPendingCallIsCompleteData*>(user_data); 347 ObjectProxy* self = data->object_proxy; 348 self->OnPendingCallIsComplete(pending_call, 349 data->response_callback, 350 data->error_callback, 351 data->start_time); 352 delete data; 353} 354 355void ObjectProxy::ConnectToSignalInternal( 356 const std::string& interface_name, 357 const std::string& signal_name, 358 SignalCallback signal_callback, 359 OnConnectedCallback on_connected_callback) { 360 bus_->AssertOnDBusThread(); 361 362 const std::string absolute_signal_name = 363 GetAbsoluteSignalName(interface_name, signal_name); 364 365 // Will become true, if everything is successful. 366 bool success = false; 367 368 if (bus_->Connect() && bus_->SetUpAsyncOperations()) { 369 // We should add the filter only once. Otherwise, HandleMessage() will 370 // be called more than once. 371 if (!filter_added_) { 372 if (bus_->AddFilterFunction(&ObjectProxy::HandleMessageThunk, this)) { 373 filter_added_ = true; 374 } else { 375 LOG(ERROR) << "Failed to add filter function"; 376 } 377 } 378 // Add a match rule so the signal goes through HandleMessage(). 379 const std::string match_rule = 380 base::StringPrintf("type='signal', interface='%s', path='%s'", 381 interface_name.c_str(), 382 object_path_.value().c_str()); 383 // Add a match_rule listening NameOwnerChanged for the well-known name 384 // |service_name_|. 385 const std::string name_owner_changed_match_rule = 386 base::StringPrintf( 387 "type='signal',interface='org.freedesktop.DBus'," 388 "member='NameOwnerChanged',path='/org/freedesktop/DBus'," 389 "sender='org.freedesktop.DBus',arg0='%s'", 390 service_name_.c_str()); 391 if (AddMatchRuleWithCallback(match_rule, 392 absolute_signal_name, 393 signal_callback) && 394 AddMatchRuleWithoutCallback(name_owner_changed_match_rule, 395 "org.freedesktop.DBus.NameOwnerChanged")) { 396 success = true; 397 } 398 399 // Try getting the current name owner. It's not guaranteed that we can get 400 // the name owner at this moment, as the service may not yet be started. If 401 // that's the case, we'll get the name owner via NameOwnerChanged signal, 402 // as soon as the service is started. 403 UpdateNameOwnerAndBlock(); 404 } 405 406 // Run on_connected_callback in the origin thread. 407 bus_->PostTaskToOriginThread( 408 FROM_HERE, 409 base::Bind(&ObjectProxy::OnConnected, 410 this, 411 on_connected_callback, 412 interface_name, 413 signal_name, 414 success)); 415} 416 417void ObjectProxy::OnConnected(OnConnectedCallback on_connected_callback, 418 const std::string& interface_name, 419 const std::string& signal_name, 420 bool success) { 421 bus_->AssertOnOriginThread(); 422 423 on_connected_callback.Run(interface_name, signal_name, success); 424} 425 426void ObjectProxy::SetNameOwnerChangedCallback(SignalCallback callback) { 427 bus_->AssertOnOriginThread(); 428 429 name_owner_changed_callback_ = callback; 430} 431 432DBusHandlerResult ObjectProxy::HandleMessage( 433 DBusConnection* connection, 434 DBusMessage* raw_message) { 435 bus_->AssertOnDBusThread(); 436 437 if (dbus_message_get_type(raw_message) != DBUS_MESSAGE_TYPE_SIGNAL) 438 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 439 440 // raw_message will be unrefed on exit of the function. Increment the 441 // reference so we can use it in Signal. 442 dbus_message_ref(raw_message); 443 scoped_ptr<Signal> signal( 444 Signal::FromRawMessage(raw_message)); 445 446 // Verify the signal comes from the object we're proxying for, this is 447 // our last chance to return DBUS_HANDLER_RESULT_NOT_YET_HANDLED and 448 // allow other object proxies to handle instead. 449 const ObjectPath path = signal->GetPath(); 450 if (path != object_path_) { 451 if (path.value() == kDBusSystemObjectPath && 452 signal->GetMember() == kNameOwnerChangedMember) { 453 // Handle NameOwnerChanged separately 454 return HandleNameOwnerChanged(signal.Pass()); 455 } 456 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 457 } 458 459 const std::string interface = signal->GetInterface(); 460 const std::string member = signal->GetMember(); 461 462 statistics::AddReceivedSignal(service_name_, interface, member); 463 464 // Check if we know about the signal. 465 const std::string absolute_signal_name = GetAbsoluteSignalName( 466 interface, member); 467 MethodTable::const_iterator iter = method_table_.find(absolute_signal_name); 468 if (iter == method_table_.end()) { 469 // Don't know about the signal. 470 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 471 } 472 VLOG(1) << "Signal received: " << signal->ToString(); 473 474 std::string sender = signal->GetSender(); 475 if (service_name_owner_ != sender) { 476 LOG(ERROR) << "Rejecting a message from a wrong sender."; 477 UMA_HISTOGRAM_COUNTS("DBus.RejectedSignalCount", 1); 478 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 479 } 480 481 const base::TimeTicks start_time = base::TimeTicks::Now(); 482 if (bus_->HasDBusThread()) { 483 // Post a task to run the method in the origin thread. 484 // Transfer the ownership of |signal| to RunMethod(). 485 // |released_signal| will be deleted in RunMethod(). 486 Signal* released_signal = signal.release(); 487 bus_->PostTaskToOriginThread(FROM_HERE, 488 base::Bind(&ObjectProxy::RunMethod, 489 this, 490 start_time, 491 iter->second, 492 released_signal)); 493 } else { 494 const base::TimeTicks start_time = base::TimeTicks::Now(); 495 // If the D-Bus thread is not used, just call the callback on the 496 // current thread. Transfer the ownership of |signal| to RunMethod(). 497 Signal* released_signal = signal.release(); 498 RunMethod(start_time, iter->second, released_signal); 499 } 500 501 return DBUS_HANDLER_RESULT_HANDLED; 502} 503 504void ObjectProxy::RunMethod(base::TimeTicks start_time, 505 std::vector<SignalCallback> signal_callbacks, 506 Signal* signal) { 507 bus_->AssertOnOriginThread(); 508 509 for (std::vector<SignalCallback>::iterator iter = signal_callbacks.begin(); 510 iter != signal_callbacks.end(); ++iter) 511 iter->Run(signal); 512 513 // Delete the message on the D-Bus thread. See comments in 514 // RunResponseCallback(). 515 bus_->PostTaskToDBusThread( 516 FROM_HERE, 517 base::Bind(&base::DeletePointer<Signal>, signal)); 518 519 // Record time spent for handling the signal. 520 UMA_HISTOGRAM_TIMES("DBus.SignalHandleTime", 521 base::TimeTicks::Now() - start_time); 522} 523 524DBusHandlerResult ObjectProxy::HandleMessageThunk( 525 DBusConnection* connection, 526 DBusMessage* raw_message, 527 void* user_data) { 528 ObjectProxy* self = reinterpret_cast<ObjectProxy*>(user_data); 529 return self->HandleMessage(connection, raw_message); 530} 531 532void ObjectProxy::LogMethodCallFailure( 533 const base::StringPiece& interface_name, 534 const base::StringPiece& method_name, 535 const base::StringPiece& error_name, 536 const base::StringPiece& error_message) const { 537 if (ignore_service_unknown_errors_ && error_name == kErrorServiceUnknown) 538 return; 539 LOG(ERROR) << "Failed to call method: " 540 << interface_name << "." << method_name 541 << ": object_path= " << object_path_.value() 542 << ": " << error_name << ": " << error_message; 543} 544 545void ObjectProxy::OnCallMethodError(const std::string& interface_name, 546 const std::string& method_name, 547 ResponseCallback response_callback, 548 ErrorResponse* error_response) { 549 if (error_response) { 550 // Error message may contain the error message as string. 551 MessageReader reader(error_response); 552 std::string error_message; 553 reader.PopString(&error_message); 554 LogMethodCallFailure(interface_name, 555 method_name, 556 error_response->GetErrorName(), 557 error_message); 558 } 559 response_callback.Run(NULL); 560} 561 562bool ObjectProxy::AddMatchRuleWithCallback( 563 const std::string& match_rule, 564 const std::string& absolute_signal_name, 565 SignalCallback signal_callback) { 566 DCHECK(!match_rule.empty()); 567 DCHECK(!absolute_signal_name.empty()); 568 bus_->AssertOnDBusThread(); 569 570 if (match_rules_.find(match_rule) == match_rules_.end()) { 571 ScopedDBusError error; 572 bus_->AddMatch(match_rule, error.get()); 573 if (error.is_set()) { 574 LOG(ERROR) << "Failed to add match rule \"" << match_rule << "\". Got " 575 << error.name() << ": " << error.message(); 576 return false; 577 } else { 578 // Store the match rule, so that we can remove this in Detach(). 579 match_rules_.insert(match_rule); 580 // Add the signal callback to the method table. 581 method_table_[absolute_signal_name].push_back(signal_callback); 582 return true; 583 } 584 } else { 585 // We already have the match rule. 586 method_table_[absolute_signal_name].push_back(signal_callback); 587 return true; 588 } 589} 590 591bool ObjectProxy::AddMatchRuleWithoutCallback( 592 const std::string& match_rule, 593 const std::string& absolute_signal_name) { 594 DCHECK(!match_rule.empty()); 595 DCHECK(!absolute_signal_name.empty()); 596 bus_->AssertOnDBusThread(); 597 598 if (match_rules_.find(match_rule) != match_rules_.end()) 599 return true; 600 601 ScopedDBusError error; 602 bus_->AddMatch(match_rule, error.get()); 603 if (error.is_set()) { 604 LOG(ERROR) << "Failed to add match rule \"" << match_rule << "\". Got " 605 << error.name() << ": " << error.message(); 606 return false; 607 } 608 // Store the match rule, so that we can remove this in Detach(). 609 match_rules_.insert(match_rule); 610 return true; 611} 612 613void ObjectProxy::UpdateNameOwnerAndBlock() { 614 bus_->AssertOnDBusThread(); 615 service_name_owner_ = 616 bus_->GetServiceOwnerAndBlock(service_name_, Bus::REPORT_ERRORS); 617} 618 619DBusHandlerResult ObjectProxy::HandleNameOwnerChanged( 620 scoped_ptr<Signal> signal) { 621 DCHECK(signal); 622 bus_->AssertOnDBusThread(); 623 624 // Confirm the validity of the NameOwnerChanged signal. 625 if (signal->GetMember() == kNameOwnerChangedMember && 626 signal->GetInterface() == kDBusSystemObjectInterface && 627 signal->GetSender() == kDBusSystemObjectAddress) { 628 MessageReader reader(signal.get()); 629 std::string name, old_owner, new_owner; 630 if (reader.PopString(&name) && 631 reader.PopString(&old_owner) && 632 reader.PopString(&new_owner) && 633 name == service_name_) { 634 service_name_owner_ = new_owner; 635 if (!name_owner_changed_callback_.is_null()) { 636 const base::TimeTicks start_time = base::TimeTicks::Now(); 637 Signal* released_signal = signal.release(); 638 std::vector<SignalCallback> callbacks; 639 callbacks.push_back(name_owner_changed_callback_); 640 bus_->PostTaskToOriginThread(FROM_HERE, 641 base::Bind(&ObjectProxy::RunMethod, 642 this, 643 start_time, 644 callbacks, 645 released_signal)); 646 } 647 } 648 } 649 650 // Always return unhandled to let other object proxies handle the same 651 // signal. 652 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 653} 654 655} // namespace dbus 656