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