blob: 493a8dba39c374ca289d0c701bbb53c5f3dac8e3 [file] [log] [blame]
// Copyright 2015 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 "components/arc/arc_bridge_bootstrap.h"
#include <fcntl.h>
#include <utility>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/task_runner_util.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/thread_checker.h"
#include "base/threading/worker_pool.h"
#include "chromeos/dbus/dbus_method_call_status.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/session_manager_client.h"
#include "ipc/unix_domain_socket_util.h"
#include "mojo/public/cpp/bindings/binding.h"
#include "third_party/mojo/src/mojo/edk/embedder/embedder.h"
#include "third_party/mojo/src/mojo/edk/embedder/platform_channel_pair.h"
#include "third_party/mojo/src/mojo/edk/embedder/scoped_platform_handle.h"
namespace arc {
namespace {
const base::FilePath::CharType kArcBridgeSocketPath[] =
FILE_PATH_LITERAL("/home/chronos/ArcBridge/bridge.sock");
void OnChannelCreated(mojo::embedder::ChannelInfo* channel) {}
class ArcBridgeBootstrapImpl : public ArcBridgeBootstrap {
public:
// The possible states of the bootstrap connection. In the normal flow,
// the state changes in the following sequence:
//
// STOPPED
// Start() ->
// SOCKET_CREATING
// CreateSocket() -> OnSocketCreated() ->
// STARTING
// StartArcInstance() -> OnInstanceStarted() ->
// STARTED
// AcceptInstanceConnection() -> OnInstanceConnected() ->
// READY
//
// When Stop() is called from any state, either because an operation
// resulted in an error or because the user is logging out:
//
// (any)
// Stop() ->
// STOPPING
// StopInstance() ->
// STOPPED
enum class State {
// ARC is not currently running.
STOPPED,
// An UNIX socket is being created.
SOCKET_CREATING,
// The request to start the instance has been sent.
STARTING,
// The instance has started. Waiting for it to connect to the IPC bridge.
STARTED,
// The instance has finished booting.
READY,
// The request to shut down the instance has been sent.
STOPPING,
};
ArcBridgeBootstrapImpl();
~ArcBridgeBootstrapImpl() override;
// ArcBridgeBootstrap:
void Start() override;
void Stop() override;
private:
// Creates the UNIX socket on the bootstrap thread and then processes its
// file descriptor.
static base::ScopedFD CreateSocket();
void OnSocketCreated(base::ScopedFD fd);
// Synchronously accepts a connection on |socket_fd| and then processes the
// connected socket's file descriptor.
static base::ScopedFD AcceptInstanceConnection(base::ScopedFD socket_fd);
void OnInstanceConnected(base::ScopedFD fd);
void SetState(State state);
// DBus callbacks.
void OnInstanceStarted(base::ScopedFD socket_fd, bool success);
void OnInstanceStopped(bool success);
// The state of the bootstrap connection.
State state_ = State::STOPPED;
base::ThreadChecker thread_checker_;
// WeakPtrFactory to use callbacks.
base::WeakPtrFactory<ArcBridgeBootstrapImpl> weak_factory_;
private:
DISALLOW_COPY_AND_ASSIGN(ArcBridgeBootstrapImpl);
};
ArcBridgeBootstrapImpl::ArcBridgeBootstrapImpl() : weak_factory_(this) {}
ArcBridgeBootstrapImpl::~ArcBridgeBootstrapImpl() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(state_ == State::STOPPED);
}
void ArcBridgeBootstrapImpl::Start() {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(delegate_);
if (state_ != State::STOPPED) {
VLOG(1) << "Start() called when instance is not stopped";
return;
}
SetState(State::SOCKET_CREATING);
base::PostTaskAndReplyWithResult(
base::WorkerPool::GetTaskRunner(true).get(), FROM_HERE,
base::Bind(&ArcBridgeBootstrapImpl::CreateSocket),
base::Bind(&ArcBridgeBootstrapImpl::OnSocketCreated,
weak_factory_.GetWeakPtr()));
}
// static
base::ScopedFD ArcBridgeBootstrapImpl::CreateSocket() {
base::FilePath socket_path(kArcBridgeSocketPath);
int raw_fd = -1;
if (!IPC::CreateServerUnixDomainSocket(socket_path, &raw_fd)) {
return base::ScopedFD();
}
base::ScopedFD socket_fd(raw_fd);
// Make socket blocking.
int flags = HANDLE_EINTR(fcntl(socket_fd.get(), F_GETFL));
if (flags == -1) {
PLOG(ERROR) << "fcntl(F_GETFL)";
return base::ScopedFD();
}
if (HANDLE_EINTR(fcntl(socket_fd.get(), F_SETFL, flags & ~O_NONBLOCK)) < 0) {
PLOG(ERROR) << "fcntl(O_NONBLOCK)";
return base::ScopedFD();
}
// TODO(lhchavez): Tighten the security around the socket by tying it to
// the user the instance will run as.
if (!base::SetPosixFilePermissions(socket_path, 0777)) {
PLOG(ERROR) << "Could not set permissions: " << socket_path.value();
return base::ScopedFD();
}
return socket_fd;
}
void ArcBridgeBootstrapImpl::OnSocketCreated(base::ScopedFD socket_fd) {
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ != State::SOCKET_CREATING) {
VLOG(1) << "Stop() called while connecting";
return;
}
SetState(State::STARTING);
if (!socket_fd.is_valid()) {
LOG(ERROR) << "ARC: Error creating socket";
Stop();
return;
}
chromeos::SessionManagerClient* session_manager_client =
chromeos::DBusThreadManager::Get()->GetSessionManagerClient();
session_manager_client->StartArcInstance(
kArcBridgeSocketPath,
base::Bind(&ArcBridgeBootstrapImpl::OnInstanceStarted,
weak_factory_.GetWeakPtr(), base::Passed(&socket_fd)));
}
void ArcBridgeBootstrapImpl::OnInstanceStarted(base::ScopedFD socket_fd,
bool success) {
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ != State::STARTING) {
VLOG(1) << "Stop() called when ARC is not running";
return;
}
if (!success) {
LOG(ERROR) << "Failed to start ARC instance";
// Roll back the state to SOCKET_CREATING to avoid sending the D-Bus signal
// to stop the failed instance.
SetState(State::SOCKET_CREATING);
Stop();
return;
}
SetState(State::STARTED);
base::PostTaskAndReplyWithResult(
base::WorkerPool::GetTaskRunner(true).get(), FROM_HERE,
base::Bind(&ArcBridgeBootstrapImpl::AcceptInstanceConnection,
base::Passed(&socket_fd)),
base::Bind(&ArcBridgeBootstrapImpl::OnInstanceConnected,
weak_factory_.GetWeakPtr()));
}
// static
base::ScopedFD ArcBridgeBootstrapImpl::AcceptInstanceConnection(
base::ScopedFD socket_fd) {
int raw_fd = -1;
if (!IPC::ServerAcceptConnection(socket_fd.get(), &raw_fd)) {
return base::ScopedFD();
}
return base::ScopedFD(raw_fd);
}
void ArcBridgeBootstrapImpl::OnInstanceConnected(base::ScopedFD fd) {
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ != State::STARTED) {
VLOG(1) << "Stop() called when ARC is not running";
return;
}
SetState(State::READY);
mojo::ScopedMessagePipeHandle server_pipe = mojo::embedder::CreateChannel(
mojo::embedder::ScopedPlatformHandle(
mojo::embedder::PlatformHandle(fd.release())),
base::Bind(&OnChannelCreated), base::ThreadTaskRunnerHandle::Get());
ArcBridgeInstancePtr instance;
instance.Bind(
mojo::InterfacePtrInfo<ArcBridgeInstance>(std::move(server_pipe), 0u));
delegate_->OnConnectionEstablished(std::move(instance));
}
void ArcBridgeBootstrapImpl::Stop() {
DCHECK(thread_checker_.CalledOnValidThread());
if (state_ == State::STOPPED || state_ == State::STOPPING) {
VLOG(1) << "Stop() called when ARC is not running";
return;
}
if (state_ == State::SOCKET_CREATING) {
// This was stopped before the D-Bus command to start the instance. Skip
// the D-Bus command to stop it.
SetState(State::STOPPING);
OnInstanceStopped(true);
return;
}
SetState(State::STOPPING);
chromeos::SessionManagerClient* session_manager_client =
chromeos::DBusThreadManager::Get()->GetSessionManagerClient();
session_manager_client->StopArcInstance(base::Bind(
&ArcBridgeBootstrapImpl::OnInstanceStopped, weak_factory_.GetWeakPtr()));
}
void ArcBridgeBootstrapImpl::OnInstanceStopped(bool success) {
DCHECK(thread_checker_.CalledOnValidThread());
// STOPPING is the only valid state for this function.
// DCHECK on enum classes not supported.
DCHECK(state_ == State::STOPPING);
DCHECK(delegate_);
SetState(State::STOPPED);
delegate_->OnStopped();
}
void ArcBridgeBootstrapImpl::SetState(State state) {
DCHECK(thread_checker_.CalledOnValidThread());
// DCHECK on enum classes not supported.
DCHECK(state_ != state);
state_ = state;
}
} // namespace
ArcBridgeBootstrap::ArcBridgeBootstrap() {}
ArcBridgeBootstrap::~ArcBridgeBootstrap() {}
// static
scoped_ptr<ArcBridgeBootstrap> ArcBridgeBootstrap::Create() {
return make_scoped_ptr(new ArcBridgeBootstrapImpl());
}
} // namespace arc