blob: 520587e86bc1e659e69b2063d26155a2edbed5c5 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/chromeos/crostini/crostini_manager.h"
#include <algorithm>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "base/task_scheduler/post_task.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chromeos/crostini/crostini_remover.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chrome/browser/component_updater/cros_component_installer_chromeos.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/extensions/application_launch.h"
#include "chromeos/dbus/concierge_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon_client.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "content/public/browser/browser_thread.h"
#include "dbus/message.h"
#include "extensions/browser/extension_registry.h"
#include "net/base/escape.h"
namespace crostini {
namespace {
constexpr int64_t kMinimumDiskSize = 1ll * 1024 * 1024 * 1024; // 1 GiB
constexpr base::FilePath::CharType kHomeDirectory[] =
FILE_PATH_LITERAL("/home");
chromeos::CiceroneClient* GetCiceroneClient() {
return chromeos::DBusThreadManager::Get()->GetCiceroneClient();
}
chromeos::ConciergeClient* GetConciergeClient() {
return chromeos::DBusThreadManager::Get()->GetConciergeClient();
}
class CrostiniRestarter;
class CrostiniRestarterService : public KeyedService {
public:
CrostiniRestarterService() = default;
~CrostiniRestarterService() override = default;
CrostiniManager::RestartId Register(
std::string vm_name,
std::string crypothome_id,
std::string container_name,
std::string container_username,
CrostiniManager::RestartCrostiniCallback callback,
CrostiniManager::RestartObserver* observer);
void RunPendingCallbacks(CrostiniRestarter* restarter,
ConciergeClientResult result);
// Aborts restart_id. A "next" restarter with the same <vm_name,
// container_name> will run, if there is one.
void Abort(CrostiniManager::RestartId restart_id);
private:
void ErasePending(CrostiniRestarter* restarter);
std::map<CrostiniManager::RestartId, scoped_refptr<CrostiniRestarter>>
restarter_map_;
// Restarts by <vm_name, container_name>. Only one restarter flow is actually
// running. Other restarters will just have their callback called when the
// running restarter completes.
std::multimap<std::pair<std::string, std::string>, CrostiniManager::RestartId>
pending_map_;
};
class CrostiniRestarterServiceFactory
: public BrowserContextKeyedServiceFactory {
public:
static CrostiniRestarterService* GetForProfile(Profile* profile) {
return static_cast<CrostiniRestarterService*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
static CrostiniRestarterServiceFactory* GetInstance() {
static base::NoDestructor<CrostiniRestarterServiceFactory> factory;
return factory.get();
}
private:
friend class base::NoDestructor<CrostiniRestarterServiceFactory>;
CrostiniRestarterServiceFactory()
: BrowserContextKeyedServiceFactory(
"CrostiniRestarterService",
BrowserContextDependencyManager::GetInstance()) {}
~CrostiniRestarterServiceFactory() override = default;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override {
return new CrostiniRestarterService();
}
};
class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter> {
public:
CrostiniRestarter(CrostiniRestarterService* restarter_service,
std::string vm_name,
std::string cryptohome_id,
std::string container_name,
std::string container_username,
CrostiniManager::RestartCrostiniCallback callback)
: vm_name_(std::move(vm_name)),
cryptohome_id_(std::move(cryptohome_id)),
container_name_(std::move(container_name)),
container_username_(std::move(container_username)),
callback_(std::move(callback)),
restart_id_(next_restart_id_++),
restarter_service_(restarter_service) {}
void Restart() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (is_aborted_)
return;
CrostiniManager* crostini_manager = CrostiniManager::GetInstance();
// Finish Restart immediately if testing.
if (crostini_manager->skip_restart_for_testing()) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::BindOnce(&CrostiniRestarter::FinishRestart,
base::WrapRefCounted(this),
ConciergeClientResult::SUCCESS));
return;
}
if (chromeos::DBusThreadManager::Get()->IsUsingFakes()) {
// Running in test. We still PostTask to prevent races between
// observers aborting.
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::BindOnce(
&CrostiniRestarter::InstallImageLoaderFinishedOnUIThread,
base::WrapRefCounted(this),
component_updater::CrOSComponentManager::Error::NONE,
base::FilePath()));
return;
}
auto* cros_component_manager =
g_browser_process->platform_part()->cros_component_manager();
DCHECK(cros_component_manager);
cros_component_manager->Load(
"cros-termina",
component_updater::CrOSComponentManager::MountPolicy::kMount,
component_updater::CrOSComponentManager::UpdatePolicy::kDontForce,
base::BindOnce(&CrostiniRestarter::InstallImageLoaderFinished,
base::WrapRefCounted(this)));
}
void AddObserver(CrostiniManager::RestartObserver* observer) {
observer_list_.AddObserver(observer);
}
void RunCallback(ConciergeClientResult result) {
std::move(callback_).Run(result);
}
void Abort() {
is_aborted_ = true;
observer_list_.Clear();
}
CrostiniManager::RestartId restart_id() const { return restart_id_; }
std::string vm_name() const { return vm_name_; }
std::string container_name() const { return container_name_; }
private:
friend class base::RefCountedThreadSafe<CrostiniRestarter>;
~CrostiniRestarter() {
if (callback_) {
LOG(ERROR) << "Destroying without having called the callback.";
}
}
void FinishRestart(ConciergeClientResult result) {
restarter_service_->RunPendingCallbacks(this, result);
}
static void InstallImageLoaderFinished(
scoped_refptr<CrostiniRestarter> restarter,
component_updater::CrOSComponentManager::Error error,
const base::FilePath& result) {
DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
if (restarter->is_aborted_)
return;
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::BindOnce(&CrostiniRestarter::InstallImageLoaderFinishedOnUIThread,
restarter, error, result));
}
void InstallImageLoaderFinishedOnUIThread(
component_updater::CrOSComponentManager::Error error,
const base::FilePath& result) {
ConciergeClientResult client_result =
error == component_updater::CrOSComponentManager::Error::NONE
? ConciergeClientResult::SUCCESS
: ConciergeClientResult::CONTAINER_START_FAILED;
// Tell observers.
for (auto& observer : observer_list_) {
observer.OnComponentLoaded(client_result);
}
if (is_aborted_)
return;
if (client_result != ConciergeClientResult::SUCCESS) {
LOG(ERROR)
<< "Failed to install the cros-termina component with error code: "
<< static_cast<int>(error);
FinishRestart(client_result);
return;
}
CrostiniManager::GetInstance()->StartConcierge(
base::BindOnce(&CrostiniRestarter::ConciergeStarted, this));
}
void ConciergeStarted(bool is_started) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
ConciergeClientResult client_result =
is_started ? ConciergeClientResult::SUCCESS
: ConciergeClientResult::CONTAINER_START_FAILED;
// Tell observers.
for (auto& observer : observer_list_) {
observer.OnConciergeStarted(client_result);
}
if (is_aborted_)
return;
if (!is_started) {
LOG(ERROR) << "Failed to start Concierge service.";
FinishRestart(client_result);
return;
}
CrostiniManager::GetInstance()->CreateDiskImage(
cryptohome_id_, base::FilePath(vm_name_),
vm_tools::concierge::StorageLocation::STORAGE_CRYPTOHOME_ROOT,
base::BindOnce(&CrostiniRestarter::CreateDiskImageFinished, this));
}
void CreateDiskImageFinished(ConciergeClientResult result,
const base::FilePath& result_path) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Tell observers.
for (auto& observer : observer_list_) {
observer.OnDiskImageCreated(result);
}
if (is_aborted_)
return;
if (result != ConciergeClientResult::SUCCESS) {
LOG(ERROR) << "Failed to create disk image.";
FinishRestart(result);
return;
}
CrostiniManager::GetInstance()->StartTerminaVm(
cryptohome_id_, vm_name_, result_path,
base::BindOnce(&CrostiniRestarter::StartTerminaVmFinished, this));
}
void StartTerminaVmFinished(ConciergeClientResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Tell observers.
for (auto& observer : observer_list_) {
observer.OnVmStarted(result);
}
if (is_aborted_)
return;
if (result != ConciergeClientResult::SUCCESS) {
LOG(ERROR) << "Failed to Start Termina VM.";
FinishRestart(result);
return;
}
CrostiniManager::GetInstance()->StartContainer(
vm_name_, container_name_, container_username_, cryptohome_id_,
base::BindOnce(&CrostiniRestarter::StartContainerFinished, this));
}
void StartContainerFinished(ConciergeClientResult result) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (result != ConciergeClientResult::SUCCESS) {
LOG(ERROR) << "Failed to start container.";
}
if (is_aborted_)
return;
FinishRestart(result);
}
std::string vm_name_;
std::string cryptohome_id_;
std::string container_name_;
std::string container_username_;
CrostiniManager::RestartCrostiniCallback callback_;
base::ObserverList<CrostiniManager::RestartObserver> observer_list_;
CrostiniManager::RestartId restart_id_;
CrostiniRestarterService* restarter_service_;
bool is_aborted_ = false;
static CrostiniManager::RestartId next_restart_id_;
};
CrostiniManager::RestartId CrostiniRestarter::next_restart_id_ = 0;
CrostiniManager::RestartId CrostiniRestarterService::Register(
std::string vm_name,
std::string cryptohome_id,
std::string container_name,
std::string container_username,
CrostiniManager::RestartCrostiniCallback callback,
CrostiniManager::RestartObserver* observer) {
auto restarter = base::MakeRefCounted<CrostiniRestarter>(
this, std::move(vm_name), std::move(cryptohome_id),
std::move(container_name), std::move(container_username),
std::move(callback));
if (observer)
restarter->AddObserver(observer);
auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
pending_map_.emplace(key, restarter->restart_id());
restarter_map_[restarter->restart_id()] = restarter;
if (pending_map_.count(key) > 1) {
VLOG(1) << "Already restarting vm " << vm_name << ", container "
<< container_name;
} else {
restarter->Restart();
}
return restarter->restart_id();
}
void CrostiniRestarterService::RunPendingCallbacks(
CrostiniRestarter* restarter,
ConciergeClientResult result) {
auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
auto range = pending_map_.equal_range(key);
std::vector<scoped_refptr<CrostiniRestarter>> pending_restarters;
// Erase first, because restarter->RunCallback() may modify our maps.
for (auto it = range.first; it != range.second; ++it) {
CrostiniManager::RestartId restart_id = it->second;
pending_restarters.emplace_back(restarter_map_[restart_id]);
restarter_map_.erase(restart_id);
}
pending_map_.erase(range.first, range.second);
for (const auto& pending_restarter : pending_restarters) {
pending_restarter->RunCallback(result);
}
}
void CrostiniRestarterService::Abort(CrostiniManager::RestartId restart_id) {
auto it = restarter_map_.find(restart_id);
if (it == restarter_map_.end()) {
// This can happen if a user cancels the install flow at the exact right
// moment, for example.
LOG(ERROR) << "Aborting a restarter that already finished";
return;
}
it->second->Abort();
ErasePending(it->second.get());
// Erasing |it| also invalidates |it|, so make a key from |it| now.
auto key =
std::make_pair(it->second->vm_name(), it->second->container_name());
restarter_map_.erase(it);
// Kick off the "next" (in no order) pending Restart() if any.
auto pending_it = pending_map_.find(key);
if (pending_it != pending_map_.end()) {
auto restarter = restarter_map_[pending_it->second];
restarter->Restart();
}
}
void CrostiniRestarterService::ErasePending(CrostiniRestarter* restarter) {
// Erase from pending_map_
auto key = std::make_pair(restarter->vm_name(), restarter->container_name());
auto range = pending_map_.equal_range(key);
for (auto it = range.first; it != range.second; ++it) {
if (it->second == restarter->restart_id()) {
pending_map_.erase(it);
return;
}
}
NOTREACHED();
}
void OnConciergeServiceAvailable(
CrostiniManager::StartConciergeCallback callback,
bool success) {
if (!success) {
LOG(ERROR) << "Concierge service did not become available";
std::move(callback).Run(success);
return;
}
VLOG(1) << "Concierge service announced availability";
VLOG(1) << "Waiting for Cicerone to announce availability.";
GetCiceroneClient()->WaitForServiceToBeAvailable(std::move(callback));
}
} // namespace
// static
CrostiniManager* CrostiniManager::GetInstance() {
return base::Singleton<CrostiniManager>::get();
}
bool CrostiniManager::IsVmRunning(Profile* profile, std::string vm_name) {
return running_vms_.find(std::make_pair(CryptohomeIdForProfile(profile),
std::move(vm_name))) !=
running_vms_.end();
}
bool CrostiniManager::IsContainerRunning(Profile* profile,
std::string vm_name,
std::string container_name) {
auto range = running_containers_.equal_range(
std::make_pair(CryptohomeIdForProfile(profile), std::move(vm_name)));
for (auto it = range.first; it != range.second; ++it) {
if (it->second == container_name) {
return true;
}
}
return false;
}
CrostiniManager::CrostiniManager() : weak_ptr_factory_(this) {
// Cicerone/ConciergeClient and its observer_list_ will be destroyed together.
// We add, but don't need to remove the observer. (Doing so would force a
// "destroyed before" dependency on the owner of Cicerone/ConciergeClient).
GetCiceroneClient()->AddObserver(this);
GetConciergeClient()->AddObserver(this);
}
CrostiniManager::~CrostiniManager() {}
// static
bool CrostiniManager::IsCrosTerminaInstalled() {
return !g_browser_process->platform_part()
->cros_component_manager()
->GetCompatiblePath("cros-termina")
.empty();
}
void CrostiniManager::StartConcierge(StartConciergeCallback callback) {
VLOG(1) << "Starting Concierge service";
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->StartConcierge(
base::BindOnce(&CrostiniManager::OnStartConcierge,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::OnStartConcierge(StartConciergeCallback callback,
bool success) {
if (!success) {
LOG(ERROR) << "Failed to start Concierge service";
std::move(callback).Run(success);
return;
}
VLOG(1) << "Concierge service started";
VLOG(1) << "Waiting for Concierge to announce availability.";
GetConciergeClient()->WaitForServiceToBeAvailable(
base::BindOnce(&OnConciergeServiceAvailable, std::move(callback)));
}
void CrostiniManager::StopConcierge(StopConciergeCallback callback) {
VLOG(1) << "Stopping Concierge service";
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->StopConcierge(
base::BindOnce(&CrostiniManager::OnStopConcierge,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::OnStopConcierge(StopConciergeCallback callback,
bool success) {
if (!success) {
LOG(ERROR) << "Failed to stop Concierge service";
} else {
VLOG(1) << "Concierge service stopped";
}
std::move(callback).Run(success);
}
void CrostiniManager::CreateDiskImage(
const std::string& cryptohome_id,
const base::FilePath& disk_path,
vm_tools::concierge::StorageLocation storage_location,
CreateDiskImageCallback callback) {
if (cryptohome_id.empty()) {
LOG(ERROR) << "Cryptohome id cannot be empty";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR,
base::FilePath());
return;
}
std::string disk_path_string = disk_path.AsUTF8Unsafe();
if (disk_path_string.empty()) {
LOG(ERROR) << "Disk path cannot be empty";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR,
base::FilePath());
return;
}
vm_tools::concierge::CreateDiskImageRequest request;
request.set_cryptohome_id(std::move(cryptohome_id));
request.set_disk_path(std::move(disk_path_string));
// The type of disk image to be created.
request.set_image_type(vm_tools::concierge::DISK_IMAGE_QCOW2);
if (storage_location != vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT &&
storage_location != vm_tools::concierge::STORAGE_CRYPTOHOME_DOWNLOADS) {
LOG(ERROR) << "'" << storage_location
<< "' is not a valid storage location";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR,
base::FilePath());
return;
}
request.set_storage_location(storage_location);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&base::SysInfo::AmountOfFreeDiskSpace,
base::FilePath(kHomeDirectory)),
base::BindOnce(&CrostiniManager::CreateDiskImageAfterSizeCheck,
weak_ptr_factory_.GetWeakPtr(), std::move(request),
std::move(callback)));
}
void CrostiniManager::CreateDiskImageAfterSizeCheck(
vm_tools::concierge::CreateDiskImageRequest request,
CreateDiskImageCallback callback,
int64_t free_disk_size) {
int64_t disk_size = (free_disk_size * 9) / 10;
// Skip disk size check on dev box or trybots because
// base::SysInfo::AmountOfFreeDiskSpace returns zero in testing.
if (disk_size < kMinimumDiskSize && base::SysInfo::IsRunningOnChromeOS()) {
LOG(ERROR) << "Insufficient disk available. Need to free "
<< kMinimumDiskSize - disk_size << " bytes";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR,
base::FilePath());
return;
}
// The logical size of the new disk image, in bytes.
request.set_disk_size(std::move(disk_size));
GetConciergeClient()->CreateDiskImage(
std::move(request),
base::BindOnce(&CrostiniManager::OnCreateDiskImage,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::DestroyDiskImage(
const std::string& cryptohome_id,
const base::FilePath& disk_path,
vm_tools::concierge::StorageLocation storage_location,
DestroyDiskImageCallback callback) {
if (cryptohome_id.empty()) {
LOG(ERROR) << "Cryptohome id cannot be empty";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
std::string disk_path_string = disk_path.AsUTF8Unsafe();
if (disk_path_string.empty()) {
LOG(ERROR) << "Disk path cannot be empty";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
vm_tools::concierge::DestroyDiskImageRequest request;
request.set_cryptohome_id(std::move(cryptohome_id));
request.set_disk_path(std::move(disk_path_string));
if (storage_location != vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT &&
storage_location != vm_tools::concierge::STORAGE_CRYPTOHOME_DOWNLOADS) {
LOG(ERROR) << "'" << storage_location
<< "' is not a valid storage location";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
request.set_storage_location(storage_location);
GetConciergeClient()->DestroyDiskImage(
std::move(request),
base::BindOnce(&CrostiniManager::OnDestroyDiskImage,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::ListVmDisks(
// The cryptohome id for the user's encrypted storage.
const std::string& cryptohome_id,
ListVmDisksCallback callback) {
if (cryptohome_id.empty()) {
LOG(ERROR) << "Cryptohome id cannot be empty";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR, 0);
return;
}
vm_tools::concierge::ListVmDisksRequest request;
request.set_cryptohome_id(std::move(cryptohome_id));
request.set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT);
GetConciergeClient()->ListVmDisks(
std::move(request),
base::BindOnce(&CrostiniManager::OnListVmDisks,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::StartTerminaVm(std::string owner_id,
std::string name,
const base::FilePath& disk_path,
StartTerminaVmCallback callback) {
if (owner_id.empty()) {
LOG(ERROR) << "owner_id is required";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
if (name.empty()) {
LOG(ERROR) << "name is required";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
std::string disk_path_string = disk_path.AsUTF8Unsafe();
if (disk_path_string.empty()) {
LOG(ERROR) << "Disk path cannot be empty";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
vm_tools::concierge::StartVmRequest request;
request.set_name(std::move(name));
request.set_start_termina(true);
request.set_owner_id(std::move(owner_id));
vm_tools::concierge::DiskImage* disk_image = request.add_disks();
disk_image->set_path(std::move(disk_path_string));
disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_QCOW2);
disk_image->set_writable(true);
disk_image->set_do_mount(false);
GetConciergeClient()->StartTerminaVm(
request,
base::BindOnce(&CrostiniManager::OnStartTerminaVm,
weak_ptr_factory_.GetWeakPtr(), request.owner_id(),
request.name(), std::move(callback)));
}
void CrostiniManager::StopVm(Profile* profile,
std::string name,
StopVmCallback callback) {
if (name.empty()) {
LOG(ERROR) << "name is required";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
std::string owner_id = CryptohomeIdForProfile(profile);
vm_tools::concierge::StopVmRequest request;
request.set_owner_id(owner_id);
request.set_name(name);
GetConciergeClient()->StopVm(
std::move(request),
base::BindOnce(&CrostiniManager::OnStopVm, weak_ptr_factory_.GetWeakPtr(),
std::move(owner_id), std::move(name),
std::move(callback)));
}
void CrostiniManager::StartContainer(std::string vm_name,
std::string container_name,
std::string container_username,
std::string cryptohome_id,
StartContainerCallback callback) {
if (vm_name.empty()) {
LOG(ERROR) << "vm_name is required";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
if (container_name.empty()) {
LOG(ERROR) << "container_name is required";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
if (container_username.empty()) {
LOG(ERROR) << "container_username is required";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
if (cryptohome_id.empty()) {
LOG(ERROR) << "cryptohome_id is required";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
if (!GetConciergeClient()->IsContainerStartupFailedSignalConnected() ||
!GetCiceroneClient()->IsContainerStartedSignalConnected() ||
!GetCiceroneClient()->IsContainerShutdownSignalConnected()) {
LOG(ERROR) << "Async call to StartContainer can't complete when signal "
"is not connected.";
std::move(callback).Run(ConciergeClientResult::CLIENT_ERROR);
return;
}
vm_tools::concierge::StartContainerRequest request;
request.set_vm_name(std::move(vm_name));
request.set_container_name(std::move(container_name));
request.set_container_username(std::move(container_username));
request.set_cryptohome_id(std::move(cryptohome_id));
GetConciergeClient()->StartContainer(
std::move(request),
base::BindOnce(&CrostiniManager::OnStartContainer,
weak_ptr_factory_.GetWeakPtr(), request.cryptohome_id(),
request.vm_name(), request.container_name(),
std::move(callback)));
}
void CrostiniManager::LaunchContainerApplication(
Profile* profile,
std::string vm_name,
std::string container_name,
std::string desktop_file_id,
const std::vector<std::string>& files,
LaunchContainerApplicationCallback callback) {
vm_tools::cicerone::LaunchContainerApplicationRequest request;
request.set_owner_id(CryptohomeIdForProfile(profile));
request.set_vm_name(std::move(vm_name));
request.set_container_name(std::move(container_name));
request.set_desktop_file_id(std::move(desktop_file_id));
std::copy(
files.begin(), files.end(),
google::protobuf::RepeatedFieldBackInserter(request.mutable_files()));
GetCiceroneClient()->LaunchContainerApplication(
std::move(request),
base::BindOnce(&CrostiniManager::OnLaunchContainerApplication,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::GetContainerAppIcons(
Profile* profile,
std::string vm_name,
std::string container_name,
std::vector<std::string> desktop_file_ids,
int icon_size,
int scale,
GetContainerAppIconsCallback callback) {
vm_tools::cicerone::ContainerAppIconRequest request;
request.set_owner_id(CryptohomeIdForProfile(profile));
request.set_vm_name(std::move(vm_name));
request.set_container_name(std::move(container_name));
google::protobuf::RepeatedPtrField<std::string> ids(
std::make_move_iterator(desktop_file_ids.begin()),
std::make_move_iterator(desktop_file_ids.end()));
request.mutable_desktop_file_ids()->Swap(&ids);
request.set_size(icon_size);
request.set_scale(scale);
GetCiceroneClient()->GetContainerAppIcons(
std::move(request),
base::BindOnce(&CrostiniManager::OnGetContainerAppIcons,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::InstallLinuxPackage(
Profile* profile,
std::string vm_name,
std::string container_name,
std::string package_path,
InstallLinuxPackageCallback callback) {
if (!GetCiceroneClient()->IsInstallLinuxPackageProgressSignalConnected()) {
// Technically we could still start the install, but we wouldn't be able to
// detect when the install completes, successfully or otherwise.
LOG(ERROR)
<< "Attempted to install package when progress signal not connected.";
std::move(callback).Run(ConciergeClientResult::INSTALL_LINUX_PACKAGE_FAILED,
std::string());
return;
}
vm_tools::cicerone::InstallLinuxPackageRequest request;
request.set_owner_id(CryptohomeIdForProfile(profile));
request.set_vm_name(std::move(vm_name));
request.set_container_name(std::move(container_name));
request.set_file_path(std::move(package_path));
GetCiceroneClient()->InstallLinuxPackage(
std::move(request),
base::BindOnce(&CrostiniManager::OnInstallLinuxPackage,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void CrostiniManager::GetContainerSshKeys(
std::string vm_name,
std::string container_name,
std::string cryptohome_id,
GetContainerSshKeysCallback callback) {
vm_tools::concierge::ContainerSshKeysRequest request;
request.set_vm_name(std::move(vm_name));
request.set_container_name(std::move(container_name));
request.set_cryptohome_id(std::move(cryptohome_id));
GetConciergeClient()->GetContainerSshKeys(
std::move(request),
base::BindOnce(&CrostiniManager::OnGetContainerSshKeys,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
// static
GURL CrostiniManager::GenerateVshInCroshUrl(Profile* profile,
const std::string& vm_name,
const std::string& container_name) {
std::string vsh_crosh = base::StringPrintf(
"chrome-extension://%s/html/crosh.html?command=vmshell",
kCrostiniCroshBuiltinAppId);
std::string vm_name_param = net::EscapeQueryParamValue(
base::StringPrintf("--vm_name=%s", vm_name.c_str()), false);
std::string container_name_param = net::EscapeQueryParamValue(
base::StringPrintf("--target_container=%s", container_name.c_str()),
false);
std::string owner_id_param = net::EscapeQueryParamValue(
base::StringPrintf("--owner_id=%s",
CryptohomeIdForProfile(profile).c_str()),
false);
std::vector<base::StringPiece> pieces = {
vsh_crosh, vm_name_param, container_name_param, owner_id_param};
GURL vsh_in_crosh_url(base::JoinString(pieces, "&args[]="));
return vsh_in_crosh_url;
}
// static
AppLaunchParams CrostiniManager::GenerateTerminalAppLaunchParams(
Profile* profile) {
const extensions::Extension* crosh_extension =
extensions::ExtensionRegistry::Get(profile)->GetInstalledExtension(
kCrostiniCroshBuiltinAppId);
AppLaunchParams launch_params(
profile, crosh_extension, extensions::LAUNCH_CONTAINER_WINDOW,
WindowOpenDisposition::NEW_WINDOW, extensions::SOURCE_APP_LAUNCHER);
launch_params.override_app_name =
AppNameFromCrostiniAppId(kCrostiniTerminalId);
return launch_params;
}
Browser* CrostiniManager::CreateContainerTerminal(
const AppLaunchParams& launch_params,
const GURL& vsh_in_crosh_url) {
return CreateApplicationWindow(launch_params, vsh_in_crosh_url);
}
void CrostiniManager::ShowContainerTerminal(
const AppLaunchParams& launch_params,
const GURL& vsh_in_crosh_url,
Browser* browser) {
ShowApplicationWindow(launch_params, vsh_in_crosh_url, browser);
}
void CrostiniManager::LaunchContainerTerminal(
Profile* profile,
const std::string& vm_name,
const std::string& container_name) {
GURL vsh_in_crosh_url =
GenerateVshInCroshUrl(profile, vm_name, container_name);
AppLaunchParams launch_params = GenerateTerminalAppLaunchParams(profile);
OpenApplicationWindow(launch_params, vsh_in_crosh_url);
}
CrostiniManager::RestartId CrostiniManager::RestartCrostini(
Profile* profile,
std::string vm_name,
std::string container_name,
RestartCrostiniCallback callback,
RestartObserver* observer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
return CrostiniRestarterServiceFactory::GetForProfile(profile)->Register(
std::move(vm_name), CryptohomeIdForProfile(profile),
std::move(container_name), ContainerUserNameForProfile(profile),
std::move(callback), observer);
}
void CrostiniManager::AbortRestartCrostini(
Profile* profile,
CrostiniManager::RestartId restart_id) {
CrostiniRestarterServiceFactory::GetForProfile(profile)->Abort(restart_id);
}
void CrostiniManager::AddShutdownContainerCallback(
Profile* profile,
std::string vm_name,
std::string container_name,
ShutdownContainerCallback shutdown_callback) {
shutdown_container_callbacks_.emplace(
std::make_tuple(CryptohomeIdForProfile(profile), vm_name, container_name),
std::move(shutdown_callback));
}
void CrostiniManager::AddInstallLinuxPackageProgressObserver(
Profile* profile,
InstallLinuxPackageProgressObserver* observer) {
install_linux_package_progress_observers_.emplace(
CryptohomeIdForProfile(profile), observer);
}
void CrostiniManager::RemoveInstallLinuxPackageProgressObserver(
Profile* profile,
InstallLinuxPackageProgressObserver* observer) {
auto range = install_linux_package_progress_observers_.equal_range(
CryptohomeIdForProfile(profile));
for (auto it = range.first; it != range.second; ++it) {
if (it->second != observer) {
install_linux_package_progress_observers_.erase(it);
return;
}
}
NOTREACHED();
}
void CrostiniManager::OnCreateDiskImage(
CreateDiskImageCallback callback,
base::Optional<vm_tools::concierge::CreateDiskImageResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to create disk image. Empty response.";
std::move(callback).Run(ConciergeClientResult::CREATE_DISK_IMAGE_FAILED,
base::FilePath());
return;
}
vm_tools::concierge::CreateDiskImageResponse response = reply.value();
if (response.status() != vm_tools::concierge::DISK_STATUS_EXISTS &&
response.status() != vm_tools::concierge::DISK_STATUS_CREATED) {
LOG(ERROR) << "Failed to create disk image: " << response.failure_reason();
std::move(callback).Run(ConciergeClientResult::CREATE_DISK_IMAGE_FAILED,
base::FilePath());
return;
}
std::move(callback).Run(ConciergeClientResult::SUCCESS,
base::FilePath(response.disk_path()));
}
void CrostiniManager::OnDestroyDiskImage(
DestroyDiskImageCallback callback,
base::Optional<vm_tools::concierge::DestroyDiskImageResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to destroy disk image. Empty response.";
std::move(callback).Run(ConciergeClientResult::DESTROY_DISK_IMAGE_FAILED);
return;
}
vm_tools::concierge::DestroyDiskImageResponse response =
std::move(reply).value();
if (response.status() != vm_tools::concierge::DISK_STATUS_DESTROYED &&
response.status() != vm_tools::concierge::DISK_STATUS_DOES_NOT_EXIST) {
LOG(ERROR) << "Failed to destroy disk image: " << response.failure_reason();
std::move(callback).Run(ConciergeClientResult::DESTROY_DISK_IMAGE_FAILED);
return;
}
std::move(callback).Run(ConciergeClientResult::SUCCESS);
}
void CrostiniManager::OnListVmDisks(
ListVmDisksCallback callback,
base::Optional<vm_tools::concierge::ListVmDisksResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to get list of VM disks. Empty response.";
std::move(callback).Run(ConciergeClientResult::LIST_VM_DISKS_FAILED, 0);
return;
}
vm_tools::concierge::ListVmDisksResponse response = std::move(reply).value();
if (!response.success()) {
LOG(ERROR) << "Failed to list VM disks: " << response.failure_reason();
std::move(callback).Run(ConciergeClientResult::LIST_VM_DISKS_FAILED, 0);
return;
}
std::move(callback).Run(ConciergeClientResult::SUCCESS,
response.total_size());
}
void CrostiniManager::OnStartTerminaVm(
std::string owner_id,
std::string vm_name,
StartTerminaVmCallback callback,
base::Optional<vm_tools::concierge::StartVmResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to start termina vm. Empty response.";
std::move(callback).Run(ConciergeClientResult::VM_START_FAILED);
return;
}
vm_tools::concierge::StartVmResponse response = reply.value();
if (!response.success()) {
LOG(ERROR) << "Failed to start VM: " << response.failure_reason();
std::move(callback).Run(ConciergeClientResult::VM_START_FAILED);
return;
}
running_vms_.emplace(std::move(owner_id), std::move(vm_name));
std::move(callback).Run(ConciergeClientResult::SUCCESS);
}
void CrostiniManager::OnStopVm(
std::string owner_id,
std::string vm_name,
StopVmCallback callback,
base::Optional<vm_tools::concierge::StopVmResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to stop termina vm. Empty response.";
std::move(callback).Run(ConciergeClientResult::VM_STOP_FAILED);
return;
}
vm_tools::concierge::StopVmResponse response = reply.value();
if (!response.success()) {
LOG(ERROR) << "Failed to stop VM: " << response.failure_reason();
// TODO(rjwright): Change the service so that "Requested VM does not
// exist" is not an error. "Requested VM does not exist" means that there
// is a disk image for the VM but it is not running, either because it has
// not been started or it has already been stopped. There's no need for
// this to be an error, and making it a success will save us having to
// discriminate on failure_reason here.
if (response.failure_reason() != "Requested VM does not exist") {
std::move(callback).Run(ConciergeClientResult::VM_STOP_FAILED);
return;
}
}
// Remove from running_vms_.
auto key = std::make_pair(std::move(owner_id), std::move(vm_name));
running_vms_.erase(key);
// Remove containers from running_containers_
running_containers_.erase(key);
std::move(callback).Run(ConciergeClientResult::SUCCESS);
}
void CrostiniManager::OnStartContainer(
std::string owner_id,
std::string vm_name,
std::string container_name,
StartContainerCallback callback,
base::Optional<vm_tools::concierge::StartContainerResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to start container in vm. Empty response.";
std::move(callback).Run(ConciergeClientResult::CONTAINER_START_FAILED);
return;
}
vm_tools::concierge::StartContainerResponse response = reply.value();
if (response.status() == vm_tools::concierge::CONTAINER_STATUS_STARTING) {
// The callback will be called when we receive the ContainerStated signal.
start_container_callbacks_.emplace(
std::make_tuple(owner_id, vm_name, container_name),
std::move(callback));
return;
}
if (response.status() != vm_tools::concierge::CONTAINER_STATUS_RUNNING) {
LOG(ERROR) << "Failed to start container: " << response.failure_reason();
std::move(callback).Run(ConciergeClientResult::CONTAINER_START_FAILED);
return;
}
std::move(callback).Run(ConciergeClientResult::SUCCESS);
}
void CrostiniManager::OnContainerStarted(
const vm_tools::cicerone::ContainerStartedSignal& signal) {
// Find the callbacks to call, then erase them from the map.
auto range = start_container_callbacks_.equal_range(std::make_tuple(
signal.owner_id(), signal.vm_name(), signal.container_name()));
for (auto it = range.first; it != range.second; ++it) {
std::move(it->second).Run(ConciergeClientResult::SUCCESS);
}
start_container_callbacks_.erase(range.first, range.second);
running_containers_.emplace(
std::make_pair(signal.owner_id(), signal.vm_name()),
signal.container_name());
}
void CrostiniManager::OnContainerStartupFailed(
const vm_tools::concierge::ContainerStartedSignal& signal) {
// Find the callbacks to call, then erase them from the map.
auto range = start_container_callbacks_.equal_range(std::make_tuple(
signal.owner_id(), signal.vm_name(), signal.container_name()));
for (auto it = range.first; it != range.second; ++it) {
std::move(it->second).Run(ConciergeClientResult::CONTAINER_START_FAILED);
}
start_container_callbacks_.erase(range.first, range.second);
}
void CrostiniManager::OnContainerShutdown(
const vm_tools::cicerone::ContainerShutdownSignal& signal) {
// Find the callbacks to call, then erase them from the map.
auto range = shutdown_container_callbacks_.equal_range(std::make_tuple(
signal.owner_id(), signal.vm_name(), signal.container_name()));
for (auto it = range.first; it != range.second; ++it) {
std::move(it->second).Run();
}
shutdown_container_callbacks_.erase(range.first, range.second);
}
void CrostiniManager::OnInstallLinuxPackageProgress(
const vm_tools::cicerone::InstallLinuxPackageProgressSignal& signal) {
if (signal.progress_percent() < 0 || signal.progress_percent() > 100) {
LOG(ERROR) << "Received install progress with invalid progress of "
<< signal.progress_percent() << "%.";
return;
}
InstallLinuxPackageProgressStatus status;
switch (signal.status()) {
case vm_tools::cicerone::InstallLinuxPackageProgressSignal::SUCCEEDED:
status = InstallLinuxPackageProgressStatus::SUCCEEDED;
break;
case vm_tools::cicerone::InstallLinuxPackageProgressSignal::FAILED:
status = InstallLinuxPackageProgressStatus::FAILED;
break;
case vm_tools::cicerone::InstallLinuxPackageProgressSignal::DOWNLOADING:
status = InstallLinuxPackageProgressStatus::DOWNLOADING;
break;
case vm_tools::cicerone::InstallLinuxPackageProgressSignal::INSTALLING:
status = InstallLinuxPackageProgressStatus::INSTALLING;
break;
default:
NOTREACHED();
}
auto range =
install_linux_package_progress_observers_.equal_range(signal.owner_id());
for (auto it = range.first; it != range.second; ++it) {
it->second->OnInstallLinuxPackageProgress(
signal.vm_name(), signal.container_name(), status,
signal.progress_percent(), signal.failure_details());
}
}
void CrostiniManager::OnLaunchContainerApplication(
LaunchContainerApplicationCallback callback,
base::Optional<vm_tools::cicerone::LaunchContainerApplicationResponse>
reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to launch application. Empty response.";
std::move(callback).Run(
ConciergeClientResult::LAUNCH_CONTAINER_APPLICATION_FAILED);
return;
}
vm_tools::cicerone::LaunchContainerApplicationResponse response =
reply.value();
if (!response.success()) {
LOG(ERROR) << "Failed to launch application: " << response.failure_reason();
std::move(callback).Run(
ConciergeClientResult::LAUNCH_CONTAINER_APPLICATION_FAILED);
return;
}
std::move(callback).Run(ConciergeClientResult::SUCCESS);
}
void CrostiniManager::OnGetContainerAppIcons(
GetContainerAppIconsCallback callback,
base::Optional<vm_tools::cicerone::ContainerAppIconResponse> reply) {
std::vector<Icon> icons;
if (!reply.has_value()) {
LOG(ERROR) << "Failed to get container application icons. Empty response.";
std::move(callback).Run(ConciergeClientResult::DBUS_ERROR, icons);
return;
}
vm_tools::cicerone::ContainerAppIconResponse response = reply.value();
for (auto& icon : *response.mutable_icons()) {
icons.emplace_back(
Icon{.desktop_file_id = std::move(*icon.mutable_desktop_file_id()),
.content = std::move(*icon.mutable_icon())});
}
std::move(callback).Run(ConciergeClientResult::SUCCESS, icons);
}
void CrostiniManager::OnInstallLinuxPackage(
InstallLinuxPackageCallback callback,
base::Optional<vm_tools::cicerone::InstallLinuxPackageResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to install Linux package. Empty response.";
std::move(callback).Run(
ConciergeClientResult::LAUNCH_CONTAINER_APPLICATION_FAILED,
std::string());
return;
}
vm_tools::cicerone::InstallLinuxPackageResponse response = reply.value();
if (response.status() ==
vm_tools::cicerone::InstallLinuxPackageResponse::FAILED) {
LOG(ERROR) << "Failed to install Linux package: "
<< response.failure_reason();
std::move(callback).Run(ConciergeClientResult::INSTALL_LINUX_PACKAGE_FAILED,
response.failure_reason());
return;
}
if (response.status() ==
vm_tools::cicerone::InstallLinuxPackageResponse::INSTALL_ALREADY_ACTIVE) {
LOG(WARNING) << "Failed to install Linux package, install already active.";
std::move(callback).Run(
ConciergeClientResult::INSTALL_LINUX_PACKAGE_ALREADY_ACTIVE,
std::string());
return;
}
std::move(callback).Run(ConciergeClientResult::SUCCESS, std::string());
}
void CrostiniManager::OnGetContainerSshKeys(
GetContainerSshKeysCallback callback,
base::Optional<vm_tools::concierge::ContainerSshKeysResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to get ssh keys. Empty response.";
std::move(callback).Run(ConciergeClientResult::DBUS_ERROR, "", "", "");
return;
}
vm_tools::concierge::ContainerSshKeysResponse response = reply.value();
std::move(callback).Run(ConciergeClientResult::SUCCESS,
response.container_public_key(),
response.host_private_key(), response.hostname());
}
void CrostiniManager::RemoveCrostini(Profile* profile,
std::string vm_name,
std::string container_name,
RemoveCrostiniCallback callback) {
auto crostini_remover = base::MakeRefCounted<CrostiniRemover>(
profile, std::move(vm_name), std::move(container_name),
std::move(callback));
crostini_remover->RemoveCrostini();
}
} // namespace crostini