blob: 348dfdee9f9602b9d4e2b5bdcef7f87edcd67432 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crosapi/browser_loader.h"
#include <utility>
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/crosapi/lacros_selection_loader.h"
#include "chrome/browser/ash/crosapi/rootfs_lacros_loader.h"
#include "chrome/browser/ash/crosapi/stateful_lacros_loader.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/component_updater/cros_component_manager.h"
#include "chromeos/ash/components/cryptohome/system_salt_getter.h"
namespace crosapi {
BrowserLoader::BrowserLoader(
scoped_refptr<component_updater::CrOSComponentManager> manager)
: BrowserLoader(std::make_unique<RootfsLacrosLoader>(),
std::make_unique<StatefulLacrosLoader>(manager)) {}
BrowserLoader::BrowserLoader(
std::unique_ptr<LacrosSelectionLoader> rootfs_lacros_loader,
std::unique_ptr<LacrosSelectionLoader> stateful_lacros_loader)
: rootfs_lacros_loader_(std::move(rootfs_lacros_loader)),
stateful_lacros_loader_(std::move(stateful_lacros_loader)) {}
BrowserLoader::~BrowserLoader() = default;
// static.
bool BrowserLoader::WillLoadStatefulComponentBuilds() {
// If the lacros chrome path is specified BrowserLoader will always attempt to
// load lacros from this path and component manager builds are ignored.
const base::FilePath lacros_chrome_path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
ash::switches::kLacrosChromePath);
if (!lacros_chrome_path.empty())
return false;
// If the lacros selection is forced by the user or by policy to rootfs it
// will always be loaded and stateful component manager builds are ignored.
absl::optional<browser_util::LacrosSelection> lacros_selection =
browser_util::DetermineLacrosSelection();
if (lacros_selection == browser_util::LacrosSelection::kRootfs) {
return false;
}
return true;
}
void BrowserLoader::SelectRootfsLacros(LoadCompletionCallback callback,
bool load_stateful_lacros) {
rootfs_lacros_loader_->Load(
base::BindOnce(&BrowserLoader::OnLoadComplete, weak_factory_.GetWeakPtr(),
std::move(callback), LacrosSelection::kRootfs));
if (load_stateful_lacros) {
stateful_lacros_loader_->Load({});
}
}
void BrowserLoader::SelectStatefulLacros(LoadCompletionCallback callback) {
stateful_lacros_loader_->Load(
base::BindOnce(&BrowserLoader::OnLoadComplete, weak_factory_.GetWeakPtr(),
std::move(callback), LacrosSelection::kStateful));
// Unmount the rootfs lacros-chrome when using stateful lacros-chrome.
// This will keep stateful lacros-chrome only mounted and not hold the rootfs
// lacros-chrome mount until an `Unload`.
rootfs_lacros_loader_->Unload();
}
void BrowserLoader::Load(LoadCompletionCallback callback) {
// Reset lacros selection loaders before reloading.
rootfs_lacros_loader_->Reset();
stateful_lacros_loader_->Reset();
lacros_start_load_time_ = base::TimeTicks::Now();
// TODO(crbug.com/1078607): Remove non-error logging from this class.
LOG(WARNING) << "Starting lacros component load.";
// If the user has specified a path for the lacros-chrome binary, use that
// rather than component manager.
base::FilePath lacros_chrome_path =
base::CommandLine::ForCurrentProcess()->GetSwitchValuePath(
ash::switches::kLacrosChromePath);
if (!lacros_chrome_path.empty()) {
// TODO(cbug.com/1429137): LacrosSelection::kStateful is not appropriate
// here. We should introduce unknown state and set it here.
OnLoadComplete(std::move(callback), LacrosSelection::kDeployedLocally,
base::Version(), lacros_chrome_path);
return;
}
// If the LacrosSelection policy or the user have specified to force using
// stateful or rootfs lacros-chrome binary, force the selection. Otherwise,
// load the newest available binary.
if (absl::optional<browser_util::LacrosSelection> lacros_selection =
browser_util::DetermineLacrosSelection()) {
// TODO(crbug.com/1293250): We should check the version compatibility here,
// too.
switch (lacros_selection.value()) {
case browser_util::LacrosSelection::kRootfs:
SelectRootfsLacros(std::move(callback));
return;
case browser_util::LacrosSelection::kStateful:
SelectStatefulLacros(std::move(callback));
return;
case browser_util::LacrosSelection::kDeployedLocally:
NOTREACHED();
std::move(callback).Run(base::FilePath(),
LacrosSelection::kDeployedLocally,
base::Version());
return;
}
}
// Proceed to load/mount the stateful lacros-chrome binary.
// In the case that the stateful lacros-chrome binary wasn't installed, this
// might take some time.
stateful_lacros_loader_->GetVersion(
base::BindOnce(&BrowserLoader::OnLoadStatefulLacros,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void BrowserLoader::OnLoadStatefulLacros(
LoadCompletionCallback callback,
base::Version stateful_lacros_version) {
// If there currently isn't a stateful lacros-chrome binary, proceed to use
// the rootfs lacros-chrome binary and start the installation of the stateful
// lacros-chrome binary in the background.
if (!stateful_lacros_version.IsValid()) {
SelectRootfsLacros(std::move(callback), /*load_stateful_lacros=*/true);
return;
}
rootfs_lacros_loader_->GetVersion(base::BindOnce(
&BrowserLoader::OnLoadVersionSelection, weak_factory_.GetWeakPtr(),
std::move(callback), std::move(stateful_lacros_version)));
}
void BrowserLoader::OnLoadVersionSelection(
LoadCompletionCallback callback,
base::Version stateful_lacros_version,
base::Version rootfs_lacros_version) {
// Compare the rootfs vs stateful lacros-chrome binary versions.
// If the rootfs lacros-chrome is greater than or equal to the stateful
// lacros-chrome version, prioritize using the rootfs lacros-chrome and let
// stateful lacros-chrome update in the background.
LOG(WARNING) << "Lacros candidates: rootfs=" << rootfs_lacros_version
<< ", stateful=" << stateful_lacros_version;
if (!rootfs_lacros_version.IsValid() && !stateful_lacros_version.IsValid()) {
// Neither rootfs lacros nor stateful lacros are available.
// Returning an empty file path to notify error.
LOG(ERROR) << "No lacros is available";
std::move(callback).Run(base::FilePath(), LacrosSelection::kStateful,
base::Version());
return;
}
LacrosSelection selection;
if (rootfs_lacros_version.IsValid() && stateful_lacros_version.IsValid()) {
selection = rootfs_lacros_version < stateful_lacros_version
? LacrosSelection::kStateful
: LacrosSelection::kRootfs;
} else if (rootfs_lacros_version.IsValid()) {
selection = LacrosSelection::kRootfs;
} else {
DCHECK(stateful_lacros_version.IsValid());
selection = LacrosSelection::kStateful;
}
// Selected lacros may be older than the one which was running in a previous
// sessions, accidentally. For experiment, now we intentionally ignore
// the case and forcibly load the selected one, which is the best we could do
// at this moment.
// TODO(crbug.com/1293250): Check the condition and report it via UMA stats.
switch (selection) {
case LacrosSelection::kRootfs: {
LOG(WARNING) << "rootfs lacros is selected";
SelectRootfsLacros(std::move(callback), /*load_stateful_lacros=*/true);
break;
}
case LacrosSelection::kStateful: {
LOG(WARNING) << "stateful lacros is selected";
SelectStatefulLacros(std::move(callback));
break;
}
case LacrosSelection::kDeployedLocally: {
NOTREACHED();
std::move(callback).Run(
base::FilePath(), LacrosSelection::kDeployedLocally, base::Version());
return;
}
}
}
void BrowserLoader::Unload() {
// Can be called even if Lacros isn't enabled, to clean up the old install.
stateful_lacros_loader_->Unload();
// Unmount the rootfs lacros-chrome if it was mounted.
rootfs_lacros_loader_->Unload();
}
void BrowserLoader::OnLoadComplete(LoadCompletionCallback callback,
LacrosSelection selection,
base::Version version,
const base::FilePath& path) {
// Bail out on empty `path` which implies there was an error on loading
// lacros.
if (path.empty()) {
std::move(callback).Run(base::FilePath(), selection, base::Version());
return;
}
// Fail early if the chrome binary still doesn't exist, such that
// (1) we end up with an error message in Ash's log, and
// (2) BrowserManager doesn't endlessly try to spawn Lacros.
// For example, in the past there have been issues with mounting rootfs Lacros
// that resulted in /run/lacros being empty at this point.
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&base::PathExists,
path.Append(LacrosSelectionLoader::kLacrosChromeBinary)),
base::BindOnce(&BrowserLoader::FinishOnLoadComplete,
weak_factory_.GetWeakPtr(), std::move(callback), path,
selection, std::move(version)));
}
void BrowserLoader::FinishOnLoadComplete(LoadCompletionCallback callback,
const base::FilePath& path,
LacrosSelection selection,
base::Version version,
bool lacros_binary_exists) {
if (!lacros_binary_exists) {
LOG(ERROR) << "Failed to find chrome binary at " << path;
std::move(callback).Run(base::FilePath(), selection, base::Version());
return;
}
base::UmaHistogramMediumTimes(
"ChromeOS.Lacros.LoadTime",
base::TimeTicks::Now() - lacros_start_load_time_);
// Log the path on success.
LOG(WARNING) << "Loaded lacros image at " << path;
std::move(callback).Run(path, selection, std::move(version));
}
} // namespace crosapi