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_macros.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 23namespace dbus { 24 25namespace { 26 27// Used for success ratio histograms. 1 for success, 0 for failure. 28const int kSuccessRatioHistogramMaxValue = 2; 29 30} // namespace 31 32ExportedObject::ExportedObject(Bus* bus, 33 const ObjectPath& object_path) 34 : bus_(bus), 35 object_path_(object_path), 36 object_is_registered_(false) { 37} 38 39ExportedObject::~ExportedObject() { 40 DCHECK(!object_is_registered_); 41} 42 43bool 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 70void 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 85void 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 114void 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 124void 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 143void 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 152void 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 164bool ExportedObject::Register() { 165 bus_->AssertOnDBusThread(); 166 167 if (object_is_registered_) 168 return true; 169 170 ScopedDBusError error; 171 172 DBusObjectPathVTable vtable = {}; 173 vtable.message_function = &ExportedObject::HandleMessageThunk; 174 vtable.unregister_function = &ExportedObject::OnUnregisteredThunk; 175 const bool success = bus_->TryRegisterObjectPath(object_path_, 176 &vtable, 177 this, 178 error.get()); 179 if (!success) { 180 LOG(ERROR) << "Failed to register the object: " << object_path_.value() 181 << ": " << (error.is_set() ? error.message() : ""); 182 return false; 183 } 184 185 object_is_registered_ = true; 186 return true; 187} 188 189DBusHandlerResult ExportedObject::HandleMessage( 190 DBusConnection* connection, 191 DBusMessage* raw_message) { 192 bus_->AssertOnDBusThread(); 193 DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message)); 194 195 // raw_message will be unrefed on exit of the function. Increment the 196 // reference so we can use it in MethodCall. 197 dbus_message_ref(raw_message); 198 std::unique_ptr<MethodCall> method_call( 199 MethodCall::FromRawMessage(raw_message)); 200 const std::string interface = method_call->GetInterface(); 201 const std::string member = method_call->GetMember(); 202 203 if (interface.empty()) { 204 // We don't support method calls without interface. 205 LOG(WARNING) << "Interface is missing: " << method_call->ToString(); 206 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 207 } 208 209 // Check if we know about the method. 210 const std::string absolute_method_name = GetAbsoluteMemberName( 211 interface, member); 212 MethodTable::const_iterator iter = method_table_.find(absolute_method_name); 213 if (iter == method_table_.end()) { 214 // Don't know about the method. 215 LOG(WARNING) << "Unknown method: " << method_call->ToString(); 216 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 217 } 218 219 const base::TimeTicks start_time = base::TimeTicks::Now(); 220 if (bus_->HasDBusThread()) { 221 // Post a task to run the method in the origin thread. 222 bus_->GetOriginTaskRunner()->PostTask(FROM_HERE, 223 base::Bind(&ExportedObject::RunMethod, 224 this, 225 iter->second, 226 base::Passed(&method_call), 227 start_time)); 228 } else { 229 // If the D-Bus thread is not used, just call the method directly. 230 MethodCall* method = method_call.get(); 231 iter->second.Run(method, 232 base::Bind(&ExportedObject::SendResponse, 233 this, 234 start_time, 235 base::Passed(&method_call))); 236 } 237 238 // It's valid to say HANDLED here, and send a method response at a later 239 // time from OnMethodCompleted() asynchronously. 240 return DBUS_HANDLER_RESULT_HANDLED; 241} 242 243void ExportedObject::RunMethod(MethodCallCallback method_call_callback, 244 std::unique_ptr<MethodCall> method_call, 245 base::TimeTicks start_time) { 246 bus_->AssertOnOriginThread(); 247 MethodCall* method = method_call.get(); 248 method_call_callback.Run(method, 249 base::Bind(&ExportedObject::SendResponse, 250 this, 251 start_time, 252 base::Passed(&method_call))); 253} 254 255void ExportedObject::SendResponse(base::TimeTicks start_time, 256 std::unique_ptr<MethodCall> method_call, 257 std::unique_ptr<Response> response) { 258 DCHECK(method_call); 259 if (bus_->HasDBusThread()) { 260 bus_->GetDBusTaskRunner()->PostTask( 261 FROM_HERE, 262 base::Bind(&ExportedObject::OnMethodCompleted, 263 this, 264 base::Passed(&method_call), 265 base::Passed(&response), 266 start_time)); 267 } else { 268 OnMethodCompleted(std::move(method_call), std::move(response), start_time); 269 } 270} 271 272void ExportedObject::OnMethodCompleted(std::unique_ptr<MethodCall> method_call, 273 std::unique_ptr<Response> response, 274 base::TimeTicks start_time) { 275 bus_->AssertOnDBusThread(); 276 277 // Record if the method call is successful, or not. 1 if successful. 278 UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess", 279 response ? 1 : 0, 280 kSuccessRatioHistogramMaxValue); 281 282 // Check if the bus is still connected. If the method takes long to 283 // complete, the bus may be shut down meanwhile. 284 if (!bus_->is_connected()) 285 return; 286 287 if (!response) { 288 // Something bad happened in the method call. 289 std::unique_ptr<ErrorResponse> error_response(ErrorResponse::FromMethodCall( 290 method_call.get(), DBUS_ERROR_FAILED, 291 "error occurred in " + method_call->GetMember())); 292 bus_->Send(error_response->raw_message(), NULL); 293 return; 294 } 295 296 // The method call was successful. 297 bus_->Send(response->raw_message(), NULL); 298 299 // Record time spent to handle the the method call. Don't include failures. 300 UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime", 301 base::TimeTicks::Now() - start_time); 302} 303 304void ExportedObject::OnUnregistered(DBusConnection* connection) { 305} 306 307DBusHandlerResult ExportedObject::HandleMessageThunk( 308 DBusConnection* connection, 309 DBusMessage* raw_message, 310 void* user_data) { 311 ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data); 312 return self->HandleMessage(connection, raw_message); 313} 314 315void ExportedObject::OnUnregisteredThunk(DBusConnection *connection, 316 void* user_data) { 317 ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data); 318 return self->OnUnregistered(connection); 319} 320 321} // namespace dbus 322