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_->PostTaskToDBusThread(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_->PostTaskToDBusThread(FROM_HERE, 103 base::Bind(&ExportedObject::SendSignalInternal, 104 this, 105 start_time, 106 signal_message)); 107 } 108 109 void ExportedObject::Unregister() { 110 bus_->AssertOnDBusThread(); 111 112 if (!object_is_registered_) 113 return; 114 115 bus_->UnregisterObjectPath(object_path_); 116 object_is_registered_ = false; 117 } 118 119 void ExportedObject::ExportMethodInternal( 120 const std::string& interface_name, 121 const std::string& method_name, 122 MethodCallCallback method_call_callback, 123 OnExportedCallback on_exported_calback) { 124 bus_->AssertOnDBusThread(); 125 126 const bool success = ExportMethodAndBlock(interface_name, 127 method_name, 128 method_call_callback); 129 bus_->PostTaskToOriginThread(FROM_HERE, 130 base::Bind(&ExportedObject::OnExported, 131 this, 132 on_exported_calback, 133 interface_name, 134 method_name, 135 success)); 136 } 137 138 void ExportedObject::OnExported(OnExportedCallback on_exported_callback, 139 const std::string& interface_name, 140 const std::string& method_name, 141 bool success) { 142 bus_->AssertOnOriginThread(); 143 144 on_exported_callback.Run(interface_name, method_name, success); 145 } 146 147 void ExportedObject::SendSignalInternal(base::TimeTicks start_time, 148 DBusMessage* signal_message) { 149 uint32 serial = 0; 150 bus_->Send(signal_message, &serial); 151 dbus_message_unref(signal_message); 152 // Record time spent to send the the signal. This is not accurate as the 153 // signal will actually be sent from the next run of the message loop, 154 // but we can at least tell the number of signals sent. 155 UMA_HISTOGRAM_TIMES("DBus.SignalSendTime", 156 base::TimeTicks::Now() - start_time); 157 } 158 159 bool ExportedObject::Register() { 160 bus_->AssertOnDBusThread(); 161 162 if (object_is_registered_) 163 return true; 164 165 ScopedDBusError error; 166 167 DBusObjectPathVTable vtable = {}; 168 vtable.message_function = &ExportedObject::HandleMessageThunk; 169 vtable.unregister_function = &ExportedObject::OnUnregisteredThunk; 170 const bool success = bus_->TryRegisterObjectPath(object_path_, 171 &vtable, 172 this, 173 error.get()); 174 if (!success) { 175 LOG(ERROR) << "Failed to register the object: " << object_path_.value() 176 << ": " << (error.is_set() ? error.message() : ""); 177 return false; 178 } 179 180 object_is_registered_ = true; 181 return true; 182 } 183 184 DBusHandlerResult ExportedObject::HandleMessage( 185 DBusConnection* connection, 186 DBusMessage* raw_message) { 187 bus_->AssertOnDBusThread(); 188 DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message)); 189 190 // raw_message will be unrefed on exit of the function. Increment the 191 // reference so we can use it in MethodCall. 192 dbus_message_ref(raw_message); 193 scoped_ptr<MethodCall> method_call( 194 MethodCall::FromRawMessage(raw_message)); 195 const std::string interface = method_call->GetInterface(); 196 const std::string member = method_call->GetMember(); 197 198 if (interface.empty()) { 199 // We don't support method calls without interface. 200 LOG(WARNING) << "Interface is missing: " << method_call->ToString(); 201 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 202 } 203 204 // Check if we know about the method. 205 const std::string absolute_method_name = GetAbsoluteMethodName( 206 interface, member); 207 MethodTable::const_iterator iter = method_table_.find(absolute_method_name); 208 if (iter == method_table_.end()) { 209 // Don't know about the method. 210 LOG(WARNING) << "Unknown method: " << method_call->ToString(); 211 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; 212 } 213 214 const base::TimeTicks start_time = base::TimeTicks::Now(); 215 if (bus_->HasDBusThread()) { 216 // Post a task to run the method in the origin thread. 217 bus_->PostTaskToOriginThread(FROM_HERE, 218 base::Bind(&ExportedObject::RunMethod, 219 this, 220 iter->second, 221 base::Passed(&method_call), 222 start_time)); 223 } else { 224 // If the D-Bus thread is not used, just call the method directly. 225 MethodCall* method = method_call.get(); 226 iter->second.Run(method, 227 base::Bind(&ExportedObject::SendResponse, 228 this, 229 start_time, 230 base::Passed(&method_call))); 231 } 232 233 // It's valid to say HANDLED here, and send a method response at a later 234 // time from OnMethodCompleted() asynchronously. 235 return DBUS_HANDLER_RESULT_HANDLED; 236 } 237 238 void ExportedObject::RunMethod(MethodCallCallback method_call_callback, 239 scoped_ptr<MethodCall> method_call, 240 base::TimeTicks start_time) { 241 bus_->AssertOnOriginThread(); 242 MethodCall* method = method_call.get(); 243 method_call_callback.Run(method, 244 base::Bind(&ExportedObject::SendResponse, 245 this, 246 start_time, 247 base::Passed(&method_call))); 248 } 249 250 void ExportedObject::SendResponse(base::TimeTicks start_time, 251 scoped_ptr<MethodCall> method_call, 252 scoped_ptr<Response> response) { 253 DCHECK(method_call); 254 if (bus_->HasDBusThread()) { 255 bus_->PostTaskToDBusThread(FROM_HERE, 256 base::Bind(&ExportedObject::OnMethodCompleted, 257 this, 258 base::Passed(&method_call), 259 base::Passed(&response), 260 start_time)); 261 } else { 262 OnMethodCompleted(method_call.Pass(), response.Pass(), start_time); 263 } 264 } 265 266 void ExportedObject::OnMethodCompleted(scoped_ptr<MethodCall> method_call, 267 scoped_ptr<Response> response, 268 base::TimeTicks start_time) { 269 bus_->AssertOnDBusThread(); 270 271 // Record if the method call is successful, or not. 1 if successful. 272 UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess", 273 response ? 1 : 0, 274 kSuccessRatioHistogramMaxValue); 275 276 // Check if the bus is still connected. If the method takes long to 277 // complete, the bus may be shut down meanwhile. 278 if (!bus_->is_connected()) 279 return; 280 281 if (!response) { 282 // Something bad happened in the method call. 283 scoped_ptr<ErrorResponse> error_response( 284 ErrorResponse::FromMethodCall( 285 method_call.get(), 286 DBUS_ERROR_FAILED, 287 "error occurred in " + method_call->GetMember())); 288 bus_->Send(error_response->raw_message(), NULL); 289 return; 290 } 291 292 // The method call was successful. 293 bus_->Send(response->raw_message(), NULL); 294 295 // Record time spent to handle the the method call. Don't include failures. 296 UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime", 297 base::TimeTicks::Now() - start_time); 298 } 299 300 void ExportedObject::OnUnregistered(DBusConnection* connection) { 301 } 302 303 DBusHandlerResult ExportedObject::HandleMessageThunk( 304 DBusConnection* connection, 305 DBusMessage* raw_message, 306 void* user_data) { 307 ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data); 308 return self->HandleMessage(connection, raw_message); 309 } 310 311 void ExportedObject::OnUnregisteredThunk(DBusConnection *connection, 312 void* user_data) { 313 ExportedObject* self = reinterpret_cast<ExportedObject*>(user_data); 314 return self->OnUnregistered(connection); 315 } 316 317 } // namespace dbus 318