1// Copyright 2014 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 "mojo/shell/dbus_application_loader_linux.h"
6
7#include <string>
8
9#include "base/command_line.h"
10#include "base/location.h"
11#include "base/logging.h"
12#include "base/task_runner_util.h"
13#include "base/threading/thread_restrictions.h"
14#include "dbus/bus.h"
15#include "dbus/file_descriptor.h"
16#include "dbus/message.h"
17#include "dbus/object_path.h"
18#include "dbus/object_proxy.h"
19#include "mojo/dbus/dbus_external_service.h"
20#include "mojo/embedder/channel_init.h"
21#include "mojo/embedder/platform_channel_pair.h"
22#include "mojo/shell/context.h"
23#include "mojo/shell/external_service.mojom.h"
24
25namespace mojo {
26namespace shell {
27
28// Manages the connection to a single externally-running service.
29class DBusApplicationLoader::LoadContext {
30 public:
31  // Kicks off the attempt to bootstrap a connection to the externally-running
32  // service specified by url_.
33  // Creates a MessagePipe and passes one end over DBus to the service. Then,
34  // calls ExternalService::Activate(ShellHandle) over the now-shared pipe.
35  LoadContext(DBusApplicationLoader* loader,
36              const scoped_refptr<dbus::Bus>& bus,
37              const GURL& url,
38              ScopedMessagePipeHandle service_provider_handle)
39      : loader_(loader),
40        bus_(bus),
41        service_dbus_proxy_(NULL),
42        url_(url),
43        service_provider_handle_(service_provider_handle.Pass()) {
44    base::PostTaskAndReplyWithResult(
45        loader_->context_->task_runners()->io_runner(),
46        FROM_HERE,
47        base::Bind(&LoadContext::CreateChannelOnIOThread,
48                   base::Unretained(this)),
49        base::Bind(&LoadContext::ConnectChannel, base::Unretained(this)));
50  }
51
52  virtual ~LoadContext() {}
53
54 private:
55  // Sets up a pipe to share with the externally-running service and returns
56  // the endpoint that should be sent over DBus.
57  // The FD for the endpoint must be validated on an IO thread.
58  scoped_ptr<dbus::FileDescriptor> CreateChannelOnIOThread() {
59    base::ThreadRestrictions::AssertIOAllowed();
60    CHECK(bus_->Connect());
61    CHECK(bus_->SetUpAsyncOperations());
62
63    embedder::PlatformChannelPair channel_pair;
64    channel_init_.reset(new embedder::ChannelInit);
65    mojo::ScopedMessagePipeHandle bootstrap_message_pipe =
66        channel_init_->Init(channel_pair.PassServerHandle().release().fd,
67                            loader_->context_->task_runners()->io_runner());
68    CHECK(bootstrap_message_pipe.is_valid());
69
70    external_service_.Bind(bootstrap_message_pipe.Pass());
71
72    scoped_ptr<dbus::FileDescriptor> client_fd(new dbus::FileDescriptor);
73    client_fd->PutValue(channel_pair.PassClientHandle().release().fd);
74    client_fd->CheckValidity();  // Must be run on an IO thread.
75    return client_fd.Pass();
76  }
77
78  // Sends client_fd over to the externally-running service. If that
79  // attempt is successful, the service will then be "activated" by
80  // sending it a ShellHandle.
81  void ConnectChannel(scoped_ptr<dbus::FileDescriptor> client_fd) {
82    size_t first_slash = url_.path().find_first_of('/');
83    DCHECK_NE(first_slash, std::string::npos);
84
85    const std::string service_name = url_.path().substr(0, first_slash);
86    const std::string object_path = url_.path().substr(first_slash);
87    service_dbus_proxy_ =
88        bus_->GetObjectProxy(service_name, dbus::ObjectPath(object_path));
89
90    dbus::MethodCall call(kMojoDBusInterface, kMojoDBusConnectMethod);
91    dbus::MessageWriter writer(&call);
92    writer.AppendFileDescriptor(*client_fd.get());
93
94    // TODO(cmasone): handle errors!
95    service_dbus_proxy_->CallMethod(
96        &call,
97        dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
98        base::Bind(&LoadContext::ActivateService, base::Unretained(this)));
99  }
100
101  // Sends a ShellHandle over to the now-connected externally-running service,
102  // using the Mojo ExternalService API.
103  void ActivateService(dbus::Response* response) {
104    external_service_->Activate(mojo::ScopedMessagePipeHandle(
105        mojo::MessagePipeHandle(service_provider_handle_.release().value())));
106  }
107
108  // Should the ExternalService disappear completely, destroy connection state.
109  // NB: This triggers off of the service disappearing from
110  // DBus. Perhaps there's a way to watch at the Mojo layer instead,
111  // and that would be superior?
112  void HandleNameOwnerChanged(const std::string& old_owner,
113                              const std::string& new_owner) {
114    DCHECK(loader_->context_->task_runners()
115               ->shell_runner()
116               ->BelongsToCurrentThread());
117
118    if (new_owner.empty()) {
119      loader_->context_->task_runners()->shell_runner()->PostTask(
120          FROM_HERE,
121          base::Bind(&DBusApplicationLoader::ForgetService,
122                     base::Unretained(loader_),
123                     url_));
124    }
125  }
126
127  DBusApplicationLoader* const loader_;
128  scoped_refptr<dbus::Bus> bus_;
129  dbus::ObjectProxy* service_dbus_proxy_;  // Owned by bus_;
130  const GURL url_;
131  ScopedMessagePipeHandle service_provider_handle_;
132  scoped_ptr<embedder::ChannelInit> channel_init_;
133  ExternalServicePtr external_service_;
134
135  DISALLOW_COPY_AND_ASSIGN(LoadContext);
136};
137
138DBusApplicationLoader::DBusApplicationLoader(Context* context)
139    : context_(context) {
140  dbus::Bus::Options options;
141  options.bus_type = dbus::Bus::SESSION;
142  options.dbus_task_runner = context_->task_runners()->io_runner();
143  bus_ = new dbus::Bus(options);
144}
145
146DBusApplicationLoader::~DBusApplicationLoader() {
147  DCHECK(url_to_load_context_.empty());
148}
149
150void DBusApplicationLoader::Load(ApplicationManager* manager,
151                                 const GURL& url,
152                                 scoped_refptr<LoadCallbacks> callbacks) {
153  // TODO(aa): This could be delayed until later, when we know that loading is
154  // going to succeed.
155  ScopedMessagePipeHandle shell_handle = callbacks->RegisterApplication();
156  if (!shell_handle.is_valid())
157    return;
158
159  DCHECK(url.SchemeIs("dbus"));
160  DCHECK(url_to_load_context_.find(url) == url_to_load_context_.end());
161  url_to_load_context_[url] =
162      new LoadContext(this, bus_, url, shell_handle.Pass());
163}
164
165void DBusApplicationLoader::OnApplicationError(ApplicationManager* manager,
166                                               const GURL& url) {
167  // TODO(cmasone): Anything at all in this method here.
168}
169
170void DBusApplicationLoader::ForgetService(const GURL& url) {
171  DCHECK(context_->task_runners()->shell_runner()->BelongsToCurrentThread());
172  DVLOG(2) << "Forgetting service (url: " << url << ")";
173
174  LoadContextMap::iterator it = url_to_load_context_.find(url);
175  DCHECK(it != url_to_load_context_.end()) << url;
176
177  LoadContext* doomed = it->second;
178  url_to_load_context_.erase(it);
179
180  delete doomed;
181}
182
183}  // namespace shell
184}  // namespace mojo
185