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
ExportedObject(Bus * bus,const ObjectPath & object_path)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
~ExportedObject()39 ExportedObject::~ExportedObject() {
40 DCHECK(!object_is_registered_);
41 }
42
ExportMethodAndBlock(const std::string & interface_name,const std::string & method_name,MethodCallCallback method_call_callback)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
ExportMethod(const std::string & interface_name,const std::string & method_name,MethodCallCallback method_call_callback,OnExportedCallback on_exported_calback)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
SendSignal(Signal * signal)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
Unregister()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
ExportMethodInternal(const std::string & interface_name,const std::string & method_name,MethodCallCallback method_call_callback,OnExportedCallback on_exported_calback)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
OnExported(OnExportedCallback on_exported_callback,const std::string & interface_name,const std::string & method_name,bool success)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
SendSignalInternal(base::TimeTicks start_time,DBusMessage * signal_message)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
Register()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 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
HandleMessage(DBusConnection *,DBusMessage * raw_message)189 DBusHandlerResult ExportedObject::HandleMessage(DBusConnection*,
190 DBusMessage* raw_message) {
191 bus_->AssertOnDBusThread();
192 DCHECK_EQ(DBUS_MESSAGE_TYPE_METHOD_CALL, dbus_message_get_type(raw_message));
193
194 // raw_message will be unrefed on exit of the function. Increment the
195 // reference so we can use it in MethodCall.
196 dbus_message_ref(raw_message);
197 std::unique_ptr<MethodCall> method_call(
198 MethodCall::FromRawMessage(raw_message));
199 const std::string interface = method_call->GetInterface();
200 const std::string member = method_call->GetMember();
201
202 if (interface.empty()) {
203 // We don't support method calls without interface.
204 LOG(WARNING) << "Interface is missing: " << method_call->ToString();
205 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
206 }
207
208 // Check if we know about the method.
209 const std::string absolute_method_name = GetAbsoluteMemberName(
210 interface, member);
211 MethodTable::const_iterator iter = method_table_.find(absolute_method_name);
212 if (iter == method_table_.end()) {
213 // Don't know about the method.
214 LOG(WARNING) << "Unknown method: " << method_call->ToString();
215 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
216 }
217
218 const base::TimeTicks start_time = base::TimeTicks::Now();
219 if (bus_->HasDBusThread()) {
220 // Post a task to run the method in the origin thread.
221 bus_->GetOriginTaskRunner()->PostTask(FROM_HERE,
222 base::Bind(&ExportedObject::RunMethod,
223 this,
224 iter->second,
225 base::Passed(&method_call),
226 start_time));
227 } else {
228 // If the D-Bus thread is not used, just call the method directly.
229 MethodCall* method = method_call.get();
230 iter->second.Run(method,
231 base::Bind(&ExportedObject::SendResponse,
232 this,
233 start_time,
234 base::Passed(&method_call)));
235 }
236
237 // It's valid to say HANDLED here, and send a method response at a later
238 // time from OnMethodCompleted() asynchronously.
239 return DBUS_HANDLER_RESULT_HANDLED;
240 }
241
RunMethod(MethodCallCallback method_call_callback,std::unique_ptr<MethodCall> method_call,base::TimeTicks start_time)242 void ExportedObject::RunMethod(MethodCallCallback method_call_callback,
243 std::unique_ptr<MethodCall> method_call,
244 base::TimeTicks start_time) {
245 bus_->AssertOnOriginThread();
246 MethodCall* method = method_call.get();
247 method_call_callback.Run(method,
248 base::Bind(&ExportedObject::SendResponse,
249 this,
250 start_time,
251 base::Passed(&method_call)));
252 }
253
SendResponse(base::TimeTicks start_time,std::unique_ptr<MethodCall> method_call,std::unique_ptr<Response> response)254 void ExportedObject::SendResponse(base::TimeTicks start_time,
255 std::unique_ptr<MethodCall> method_call,
256 std::unique_ptr<Response> response) {
257 DCHECK(method_call);
258 if (bus_->HasDBusThread()) {
259 bus_->GetDBusTaskRunner()->PostTask(
260 FROM_HERE,
261 base::Bind(&ExportedObject::OnMethodCompleted,
262 this,
263 base::Passed(&method_call),
264 base::Passed(&response),
265 start_time));
266 } else {
267 OnMethodCompleted(std::move(method_call), std::move(response), start_time);
268 }
269 }
270
OnMethodCompleted(std::unique_ptr<MethodCall> method_call,std::unique_ptr<Response> response,base::TimeTicks start_time)271 void ExportedObject::OnMethodCompleted(std::unique_ptr<MethodCall> method_call,
272 std::unique_ptr<Response> response,
273 base::TimeTicks start_time) {
274 bus_->AssertOnDBusThread();
275
276 // Record if the method call is successful, or not. 1 if successful.
277 UMA_HISTOGRAM_ENUMERATION("DBus.ExportedMethodHandleSuccess",
278 response ? 1 : 0,
279 kSuccessRatioHistogramMaxValue);
280
281 // Check if the bus is still connected. If the method takes long to
282 // complete, the bus may be shut down meanwhile.
283 if (!bus_->is_connected())
284 return;
285
286 if (!response) {
287 // Something bad happened in the method call.
288 std::unique_ptr<ErrorResponse> error_response(ErrorResponse::FromMethodCall(
289 method_call.get(), DBUS_ERROR_FAILED,
290 "error occurred in " + method_call->GetMember()));
291 bus_->Send(error_response->raw_message(), NULL);
292 return;
293 }
294
295 // The method call was successful.
296 bus_->Send(response->raw_message(), NULL);
297
298 // Record time spent to handle the the method call. Don't include failures.
299 UMA_HISTOGRAM_TIMES("DBus.ExportedMethodHandleTime",
300 base::TimeTicks::Now() - start_time);
301 }
302
OnUnregistered(DBusConnection *)303 void ExportedObject::OnUnregistered(DBusConnection*) {}
304
HandleMessageThunk(DBusConnection * connection,DBusMessage * raw_message,void * user_data)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
OnUnregisteredThunk(DBusConnection * connection,void * user_data)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