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   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 
HandleMessage(DBusConnection *,DBusMessage * raw_message)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 
RunMethod(MethodCallCallback method_call_callback,scoped_ptr<MethodCall> method_call,base::TimeTicks start_time)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 
SendResponse(base::TimeTicks start_time,scoped_ptr<MethodCall> method_call,scoped_ptr<Response> response)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 
OnMethodCompleted(scoped_ptr<MethodCall> method_call,scoped_ptr<Response> response,base::TimeTicks start_time)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 
OnUnregistered(DBusConnection *)307 void ExportedObject::OnUnregistered(DBusConnection* /* connection */) {
308 }
309 
HandleMessageThunk(DBusConnection * connection,DBusMessage * raw_message,void * user_data)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 
OnUnregisteredThunk(DBusConnection * connection,void * user_data)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