Commit d500c98b authored by cmasone's avatar cmasone Committed by Commit bot

Accept inbound connections on unix domain socket

In order for CrOS Core services to speak Mojo, the mojo_shell will need to be able to accept incoming connections from externally-managed processes.

POR is to have the shell listen on a unix domain socket and convert incoming connections to a proper Mojo MessagePipe and then wire it to an InterfaceImpl<ExternalApplicationRegistrar>, which will make the appropriate calls on ApplicationManager.

BUG=418289
TEST=mojo_external_application_tests
STATUS=Fixed

Review URL: https://codereview.chromium.org/522443003

Cr-Commit-Position: refs/heads/master@{#297184}
parent bb78b675
...@@ -48,6 +48,7 @@ group("tests") { ...@@ -48,6 +48,7 @@ group("tests") {
"//mojo/public/js/bindings/tests:mojo_js_unittests", "//mojo/public/js/bindings/tests:mojo_js_unittests",
"//mojo/services/clipboard:mojo_clipboard_unittests", "//mojo/services/clipboard:mojo_clipboard_unittests",
"//mojo/services/public/cpp/surfaces/tests:mojo_surfaces_lib_unittests", "//mojo/services/public/cpp/surfaces/tests:mojo_surfaces_lib_unittests",
"//mojo/shell:mojo_external_application_tests",
"//mojo/shell:mojo_shell_tests", "//mojo/shell:mojo_shell_tests",
"//mojo/system:mojo_message_pipe_perftests", "//mojo/system:mojo_message_pipe_perftests",
"//mojo/system:mojo_system_unittests", "//mojo/system:mojo_system_unittests",
......
...@@ -206,6 +206,13 @@ void ApplicationManager::ConnectToClient(ShellImpl* shell_impl, ...@@ -206,6 +206,13 @@ void ApplicationManager::ConnectToClient(ShellImpl* shell_impl,
} }
} }
void ApplicationManager::RegisterExternalApplication(
const GURL& url,
ScopedMessagePipeHandle shell_handle) {
url_to_shell_impl_[url] =
WeakBindToPipe(new ShellImpl(this, url), shell_handle.Pass());
}
void ApplicationManager::RegisterLoadedApplication( void ApplicationManager::RegisterLoadedApplication(
const GURL& url, const GURL& url,
const GURL& requestor_url, const GURL& requestor_url,
......
...@@ -23,7 +23,7 @@ class MOJO_APPLICATION_MANAGER_EXPORT ApplicationManager { ...@@ -23,7 +23,7 @@ class MOJO_APPLICATION_MANAGER_EXPORT ApplicationManager {
class MOJO_APPLICATION_MANAGER_EXPORT Delegate { class MOJO_APPLICATION_MANAGER_EXPORT Delegate {
public: public:
virtual ~Delegate(); virtual ~Delegate();
// Send when the Applicaiton holding the handle on the other end of the // Send when the Application holding the handle on the other end of the
// Shell pipe goes away. // Shell pipe goes away.
virtual void OnApplicationError(const GURL& url) = 0; virtual void OnApplicationError(const GURL& url) = 0;
}; };
...@@ -80,6 +80,9 @@ class MOJO_APPLICATION_MANAGER_EXPORT ApplicationManager { ...@@ -80,6 +80,9 @@ class MOJO_APPLICATION_MANAGER_EXPORT ApplicationManager {
const GURL& application_url, const GURL& application_url,
const std::string& interface_name); const std::string& interface_name);
void RegisterExternalApplication(const GURL& application_url,
ScopedMessagePipeHandle shell);
void set_delegate(Delegate* delegate) { delegate_ = delegate; } void set_delegate(Delegate* delegate) { delegate_ = delegate; }
// Sets the default Loader to be used if not overridden by SetLoaderForURL() // Sets the default Loader to be used if not overridden by SetLoaderForURL()
......
...@@ -87,6 +87,7 @@ ...@@ -87,6 +87,7 @@
'dependencies': [ 'dependencies': [
'mojo_dbus_echo', 'mojo_dbus_echo',
'mojo_dbus_echo_service', 'mojo_dbus_echo_service',
'mojo_external_application_tests',
], ],
}], }],
['component != "shared_library" and OS == "linux"', { ['component != "shared_library" and OS == "linux"', {
...@@ -176,6 +177,12 @@ ...@@ -176,6 +177,12 @@
'shell/dynamic_application_loader.cc', 'shell/dynamic_application_loader.cc',
'shell/dynamic_application_loader.h', 'shell/dynamic_application_loader.h',
'shell/dynamic_service_runner.h', 'shell/dynamic_service_runner.h',
'shell/external_application_listener_posix.cc',
'shell/external_application_listener_win.cc',
'shell/external_application_listener.h',
'shell/external_application_registrar.mojom',
'shell/incoming_connection_listener_posix.cc',
'shell/incoming_connection_listener_posix.h',
'shell/init.cc', 'shell/init.cc',
'shell/init.h', 'shell/init.h',
'shell/in_process_dynamic_service_runner.cc', 'shell/in_process_dynamic_service_runner.cc',
...@@ -204,6 +211,10 @@ ...@@ -204,6 +211,10 @@
'../build/linux/system.gyp:dbus', '../build/linux/system.gyp:dbus',
'../dbus/dbus.gyp:dbus', '../dbus/dbus.gyp:dbus',
], ],
'sources': [
'shell/external_application_registrar_connection.cc',
'shell/external_application_registrar_connection.h',
],
}], }],
['OS=="android"', { ['OS=="android"', {
'dependencies': [ 'dependencies': [
...@@ -323,7 +334,7 @@ ...@@ -323,7 +334,7 @@
], ],
}, },
{ {
# GN version: //mojo/application_manager:unittests # GN version: //mojo/application_manager:mojo_application_manager_unittests
'target_name': 'mojo_application_manager_unittests', 'target_name': 'mojo_application_manager_unittests',
'type': 'executable', 'type': 'executable',
'dependencies': [ 'dependencies': [
...@@ -446,6 +457,28 @@ ...@@ -446,6 +457,28 @@
'dbus/dbus_external_service.cc', 'dbus/dbus_external_service.cc',
], ],
}, },
{
# GN version: //mojo/shell:mojo_external_application_tests
'target_name': 'mojo_external_application_tests',
'type': '<(gtest_target_type)',
'dependencies': [
'../base/base.gyp:base',
'../base/base.gyp:test_support_base',
'../testing/gtest.gyp:gtest',
'../net/net.gyp:net_test_support',
'../url/url.gyp:url_lib',
'mojo_application_manager',
'mojo_base.gyp:mojo_common_lib',
'mojo_base.gyp:mojo_environment_chromium',
'mojo_base.gyp:mojo_system_impl',
'mojo_shell_lib',
],
'sources': [
'shell/incoming_connection_listener_unittest.cc',
'shell/external_application_listener_unittest.cc',
'shell/external_application_test_main.cc',
],
},
], ],
}], }],
['use_aura==1', { ['use_aura==1', {
......
...@@ -27,6 +27,7 @@ executable("mojo_shell") { ...@@ -27,6 +27,7 @@ executable("mojo_shell") {
source_set("lib") { source_set("lib") {
deps = [ deps = [
":app_child_process_bindings", ":app_child_process_bindings",
":external_application_registrar_bindings",
":external_service_bindings", ":external_service_bindings",
"//base", "//base",
"//base/third_party/dynamic_annotations", "//base/third_party/dynamic_annotations",
...@@ -57,6 +58,11 @@ source_set("lib") { ...@@ -57,6 +58,11 @@ source_set("lib") {
"dynamic_application_loader.cc", "dynamic_application_loader.cc",
"dynamic_application_loader.h", "dynamic_application_loader.h",
"dynamic_service_runner.h", "dynamic_service_runner.h",
"external_application_listener_posix.cc",
"external_application_listener_win.cc",
"external_application_listener.h",
"incoming_connection_listener_posix.cc",
"incoming_connection_listener_posix.h",
"init.cc", "init.cc",
"init.h", "init.h",
"in_process_dynamic_service_runner.cc", "in_process_dynamic_service_runner.cc",
...@@ -101,6 +107,25 @@ mojom("external_service_bindings") { ...@@ -101,6 +107,25 @@ mojom("external_service_bindings") {
] ]
} }
mojom("external_application_registrar_bindings") {
sources = [
"external_application_registrar.mojom"
]
}
source_set("external_application_registrar_connection") {
sources = [
"external_application_registrar_connection.cc",
"external_application_registrar_connection.h",
]
deps = [
":external_application_registrar_bindings",
"//net"
]
}
# GYP version: mojo/mojo.gyp:mojo_shell_tests
test("mojo_shell_tests") { test("mojo_shell_tests") {
deps = [ deps = [
":lib", ":lib",
...@@ -153,3 +178,26 @@ source_set("test_support") { ...@@ -153,3 +178,26 @@ source_set("test_support") {
"//mojo/system", "//mojo/system",
] ]
} }
# GYP version: mojo/mojo.gyp:external_application_tests
test("mojo_external_application_tests") {
deps = [
":lib",
":external_application_registrar_connection",
"//base",
"//base/test:test_support",
"//testing/gtest",
"//net:test_support",
"//url",
"//mojo/application_manager",
"//mojo/common",
"//mojo/environment:chromium",
"//mojo/system",
]
sources = [
"incoming_connection_listener_unittest.cc",
"external_application_listener_unittest.cc",
"external_application_test_main.cc",
]
}
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
#include <vector> #include <vector>
#include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/memory/scoped_ptr.h" #include "base/memory/scoped_ptr.h"
#include "base/memory/scoped_vector.h" #include "base/memory/scoped_vector.h"
...@@ -22,6 +24,7 @@ ...@@ -22,6 +24,7 @@
#include "mojo/public/cpp/application/application_delegate.h" #include "mojo/public/cpp/application/application_delegate.h"
#include "mojo/public/cpp/application/application_impl.h" #include "mojo/public/cpp/application/application_impl.h"
#include "mojo/shell/dynamic_application_loader.h" #include "mojo/shell/dynamic_application_loader.h"
#include "mojo/shell/external_application_listener.h"
#include "mojo/shell/in_process_dynamic_service_runner.h" #include "mojo/shell/in_process_dynamic_service_runner.h"
#include "mojo/shell/out_of_process_dynamic_service_runner.h" #include "mojo/shell/out_of_process_dynamic_service_runner.h"
#include "mojo/shell/switches.h" #include "mojo/shell/switches.h"
...@@ -178,6 +181,15 @@ void Context::Init() { ...@@ -178,6 +181,15 @@ void Context::Init() {
mojo_url_resolver_.AddLocalFileMapping(GURL(kLocalMojoURLs[i])); mojo_url_resolver_.AddLocalFileMapping(GURL(kLocalMojoURLs[i]));
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kEnableExternalApplications)) {
listener_ = ExternalApplicationListener::Create(
task_runners_->shell_runner(), task_runners_->io_runner());
listener_->ListenInBackground(
ExternalApplicationListener::ConstructDefaultSocketPath(),
base::Bind(&ApplicationManager::RegisterExternalApplication,
base::Unretained(&application_manager_)));
}
scoped_ptr<DynamicServiceRunnerFactory> runner_factory; scoped_ptr<DynamicServiceRunnerFactory> runner_factory;
if (command_line->HasSwitch(switches::kEnableMultiprocess)) if (command_line->HasSwitch(switches::kEnableMultiprocess))
runner_factory.reset(new OutOfProcessDynamicServiceRunnerFactory()); runner_factory.reset(new OutOfProcessDynamicServiceRunnerFactory());
...@@ -225,6 +237,9 @@ void Context::Init() { ...@@ -225,6 +237,9 @@ void Context::Init() {
GURL("mojo:mojo_network_service")); GURL("mojo:mojo_network_service"));
} }
#endif #endif
if (listener_)
listener_->WaitForListening();
} }
void Context::OnApplicationError(const GURL& gurl) { void Context::OnApplicationError(const GURL& gurl) {
......
...@@ -22,6 +22,7 @@ class Spy; ...@@ -22,6 +22,7 @@ class Spy;
namespace shell { namespace shell {
class DynamicApplicationLoader; class DynamicApplicationLoader;
class ExternalApplicationListener;
// The "global" context for the shell's main process. // The "global" context for the shell's main process.
class Context : ApplicationManager::Delegate { class Context : ApplicationManager::Delegate {
...@@ -53,6 +54,7 @@ class Context : ApplicationManager::Delegate { ...@@ -53,6 +54,7 @@ class Context : ApplicationManager::Delegate {
std::set<GURL> app_urls_; std::set<GURL> app_urls_;
scoped_ptr<TaskRunners> task_runners_; scoped_ptr<TaskRunners> task_runners_;
scoped_ptr<ExternalApplicationListener> listener_;
ApplicationManager application_manager_; ApplicationManager application_manager_;
MojoURLResolver mojo_url_resolver_; MojoURLResolver mojo_url_resolver_;
scoped_ptr<Spy> spy_; scoped_ptr<Spy> spy_;
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_H_
#define MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_H_
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/sequenced_task_runner.h"
#include "mojo/public/cpp/system/message_pipe.h"
#include "net/socket/socket_descriptor.h"
#include "url/gurl.h"
namespace mojo {
namespace shell {
// In order to support Mojo apps whose lifetime is managed by
// something other than mojo_shell, mojo_shell needs to support a
// mechanism by which such an application can discover a running shell
// instance, connect to it, and ask to be "registered" at a given
// URL. Registration implies that the app can be connected to at that
// URL from then on out, and that the app has received a usable ShellPtr.
//
// External applications can connect to the shell using the
// ExternalApplicationRegistrarConnection class.
class ExternalApplicationListener {
public:
// When run, a RegisterCallback should note that an app has asked to be
// registered at app_url and Bind the provided pipe handle to a ShellImpl.
typedef base::Callback<void(const GURL& app_url,
ScopedMessagePipeHandle shell)> RegisterCallback;
typedef base::Callback<void(int rv)> ErrorCallback;
virtual ~ExternalApplicationListener() {}
// Implementations of this class may use two threads, an IO thread for
// listening and accepting incoming sockets, and a "main" thread
// where all Mojo traffic is processed and provided callbacks are run.
static scoped_ptr<ExternalApplicationListener> Create(
const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
const scoped_refptr<base::SequencedTaskRunner>& io_runner);
static base::FilePath ConstructDefaultSocketPath();
// Begin listening (on io_runner) to a socket at listen_socket_path.
// Incoming registration requests will be forwarded to register_callback.
// Errors are ignored.
virtual void ListenInBackground(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback) = 0;
// Begin listening (on io_runner) to a socket at listen_socket_path.
// Incoming registration requests will be forwarded to register_callback.
// Errors are reported via error_callback.
virtual void ListenInBackgroundWithErrorCallback(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback,
const ErrorCallback& error_callback) = 0;
// Block the current thread until listening has started on io_runner.
// If listening has already started, returns immediately.
virtual void WaitForListening() = 0;
};
} // namespace shell
} // namespace mojo
#endif // MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/shell/external_application_listener_posix.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_checker.h"
#include "base/tracked_objects.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/embedder/channel_init.h"
#include "mojo/public/cpp/bindings/error_handler.h"
#include "mojo/shell/external_application_registrar.mojom.h"
#include "mojo/shell/incoming_connection_listener_posix.h"
#include "net/base/net_errors.h"
#include "net/socket/socket_descriptor.h"
#include "url/gurl.h"
namespace mojo {
namespace shell {
namespace {
const char kDefaultListenSocketPath[] = "/var/run/mojo/system_socket";
} // namespace
// static
base::FilePath ExternalApplicationListener::ConstructDefaultSocketPath() {
return base::FilePath(FILE_PATH_LITERAL(kDefaultListenSocketPath));
}
// static
scoped_ptr<ExternalApplicationListener> ExternalApplicationListener::Create(
const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
const scoped_refptr<base::SequencedTaskRunner>& io_runner) {
return make_scoped_ptr(
new ExternalApplicationListenerPosix(shell_runner, io_runner));
}
class ExternalApplicationListenerPosix::RegistrarImpl
: public InterfaceImpl<ExternalApplicationRegistrar> {
public:
explicit RegistrarImpl(const RegisterCallback& callback);
virtual ~RegistrarImpl() MOJO_OVERRIDE;
virtual void OnConnectionError() MOJO_OVERRIDE;
embedder::ChannelInit channel_init;
private:
virtual void Register(const String& app_url,
InterfaceRequest<Shell> shell,
const mojo::Closure& callback) MOJO_OVERRIDE;
const RegisterCallback register_callback_;
};
ExternalApplicationListenerPosix::ExternalApplicationListenerPosix(
const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
const scoped_refptr<base::SequencedTaskRunner>& io_runner)
: shell_runner_(shell_runner),
io_runner_(io_runner),
signal_on_listening_(true, false),
weak_ptr_factory_(this) {
DCHECK(shell_runner_.get() && shell_runner_->RunsTasksOnCurrentThread());
DCHECK(io_runner_.get());
listener_thread_checker_.DetachFromThread(); // Will attach in StartListener.
}
ExternalApplicationListenerPosix::~ExternalApplicationListenerPosix() {
DCHECK(register_thread_checker_.CalledOnValidThread());
weak_ptr_factory_.InvalidateWeakPtrs();
// listener_ needs to be destroyed on io_runner_, and it has to die before
// this object does, as it holds a pointer back to this instance.
base::WaitableEvent stop_listening_event(true, false);
io_runner_->PostTask(
FROM_HERE,
base::Bind(&ExternalApplicationListenerPosix::StopListening,
base::Unretained(this),
&stop_listening_event));
stop_listening_event.Wait();
}
void ExternalApplicationListenerPosix::ListenInBackground(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback) {
DCHECK(register_thread_checker_.CalledOnValidThread());
ListenInBackgroundWithErrorCallback(
listen_socket_path, register_callback, ErrorCallback());
}
void ExternalApplicationListenerPosix::ListenInBackgroundWithErrorCallback(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback,
const ErrorCallback& error_callback) {
DCHECK(register_thread_checker_.CalledOnValidThread());
register_callback_ = register_callback;
error_callback_ = error_callback;
io_runner_->PostTask(
FROM_HERE,
base::Bind(&ExternalApplicationListenerPosix::StartListening,
base::Unretained(this),
listen_socket_path));
}
void ExternalApplicationListenerPosix::WaitForListening() {
DCHECK(register_thread_checker_.CalledOnValidThread());
signal_on_listening_.Wait();
}
void ExternalApplicationListenerPosix::StartListening(
const base::FilePath& listen_socket_path) {
CHECK_EQ(base::MessageLoop::current()->type(), base::MessageLoop::TYPE_IO);
DCHECK(listener_thread_checker_.CalledOnValidThread());
listener_.reset(
new IncomingConnectionListenerPosix(listen_socket_path, this));
listener_->StartListening();
}
void ExternalApplicationListenerPosix::StopListening(
base::WaitableEvent* event) {
DCHECK(listener_thread_checker_.CalledOnValidThread());
listener_.reset();
event->Signal();
}
void ExternalApplicationListenerPosix::OnListening(int rv) {
DCHECK(listener_thread_checker_.CalledOnValidThread());
signal_on_listening_.Signal();
shell_runner_->PostTask(
FROM_HERE,
base::Bind(
&ExternalApplicationListenerPosix::RunErrorCallbackIfListeningFailed,
weak_ptr_factory_.GetWeakPtr(),
rv));
}
void ExternalApplicationListenerPosix::OnConnection(
net::SocketDescriptor incoming) {
DCHECK(listener_thread_checker_.CalledOnValidThread());
shell_runner_->PostTask(
FROM_HERE,
base::Bind(
&ExternalApplicationListenerPosix::CreatePipeAndBindToRegistrarImpl,
weak_ptr_factory_.GetWeakPtr(),
incoming));
}
void ExternalApplicationListenerPosix::RunErrorCallbackIfListeningFailed(
int rv) {
DCHECK(register_thread_checker_.CalledOnValidThread());
if (rv != net::OK && !error_callback_.is_null()) {
error_callback_.Run(rv);
}
}
void ExternalApplicationListenerPosix::CreatePipeAndBindToRegistrarImpl(
net::SocketDescriptor incoming_socket) {
DCHECK(register_thread_checker_.CalledOnValidThread());
DVLOG(1) << "Accepted incoming connection";
scoped_ptr<RegistrarImpl> registrar(new RegistrarImpl(register_callback_));
// Passes ownership of incoming_socket to registrar->channel_init.
mojo::ScopedMessagePipeHandle message_pipe =
registrar->channel_init.Init(incoming_socket, io_runner_);
CHECK(message_pipe.is_valid());
BindToPipe(registrar.release(), message_pipe.Pass());
}
ExternalApplicationListenerPosix::RegistrarImpl::RegistrarImpl(
const RegisterCallback& callback)
: register_callback_(callback) {
}
ExternalApplicationListenerPosix::RegistrarImpl::~RegistrarImpl() {
}
void ExternalApplicationListenerPosix::RegistrarImpl::OnConnectionError() {
channel_init.WillDestroySoon();
}
void ExternalApplicationListenerPosix::RegistrarImpl::Register(
const String& app_url,
InterfaceRequest<Shell> shell,
const mojo::Closure& callback) {
register_callback_.Run(app_url.To<GURL>(), shell.PassMessagePipe());
callback.Run();
}
} // namespace shell
} // namespace mojo
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
#define MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
#include "mojo/shell/external_application_listener.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_checker.h"
#include "mojo/embedder/channel_init.h"
#include "mojo/public/interfaces/application/shell.mojom.h"
#include "mojo/shell/external_application_registrar.mojom.h"
#include "mojo/shell/incoming_connection_listener_posix.h"
#include "net/socket/socket_descriptor.h"
#include "url/gurl.h"
namespace mojo {
namespace shell {
// In order to support Mojo apps whose lifetime is managed by
// something other than mojo_shell, mojo_shell needs to support a
// mechanism by which such an application can discover a running shell
// instance, connect to it, and ask to be "registered" at a given
// URL. Registration implies that the app can be connected to at that
// URL from then on out, and that the app has received a usable ShellPtr.
//
// This class implements most of the mojo_shell side of external application
// registration. It handles:
// 1) discoverability - sets up a unix domain socket at a well-known location,
// 2) incoming connections - listens for and accepts incoming connections on
// that socket, and
// 3) registration requests - forwarded to a RegisterCallback that implements
// the actual registration logic.
//
// External applications can connect to the shell using the
// ExternalApplicationRegistrarConnection class.
class ExternalApplicationListenerPosix
: public ExternalApplicationListener,
public IncomingConnectionListenerPosix::Delegate {
public:
// This class uses two threads, an IO thread for listening and accepting
// incoming sockets, and a "main" thread where all Mojo traffic is processed
// and provided callbacks are run.
ExternalApplicationListenerPosix(
const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
const scoped_refptr<base::SequencedTaskRunner>& io_runner);
// Some of this class' internal state needs to be destroyed on io_runner_,
// so the destructor will post a task to that thread to call StopListening()
// and then wait for it to complete.
virtual ~ExternalApplicationListenerPosix();
// Begin listening (on io_runner) to a socket at listen_socket_path.
// Incoming registration requests will be forwarded to register_callback.
// Errors are ignored.
virtual void ListenInBackground(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback) OVERRIDE;
// Begin listening (on io_runner) to a socket at listen_socket_path.
// Incoming registration requests will be forwarded to register_callback.
// Errors are reported via error_callback.
virtual void ListenInBackgroundWithErrorCallback(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback,
const ErrorCallback& error_callback) OVERRIDE;
// Block the current thread until listening has started on io_runner.
// If listening has already started, returns immediately.
virtual void WaitForListening() OVERRIDE;
private:
class RegistrarImpl;
// MUST be called on io_runner.
// Creates listener_ and tells it to StartListening() on a socket it creates
// at listen_socket_path.
void StartListening(const base::FilePath& listen_socket_path);
// MUST be called on io_runner.
// Destroys listener_ and signals event when done.
void StopListening(base::WaitableEvent* event);
// Implementation of IncomingConnectionListener::Delegate
virtual void OnListening(int rv) MOJO_OVERRIDE;
virtual void OnConnection(net::SocketDescriptor incoming) MOJO_OVERRIDE;
// If listener_ fails to start listening, this method is run on shell_runner_
// to report the error.
void RunErrorCallbackIfListeningFailed(int rv);
// When a connection succeeds, it is passed to this method running
// on shell_runner_, where it is "promoted" to a Mojo MessagePipe and
// bound to a RegistrarImpl.
void CreatePipeAndBindToRegistrarImpl(net::SocketDescriptor incoming_socket);
scoped_refptr<base::SequencedTaskRunner> shell_runner_;
scoped_refptr<base::SequencedTaskRunner> io_runner_;
// MUST be created, used, and destroyed on io_runner_.
scoped_ptr<IncomingConnectionListenerPosix> listener_;
// Callers can wait on this event, which will be signalled once listening
// has either successfully begun or definitively failed.
base::WaitableEvent signal_on_listening_;
// Locked to thread on which StartListening() is run (should be io_runner_).
// All methods that touch listener_ check that they're on that same thread.
base::ThreadChecker listener_thread_checker_;
ErrorCallback error_callback_;
RegisterCallback register_callback_;
base::ThreadChecker register_thread_checker_;
// Used on shell_runner_.
base::WeakPtrFactory<ExternalApplicationListenerPosix> weak_ptr_factory_;
};
} // namespace shell
} // namespace mojo
#endif // MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_POSIX_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread.h"
#include "mojo/application_manager/application_manager.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/public/interfaces/application/application.mojom.h"
#include "mojo/public/interfaces/application/service_provider.mojom.h"
#include "mojo/public/interfaces/application/shell.mojom.h"
#include "mojo/shell/external_application_listener_posix.h"
#include "mojo/shell/external_application_registrar.mojom.h"
#include "mojo/shell/external_application_registrar_connection.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/unix_domain_client_socket_posix.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace mojo {
namespace shell {
class ExternalApplicationListenerTest : public testing::Test {
public:
ExternalApplicationListenerTest() : io_thread_("io thread") {}
virtual ~ExternalApplicationListenerTest() {}
virtual void SetUp() OVERRIDE {
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO;
io_thread_.StartWithOptions(options);
listener_.reset(new ExternalApplicationListenerPosix(
loop_.task_runner(), io_thread_.task_runner()));
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
socket_path_ = temp_dir_.path().Append(FILE_PATH_LITERAL("socket"));
}
protected:
base::MessageLoop loop_;
base::RunLoop run_loop_;
base::Thread io_thread_;
base::ScopedTempDir temp_dir_;
ApplicationManager application_manager_;
base::FilePath socket_path_;
scoped_ptr<ExternalApplicationListener> listener_;
};
namespace {
class StubShellImpl : public InterfaceImpl<Shell> {
private:
virtual void ConnectToApplication(
const String& requestor_url,
InterfaceRequest<ServiceProvider> in_service_provider) MOJO_OVERRIDE {
ServiceProviderPtr out_service_provider;
out_service_provider.Bind(in_service_provider.PassMessagePipe());
client()->AcceptConnection(requestor_url, out_service_provider.Pass());
}
};
void DoLocalRegister(const GURL& app_url, ScopedMessagePipeHandle shell) {
BindToPipe(new StubShellImpl, shell.Pass());
}
void QuitLoopOnConnect(scoped_refptr<base::TaskRunner> loop,
base::Closure quit_callback,
int rv) {
EXPECT_EQ(net::OK, rv);
loop->PostTask(FROM_HERE, quit_callback);
}
void ConnectOnIOThread(const base::FilePath& socket_path,
scoped_refptr<base::TaskRunner> to_quit,
base::Closure quit_callback) {
ExternalApplicationRegistrarConnection connection(socket_path);
connection.Connect(base::Bind(&QuitLoopOnConnect, to_quit, quit_callback));
}
} // namespace
TEST_F(ExternalApplicationListenerTest, ConnectConnection) {
listener_->ListenInBackground(socket_path_, base::Bind(&DoLocalRegister));
listener_->WaitForListening();
io_thread_.task_runner()->PostTask(FROM_HERE,
base::Bind(&ConnectOnIOThread,
socket_path_,
loop_.task_runner(),
run_loop_.QuitClosure()));
run_loop_.Run();
}
namespace {
class QuitLoopOnConnectApplicationImpl : public InterfaceImpl<Application> {
public:
QuitLoopOnConnectApplicationImpl(const std::string& url,
scoped_refptr<base::TaskRunner> loop,
base::Closure quit_callback)
: url_(url), to_quit_(loop), quit_callback_(quit_callback) {}
private:
virtual void Initialize(Array<String> args) MOJO_OVERRIDE {}
virtual void AcceptConnection(const String& requestor_url,
ServiceProviderPtr p) MOJO_OVERRIDE {
DVLOG(1) << url_ << " accepting connection from " << requestor_url;
to_quit_->PostTask(FROM_HERE, quit_callback_);
}
const std::string url_;
scoped_refptr<base::TaskRunner> to_quit_;
base::Closure quit_callback_;
};
class FakeExternalApplication {
public:
FakeExternalApplication(const std::string& url) : url_(url) {}
void ConnectSynchronously(const base::FilePath& socket_path) {
connection_.reset(new ExternalApplicationRegistrarConnection(socket_path));
net::TestCompletionCallback connect_callback;
connection_->Connect(connect_callback.callback());
connect_callback.WaitForResult();
}
// application_impl is the the actual implementation to be registered.
void Register(scoped_ptr<InterfaceImpl<Application>> application_impl,
base::Closure register_complete_callback) {
connection_->Register(GURL(url_), &ptr_, register_complete_callback);
application_impl_ = application_impl.Pass();
ptr_.set_client(application_impl_.get());
}
void ConnectToAppByUrl(std::string app_url) {
ServiceProviderPtr sp;
ptr_->ConnectToApplication(app_url, Get(&sp));
}
const std::string& url() { return url_; }
private:
const std::string url_;
scoped_ptr<InterfaceImpl<Application>> application_impl_;
ShellPtr ptr_;
scoped_ptr<ExternalApplicationRegistrarConnection> connection_;
};
void ConnectToApp(FakeExternalApplication* connector,
FakeExternalApplication* connectee) {
connector->ConnectToAppByUrl(connectee->url());
}
void NoOp() {
}
void ConnectAndRegisterOnIOThread(const base::FilePath& socket_path,
scoped_refptr<base::TaskRunner> loop,
base::Closure quit_callback,
FakeExternalApplication* connector,
FakeExternalApplication* connectee) {
// Connect the first app to the registrar.
connector->ConnectSynchronously(socket_path);
// connector will use this implementation of the Mojo Application interface
// once registration complete.
scoped_ptr<QuitLoopOnConnectApplicationImpl> connector_app_impl(
new QuitLoopOnConnectApplicationImpl(
connector->url(), loop, quit_callback));
// Since connectee won't be ready when connector is done registering, pass
// in a do-nothing callback.
connector->Register(connector_app_impl.PassAs<InterfaceImpl<Application>>(),
base::Bind(&NoOp));
// Connect the second app to the registrar.
connectee->ConnectSynchronously(socket_path);
scoped_ptr<QuitLoopOnConnectApplicationImpl> connectee_app_impl(
new QuitLoopOnConnectApplicationImpl(
connectee->url(), loop, quit_callback));
// After connectee is successfully registered, connector should be
// able to connect to is by URL. Pass in a callback to attempt the
// app -> app connection.
connectee->Register(connectee_app_impl.PassAs<InterfaceImpl<Application>>(),
base::Bind(&ConnectToApp, connector, connectee));
}
void DestroyOnIOThread(scoped_ptr<FakeExternalApplication> doomed1,
scoped_ptr<FakeExternalApplication> doomed2) {
}
} // namespace
// Create two external applications, have them discover and connect to
// the registrar, and then have one app connect to the other by URL.
TEST_F(ExternalApplicationListenerTest, ConnectTwoExternalApplications) {
listener_->ListenInBackground(
socket_path_,
base::Bind(&ApplicationManager::RegisterExternalApplication,
base::Unretained(&application_manager_)));
listener_->WaitForListening();
// Create two external apps.
scoped_ptr<FakeExternalApplication> supersweet_app(
new FakeExternalApplication("http://my.supersweet.app"));
scoped_ptr<FakeExternalApplication> awesome_app(
new FakeExternalApplication("http://my.awesome.app"));
// Connecting and talking to the registrar has to happen on the IO thread.
io_thread_.task_runner()->PostTask(FROM_HERE,
base::Bind(&ConnectAndRegisterOnIOThread,
socket_path_,
loop_.task_runner(),
run_loop_.QuitClosure(),
supersweet_app.get(),
awesome_app.get()));
run_loop_.Run();
// The apps need to be destroyed on the thread where they did socket stuff.
io_thread_.task_runner()->PostTask(FROM_HERE,
base::Bind(&DestroyOnIOThread,
base::Passed(&supersweet_app),
base::Passed(&awesome_app)));
}
} // namespace shell
} // namespace mojo
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/shell/external_application_listener_win.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/sequenced_task_runner.h"
namespace mojo {
namespace shell {
// static
base::FilePath ExternalApplicationListener::ConstructDefaultSocketPath() {
return base::FilePath(FILE_PATH_LITERAL(""));
}
// static
scoped_ptr<ExternalApplicationListener> ExternalApplicationListener::Create(
const scoped_refptr<base::SequencedTaskRunner>& shell_runner,
const scoped_refptr<base::SequencedTaskRunner>& io_runner) {
return make_scoped_ptr(new ExternalApplicationListenerStub);
}
ExternalApplicationListenerStub::ExternalApplicationListenerStub() {
}
ExternalApplicationListenerStub::~ExternalApplicationListenerStub() {
}
void ExternalApplicationListenerStub::ListenInBackground(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback) {
}
void ExternalApplicationListenerStub::ListenInBackgroundWithErrorCallback(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback,
const ErrorCallback& error_callback) {
}
void ExternalApplicationListenerStub::WaitForListening() {
}
} // namespace shell
} // namespace mojo
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
#define MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
#include "mojo/shell/external_application_listener.h"
#include "base/callback.h"
#include "base/files/file_path.h"
namespace mojo {
namespace shell {
// External application registration is not supported on Windows, hence this
// stub.
class ExternalApplicationListenerStub : public ExternalApplicationListener {
public:
ExternalApplicationListenerStub();
virtual ~ExternalApplicationListenerStub() OVERRIDE;
void ListenInBackground(const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback) OVERRIDE;
void ListenInBackgroundWithErrorCallback(
const base::FilePath& listen_socket_path,
const RegisterCallback& register_callback,
const ErrorCallback& error_callback) OVERRIDE;
void WaitForListening() OVERRIDE;
};
} // namespace shell
} // namespace mojo
#endif // MOJO_SHELL_EXTERNAL_APPLICATION_LISTENER_WIN_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import "mojo/public/interfaces/application/shell.mojom"
module mojo {
// This interface allows applications running outside the auspices of a Shell
// to request registration at the given application_url.
// The shell implementing this interface will make the calling application
// available to other apps at that URL and bind the provided Shell& to
// an impl capable of servicing the external application's connection requests.
interface ExternalApplicationRegistrar {
Register(string application_url, Shell& shell) => ();
};
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/shell/external_application_registrar_connection.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_loop.h"
#include "mojo/embedder/channel_init.h"
#include "mojo/public/cpp/bindings/error_handler.h"
#include "mojo/public/interfaces/application/application.mojom.h"
#include "mojo/public/interfaces/application/shell.mojom.h"
#include "mojo/shell/external_application_registrar.mojom.h"
#include "net/base/net_errors.h"
#include "net/socket/socket_descriptor.h"
#include "net/socket/unix_domain_client_socket_posix.h"
#include "url/gurl.h"
namespace mojo {
namespace shell {
ExternalApplicationRegistrarConnection::ExternalApplicationRegistrarConnection(
const base::FilePath& socket_path)
: client_socket_(
new net::UnixDomainClientSocket(socket_path.value(), false)) {
}
ExternalApplicationRegistrarConnection::
~ExternalApplicationRegistrarConnection() {
channel_init_.WillDestroySoon();
}
void ExternalApplicationRegistrarConnection::OnConnectionError() {
channel_init_.WillDestroySoon();
}
void ExternalApplicationRegistrarConnection::Connect(
const net::CompletionCallback& callback) {
DCHECK(client_socket_) << "Single use only.";
int rv = client_socket_->Connect(
base::Bind(&ExternalApplicationRegistrarConnection::OnConnect,
base::Unretained(this),
callback));
if (rv != net::ERR_IO_PENDING) {
DVLOG(1) << "Connect returning immediately: " << net::ErrorToString(rv);
OnConnect(callback, rv);
return;
}
DVLOG(1) << "Waiting for connection.";
}
void ExternalApplicationRegistrarConnection::Register(
const GURL& app_url,
ShellPtr* shell,
base::Closure register_complete_callback) {
DCHECK(!client_socket_);
registrar_->Register(
String::From(app_url), Get(shell), register_complete_callback);
}
void ExternalApplicationRegistrarConnection::OnConnect(
net::CompletionCallback callback,
int rv) {
DVLOG(1) << "OnConnect called: " << net::ErrorToString(rv);
if (rv != net::OK) {
callback.Run(rv);
return;
}
mojo::ScopedMessagePipeHandle ptr_message_pipe_handle =
channel_init_.Init(client_socket_->ReleaseConnectedSocket(),
base::MessageLoopProxy::current());
CHECK(ptr_message_pipe_handle.is_valid());
client_socket_.reset(); // This is dead now, ensure it can't be reused.
registrar_.Bind(ptr_message_pipe_handle.Pass());
callback.Run(rv);
}
} // namespace shell
} // namespace mojo
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_SHELL_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
#define MOJO_SHELL_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
#include "base/callback_forward.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_ptr.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/embedder/channel_init.h"
#include "mojo/public/cpp/bindings/error_handler.h"
#include "mojo/public/interfaces/application/application.mojom.h"
#include "mojo/public/interfaces/application/shell.mojom.h"
#include "mojo/shell/external_application_registrar.mojom.h"
#include "net/socket/socket_descriptor.h"
#include "net/socket/unix_domain_client_socket_posix.h"
#include "url/gurl.h"
namespace mojo {
namespace shell {
// Externally-running applications can use this class to discover and register
// with a running mojo_shell instance.
// MUST be run on an IO thread
class ExternalApplicationRegistrarConnection : public ErrorHandler {
public:
// Configures client_socket_ to point at socket_path.
explicit ExternalApplicationRegistrarConnection(
const base::FilePath& socket_path);
virtual ~ExternalApplicationRegistrarConnection();
// Implementation of ErrorHandler
virtual void OnConnectionError() OVERRIDE;
// Connects client_socket_ and binds it to registrar_.
// Status code is passed to callback upon success or failure.
// May return either synchronously or asynchronously, depending on the
// status of the underlying socket.
void Connect(const net::CompletionCallback& callback);
// Registers this app with the shell at the provided URL.
// shell is not ready for use until register_complete_callback fires.
// TODO(cmasone): Once the pipe for shell can be placed in a FIFO relationship
// with the one underlying registrar_, the callback becomes unneeded.
void Register(const GURL& app_url,
ShellPtr* shell,
base::Closure register_complete_callback);
private:
// Handles the result of Connect(). If it was successful, promotes the socket
// to a MessagePipe and binds it to registrar_.
// Hands rv to callback regardless.
void OnConnect(net::CompletionCallback callback, int rv);
scoped_ptr<net::UnixDomainClientSocket> client_socket_;
mojo::embedder::ChannelInit channel_init_;
ExternalApplicationRegistrarPtr registrar_;
};
} // namespace shell
} // namespace mojo
#endif // MOJO_SHELL_EXTERNAL_APPLICATION_REGISTRAR_CONNECTION_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/bind.h"
#include "base/memory/scoped_ptr.h"
#include "base/test/launcher/unit_test_launcher.h"
#include "base/test/test_suite.h"
#include "mojo/embedder/embedder.h"
#include "mojo/embedder/simple_platform_support.h"
#include "testing/gtest/include/gtest/gtest.h"
int main(int argc, char** argv) {
mojo::embedder::Init(scoped_ptr<mojo::embedder::PlatformSupport>(
new mojo::embedder::SimplePlatformSupport()));
base::TestSuite test_suite(argc, argv);
return base::LaunchUnitTests(
argc,
argv,
base::Bind(&base::TestSuite::Run, base::Unretained(&test_suite)));
}
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/shell/incoming_connection_listener_posix.h"
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/tracked_objects.h"
#include "net/base/net_errors.h"
#include "net/socket/socket_descriptor.h"
#include "net/socket/unix_domain_server_socket_posix.h"
namespace mojo {
namespace shell {
namespace {
// TODO(cmasone): Figure out what we should be doing about "authenticating" the
// process trying to connect.
bool Yes(const net::UnixDomainServerSocket::Credentials& ignored) {
return true;
}
} // anonymous namespace
IncomingConnectionListenerPosix::IncomingConnectionListenerPosix(
const base::FilePath& socket_path,
Delegate* delegate)
: delegate_(delegate),
socket_path_(socket_path),
listen_socket_(base::Bind(&Yes), false),
incoming_socket_(net::kInvalidSocket),
weak_ptr_factory_(this) {
DCHECK(delegate_);
}
IncomingConnectionListenerPosix::~IncomingConnectionListenerPosix() {
weak_ptr_factory_.InvalidateWeakPtrs();
if (!base::DeleteFile(socket_path_, false))
PLOG(ERROR) << "Listening Unix domain socket can't be destroyed.";
}
void IncomingConnectionListenerPosix::StartListening() {
DCHECK(listen_thread_checker_.CalledOnValidThread());
int rv = net::OK;
if (base::PathExists(socket_path_)) {
LOG(ERROR) << "Listening socket file already exists.";
rv = net::ERR_FILE_EXISTS;
} else if (!base::DirectoryExists(socket_path_.DirName())) {
LOG(ERROR) << "Directorty for listening socket does not exist.";
rv = net::ERR_FILE_NOT_FOUND;
} else if (!base::PathIsWritable(socket_path_.DirName())) {
LOG(ERROR) << "Listening socket file path is not writable.";
rv = net::ERR_ACCESS_DENIED;
} else {
const std::string& socket_address = socket_path_.value();
rv = listen_socket_.ListenWithAddressAndPort(socket_address, 0, 100);
}
// Call OnListening() before Accept(), so that the delegate is certain to
// hear about listening before a connection might be accepted below.
delegate_->OnListening(rv);
if (rv == net::OK)
Accept();
}
void IncomingConnectionListenerPosix::Accept() {
DCHECK(listen_thread_checker_.CalledOnValidThread());
int rv = listen_socket_.AcceptSocketDescriptor(
&incoming_socket_,
base::Bind(&IncomingConnectionListenerPosix::OnAccept,
weak_ptr_factory_.GetWeakPtr()));
// If rv == net::ERR_IO_PENDING), listen_socket_ will call
// OnAccept() later, when a connection attempt comes in.
if (rv != net::ERR_IO_PENDING) {
DVLOG_IF(1, rv == net::OK) << "Accept succeeded immediately";
OnAccept(rv);
}
}
void IncomingConnectionListenerPosix::OnAccept(int rv) {
DCHECK(listen_thread_checker_.CalledOnValidThread());
if (rv != net::OK || incoming_socket_ == net::kInvalidSocket) {
LOG_IF(ERROR, rv != net::OK) << "Accept failed " << net::ErrorToString(rv);
PLOG_IF(ERROR, rv == net::OK) << "Socket invalid";
} else {
// Passes ownership of incoming_socket_ to delegate_.
delegate_->OnConnection(incoming_socket_);
incoming_socket_ = net::kInvalidSocket;
}
// Continue waiting to accept incoming connections...
Accept();
}
} // namespace shell
} // namespace mojo
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef MOJO_SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
#define MOJO_SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
#include "base/files/file_path.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "net/socket/socket_descriptor.h"
#include "net/socket/unix_domain_server_socket_posix.h"
namespace mojo {
namespace shell {
// Asynchronously listens for incoming connections on a unix domain
// socket at the provided path. Expects the parent directory in the
// path to exist. Must be run on an IO thread.
class IncomingConnectionListenerPosix {
public:
class Delegate {
public:
virtual ~Delegate() {} // Abstract base class, so this is safe.
// Called when listening has started. rv is from net/base/net_error_list.h
virtual void OnListening(int rv) = 0;
// Called every time an incoming connection is accepted. The delegate
// takes ownership of incoming.
virtual void OnConnection(net::SocketDescriptor incoming) = 0;
};
IncomingConnectionListenerPosix(const base::FilePath& socket_path,
Delegate* delegate);
virtual ~IncomingConnectionListenerPosix();
// Attempts to bind a unix domain socket, set up for listening, at
// socket_path_.
// Regardless of success or failure, calls delegate->OnListening() with a
// status code. If the socket was successfully created, begins asynchronously
// waiting to accept incoming connections.
void StartListening();
private:
// Tells listen_socket_ to perform a non-blocking accept(). It may succeed
// or fail immediately, or asynchronously wait for a later connection attempt.
// Regardless, when it returns a definitive result (OK or a failing error),
// calls OnAccept().
void Accept();
// If rv indicates success, incoming_socket_ should be populated with a
// connected FD. Hands this off to delegate->OnConnection() and goes
// back to non-blocking accept().
// Upon error, logs the error and goes back to non-blocking accept().
void OnAccept(int rv);
Delegate* const delegate_;
const base::FilePath socket_path_;
net::UnixDomainServerSocket listen_socket_;
base::ThreadChecker listen_thread_checker_;
net::SocketDescriptor incoming_socket_;
base::WeakPtrFactory<IncomingConnectionListenerPosix> weak_ptr_factory_;
};
} // namespace shell
} // namespace mojo
#endif // MOJO_SHELL_INCOMING_CONNECTION_LISTENER_POSIX_H_
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "mojo/embedder/channel_init.h"
#include "mojo/embedder/platform_channel_pair.h"
#include "mojo/shell/external_application_registrar_connection.h"
#include "mojo/shell/incoming_connection_listener_posix.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/socket_descriptor.h"
#include "net/socket/unix_domain_client_socket_posix.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace mojo {
namespace shell {
namespace {
// Delegate implementation that expects success.
class TestDelegate : public IncomingConnectionListenerPosix::Delegate {
public:
TestDelegate() {}
virtual ~TestDelegate() {}
virtual void OnListening(int rv) MOJO_OVERRIDE { EXPECT_EQ(net::OK, rv); }
virtual void OnConnection(net::SocketDescriptor incoming) MOJO_OVERRIDE {
EXPECT_NE(net::kInvalidSocket, incoming);
}
};
// Delegate implementation that expects a (configurable) failure to listen.
class ListeningFailsDelegate
: public IncomingConnectionListenerPosix::Delegate {
public:
explicit ListeningFailsDelegate(int expected) : expected_error_(expected) {}
virtual ~ListeningFailsDelegate() {}
virtual void OnListening(int rv) MOJO_OVERRIDE {
EXPECT_EQ(expected_error_, rv);
}
virtual void OnConnection(net::SocketDescriptor incoming) MOJO_OVERRIDE {
FAIL() << "No connection should be attempted.";
}
private:
const int expected_error_;
};
// For ExternalApplicationRegistrarConnection::Connect() callbacks.
void OnConnect(base::Closure quit_callback, int rv) {
EXPECT_EQ(net::OK, rv);
base::MessageLoop::current()->PostTask(FROM_HERE, quit_callback);
}
} // namespace
class IncomingConnectionListenerTest : public testing::Test {
public:
IncomingConnectionListenerTest() {}
virtual ~IncomingConnectionListenerTest() {}
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
socket_path_ = temp_dir_.path().Append(FILE_PATH_LITERAL("socket"));
}
protected:
base::MessageLoopForIO loop_;
base::RunLoop run_loop_;
base::ScopedTempDir temp_dir_;
base::FilePath socket_path_;
};
TEST_F(IncomingConnectionListenerTest, CleanupCheck) {
TestDelegate delegate;
{
IncomingConnectionListenerPosix cleanup_check(socket_path_, &delegate);
cleanup_check.StartListening();
ASSERT_TRUE(base::PathExists(socket_path_));
}
ASSERT_FALSE(base::PathExists(socket_path_));
}
TEST_F(IncomingConnectionListenerTest, ConnectSuccess) {
TestDelegate delegate;
IncomingConnectionListenerPosix listener(socket_path_, &delegate);
ASSERT_FALSE(base::PathExists(socket_path_));
listener.StartListening();
ASSERT_TRUE(base::PathExists(socket_path_));
ExternalApplicationRegistrarConnection connection(socket_path_);
connection.Connect(base::Bind(&OnConnect, run_loop_.QuitClosure()));
run_loop_.Run();
}
TEST_F(IncomingConnectionListenerTest, ConnectFails_SocketFileExists) {
ListeningFailsDelegate fail_delegate(net::ERR_FILE_EXISTS);
IncomingConnectionListenerPosix listener(socket_path_, &fail_delegate);
ASSERT_EQ(1, base::WriteFile(socket_path_, "1", 1));
ASSERT_TRUE(base::PathExists(socket_path_));
// The listener should fail to start up.
listener.StartListening();
}
TEST_F(IncomingConnectionListenerTest, ConnectFails_SocketDirNonexistent) {
base::FilePath nonexistent_dir(temp_dir_.path().Append("dir").Append("file"));
ListeningFailsDelegate fail_delegate(net::ERR_FILE_NOT_FOUND);
IncomingConnectionListenerPosix listener(nonexistent_dir, &fail_delegate);
// The listener should fail to start up.
listener.StartListening();
}
} // namespace shell
} // namespace mojo
...@@ -18,6 +18,11 @@ const char kContentHandlers[] = "content-handlers"; ...@@ -18,6 +18,11 @@ const char kContentHandlers[] = "content-handlers";
// instructions. // instructions.
const char kDisableCache[] = "disable-cache"; const char kDisableCache[] = "disable-cache";
// Allow externally-running applications to discover, connect to, and register
// themselves with the shell.
// TODO(cmasone): Work in progress. Once we're sure this works, remove.
const char kEnableExternalApplications[] = "enable-external-applications";
// Load apps in separate processes. // Load apps in separate processes.
// TODO(vtl): Work in progress; doesn't work. Flip this to "disable" (or maybe // TODO(vtl): Work in progress; doesn't work. Flip this to "disable" (or maybe
// change it to "single-process") when it works. // change it to "single-process") when it works.
......
...@@ -12,6 +12,7 @@ namespace switches { ...@@ -12,6 +12,7 @@ namespace switches {
extern const char kChildProcessType[]; extern const char kChildProcessType[];
extern const char kContentHandlers[]; extern const char kContentHandlers[];
extern const char kDisableCache[]; extern const char kDisableCache[];
extern const char kEnableExternalApplications[];
extern const char kEnableMultiprocess[]; extern const char kEnableMultiprocess[];
extern const char kOrigin[]; extern const char kOrigin[];
extern const char kSpy[]; extern const char kSpy[];
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment