Home | History | Annotate | Download | only in dbus
      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/exported_object.h"
      6 
      7 #include <stdint.h>
      8 #include <utility>
      9 
     10 #include "base/bind.h"
     11 #include "base/logging.h"
     12 #include "base/memory/ref_counted.h"
     13 #include "base/message_loop/message_loop.h"
     14 #include "base/metrics/histogram.h"
     15 #include "base/threading/thread_restrictions.h"
     16 #include "base/time/time.h"
     17 #include "dbus/bus.h"
     18 #include "dbus/message.h"
     19 #include "dbus/object_path.h"
     20 #include "dbus/scoped_dbus_error.h"
     21 #include "dbus/util.h"
     22 
     23 namespace dbus {
     24 
     25 namespace {
     26 
     27 // Used for success ratio histograms. 1 for success, 0 for failure.
     28 const int kSuccessRatioHistogramMaxValue = 2;
     29 
     30 }  // namespace
     31 
     32 ExportedObject::ExportedObject(Bus* bus,
     33                                const ObjectPath& object_path)
     34     : bus_(bus),
     35       object_path_(object_path),
     36       object_is_registered_(false) {
     37 }
     38 
     39 ExportedObject::~ExportedObject() {
     40   DCHECK(!object_is_registered_);
     41 }
     42 
     43 bool ExportedObject::ExportMethodAndBlock(
     44     const std::string& interface_name,
     45     const std::string& method_name,
     46     MethodCallCallback method_call_callback) {
     47   bus_->AssertOnDBusThread();
     48 
     49   // Check if the method is already exported.
     50   const std::string absolute_method_name =
     51       GetAbsoluteMemberName(interface_name, method_name);
     52   if (method_table_.find(absolute_method_name) != method_table_.end()) {
     53     LOG(ERROR) << absolute_method_name << " is already exported";
     54     return false;
     55   }
     56 
     57   if (!bus_->Connect())
     58     return false;
     59   if (!bus_->SetUpAsyncOperations())
     60     return false;
     61   if (!Register())
     62     return false;
     63 
     64   // Add the method callback to the method table.
     65   method_table_[absolute_method_name] = method_call_callback;
     66 
     67   return true;
     68 }
     69 
     70 void ExportedObject::ExportMethod(const std::string& interface_name,
     71                                   const std::string& method_name,
     72                                   MethodCallCallback method_call_callback,
     73                                   OnExportedCallback on_exported_calback) {
     74   bus_->AssertOnOriginThread();
     75 
     76   base::Closure task = base::Bind(&ExportedObject::ExportMethodInternal,
     77                                   this,
     78                                   interface_name,
     79                                   method_name,
     80                                   method_call_callback,
     81                                   on_exported_calback);
     82   bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, task);
     83 }
     84 
     85 void ExportedObject::SendSignal(Signal* signal) {
     86   // For signals, the object path should be set to the path to the sender
     87   // object, which is this exported object here.
     88   CHECK(signal->SetPath(object_path_));
     89 
     90   // Increment the reference count so we can safely reference the
     91   // underlying signal message until the signal sending is complete. This
     92   // will be unref'ed in SendSignalInternal().
     93   DBusMessage* signal_message = signal->raw_message();
     94   dbus_message_ref(signal_message);
     95 
     96   const base::TimeTicks start_time = base::TimeTicks::Now();
     97   if (bus_->GetDBusTaskRunner()->RunsTasksOnCurrentThread()) {
     98     // The Chrome OS power manager doesn't use a dedicated TaskRunner for
     99     // sending DBus messages.  Sending signals asynchronously can cause an
    100     // inversion in the message order if the power manager calls
    101     // ObjectProxy::CallMethodAndBlock() before going back to the top level of
    102     // the MessageLoop: crbug.com/472361.
    103     SendSignalInternal(start_time, signal_message);
    104   } else {
    105     bus_->GetDBusTaskRunner()->PostTask(
    106         FROM_HERE,
    107         base::Bind(&ExportedObject::SendSignalInternal,
    108                    this,
    109                    start_time,
    110                    signal_message));
    111   }
    112 }
    113 
    114 void ExportedObject::Unregister() {
    115   bus_->AssertOnDBusThread();
    116 
    117   if (!object_is_registered_)
    118     return;
    119 
    120   bus_->UnregisterObjectPath(object_path_);
    121   object_is_registered_ = false;
    122 }
    123 
    124 void ExportedObject::ExportMethodInternal(
    125     const std::string& interface_name,
    126     const std::string& method_name,
    127     MethodCallCallback method_call_callback,
    128     OnExportedCallback on_exported_calback) {
    129   bus_->AssertOnDBusThread();
    130 
    131   const bool success = ExportMethodAndBlock(interface_name,
    132                                             method_name,
    133                                             method_call_callback);
    134   bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
    135                                         base::Bind(&ExportedObject::OnExported,
    136                                                    this,
    137                                                    on_exported_calback,
    138                                                    interface_name,
    139                                                    method_name,
    140                                                    success));
    141 }
    142 
    143 void ExportedObject::OnExported(OnExportedCallback on_exported_callback,
    144                                 const std::string& interface_name,
    145                                 const std::string& method_name,
    146                                 bool success) {
    147   bus_->AssertOnOriginThread();
    148 
    149   on_exported_callback.Run(interface_name, method_name, success);
    150 }
    151 
    152 void ExportedObject::SendSignalInternal(base::TimeTicks start_time,
    153                                         DBusMessage* signal_message) {
    154   uint32_t serial = 0;
    155   bus_->Send(signal_message, &serial);
    156   dbus_message_unref(signal_message);
    157   // Record time spent to send the the signal. This is not accurate as the
    158   // signal will actually be sent from the next run of the message loop,
    159   // but we can at least tell the number of signals sent.
    160   UMA_HISTOGRAM_TIMES("DBus.SignalSendTime",
    161                       base::TimeTicks::Now() - start_time);
    162 }
    163 
    164 bool ExportedObject::Register() {
    165   bus_->AssertOnDBusThread();
    166 
    167   if (object_is_registered_)
    168     return true;
    169 
    170   ScopedDBusError error;
    171 
    172   DBusObjectPathVTable vtable;
    173   memset(&vtable, 0, sizeof(vtable));
    174   vtable.message_function = &ExportedObject::HandleMessageThunk;
    175   vtable.unregister_function = &ExportedObject::OnUnregisteredThunk;
    176   const bool success = bus_->TryRegisterObjectPath(object_path_,
    177                                                    &vtable,
    178                                                    this,
    179                                                    error.get());
    180   if (!success) {
    181     LOG(ERROR) << "Failed to register the object: " << object_path_.value()
    182                << ": " << (error.is_set() ? error.message() : "");
    183     return false;
    184   }
    185 
    186   object_is_registered_ = true;
    187   return true;
    188 }
    189 
    190 DBusHandlerResult ExportedObject::HandleMessage(
    191     DBusConnection* /* connection */,
    192     DBusMessage* raw_message) {
    193   bus_->AssertOnDBusThread();
    194   DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message));
    195 
    196   // raw_message will be unrefed on exit of the function. Increment the
    197   // reference so we can use it in MethodCall.
    198   dbus_message_ref(raw_message);
    199   scoped_ptr<MethodCall> method_call(
    200       MethodCall::FromRawMessage(raw_message));
    201   const std::string interface = method_call->GetInterface();
    202   const std::string member = method_call->GetMember();
    203 
    204   if (interface.empty()) {
    205     // We don't support method calls without interface.
    206     LOG(WARNING) << "Interface is missing: " << method_call->ToString();
    207     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    208   }
    209 
    210   // Check if we know about the method.
    211   const std::string absolute_method_name = GetAbsoluteMemberName(
    212       interface, member);
    213   MethodTable::const_iterator iter = method_table_.find(absolute_method_name);
    214   if (iter == method_table_.end()) {
    215     // Don't know about the method.
    216     LOG(WARNING) << "Unknown method: " << method_call->ToString();
    217     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
    218   }
    219 
    220   const base::TimeTicks start_time = base::TimeTicks::Now();
    221   if (bus_->HasDBusThread()) {
    222     // Post a task to run the method in the origin thread.
    223     bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
    224                                           base::Bind(&ExportedObject::RunMethod,
    225                                                      this,
    226                                                      iter->second,
    227                                                      base::Passed(&method_call),
    228                                                      start_time));
    229   } else {
    230     // If the D-Bus thread is not used, just call the method directly.
    231     MethodCall* method = method_call.get();
    232     iter->second.Run(method,
    233                      base::Bind(&ExportedObject::SendResponse,
    234                                 this,
    235                                 start_time,
    236                                 base::Passed(&method_call)));
    237   }
    238 
    239   // It's valid to say HANDLED here, and send a method response at a later
    240   // time from OnMethodCompleted() asynchronously.
    241   return DBUS_HANDLER_RESULT_HANDLED;
    242 }
    243 
    244 void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
    245                                scoped_ptr<MethodCall> method_call,
    246                                base::TimeTicks start_time) {
    247   bus_->AssertOnOriginThread();
    248   MethodCall* method = method_call.get();
    249   method_call_callback.Run(method,
    250                            base::Bind(&ExportedObject::SendResponse,
    251                                       this,
    252                                       start_time,
    253                                       base::Passed(&method_call)));
    254 }
    255 
    256 void ExportedObject::SendResponse(base::TimeTicks start_time,
    257                                   scoped_ptr<MethodCall> method_call,
    258                                   scoped_ptr<Response> response) {
    259   DCHECK(method_call);
    260   if (bus_->HasDBusThread()) {
    261     bus_->GetDBusTaskRunner()->PostTask(
    262         FROM_HERE,
    263         base::Bind(&ExportedObject::OnMethodCompleted,
    264                    this,
    265                    base::Passed(&method_call),
    266                    base::Passed(&response),
    267                    start_time));
    268   } else {
    269     OnMethodCompleted(std::move(method_call), std::move(response), start_time);
    270   }
    271 }
    272 
    273 void ExportedObject::OnMethodCompleted(scoped_ptr<MethodCall> method_call,
    274                                        scoped_ptr<Response> response,
    275                                        base::TimeTicks start_time) {
    276   bus_->AssertOnDBusThread();
    277 
    278   // Record if the method call is successful, or not. 1 if successful.
    279   UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
    280                             response ? 1 : 0,
    281                             kSuccessRatioHistogramMaxValue);
    282 
    283   // Check if the bus is still connected. If the method takes long to
    284   // complete, the bus may be shut down meanwhile.
    285   if (!bus_->is_connected())
    286     return;
    287 
    288   if (!response) {
    289     // Something bad happened in the method call.
    290     scoped_ptr<ErrorResponse> error_response(
    291         ErrorResponse::FromMethodCall(
    292             method_call.get(),
    293             DBUS_ERROR_FAILED,
    294             "error occurred in " + method_call->GetMember()));
    295     bus_->Send(error_response->raw_message(), NULL);
    296     return;
    297   }
    298 
    299   // The method call was successful.
    300   bus_->Send(response->raw_message(), NULL);
    301 
    302   // Record time spent to handle the the method call. Don't include failures.
    303   UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
    304                       base::TimeTicks::Now() - start_time);
    305 }
    306 
    307 void ExportedObject::OnUnregistered(DBusConnection* /* connection */) {
    308 }
    309 
    310 DBusHandlerResult ExportedObject::HandleMessageThunk(
    311     DBusConnection* connection,
    312     DBusMessage* raw_message,
    313     void* user_data) {
    314   ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
    315   return self->HandleMessage(connection, raw_message);
    316 }
    317 
    318 void ExportedObject::OnUnregisteredThunk(DBusConnection *connection,
    319                                          void* user_data) {
    320   ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data);
    321   return self->OnUnregistered(connection);
    322 }
    323 
    324 }  // namespace dbus
    325