|  | // 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 |