| // Copyright 2023 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/rootfs_lacros_loader.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/files/file_util.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/path_service.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/ash/crosapi/browser_util.h" |
| #include "chromeos/ash/components/dbus/upstart/upstart_client.h" |
| #include "components/user_manager/user_manager.h" |
| |
| namespace crosapi { |
| |
| namespace { |
| |
| // The rootfs lacros-chrome binary related files. |
| constexpr char kLacrosMetadata[] = "metadata.json"; |
| |
| // The rootfs lacros-chrome binary related paths. |
| // Must be kept in sync with lacros upstart conf files. |
| constexpr char kRootfsLacrosMountPoint[] = "/run/lacros"; |
| constexpr char kRootfsLacrosPath[] = "/opt/google/lacros"; |
| |
| // Lacros upstart jobs for mounting/unmounting the lacros-chrome image. |
| // The conversion of upstart job names to dbus object paths is undocumented. See |
| // function nih_dbus_path in libnih for the implementation. |
| constexpr char kLacrosMounterUpstartJob[] = "lacros_2dmounter"; |
| constexpr char kLacrosUnmounterUpstartJob[] = "lacros_2dunmounter"; |
| |
| } // namespace |
| |
| RootfsLacrosLoader::RootfsLacrosLoader() |
| : RootfsLacrosLoader( |
| ash::UpstartClient::Get(), |
| base::FilePath(kRootfsLacrosPath).Append(kLacrosMetadata)) {} |
| |
| RootfsLacrosLoader::RootfsLacrosLoader(ash::UpstartClient* upstart_client, |
| base::FilePath metadata_path) |
| : upstart_client_(upstart_client), |
| metadata_path_(std::move(metadata_path)) {} |
| |
| RootfsLacrosLoader::~RootfsLacrosLoader() = default; |
| |
| void RootfsLacrosLoader::Load(LoadCompletionCallback callback, bool forced) { |
| CHECK(state_ == State::kNotLoaded || |
| state_ == State::kVersionReadyButNotLoaded) |
| << state_; |
| LOG(WARNING) << "Loading rootfs lacros."; |
| |
| if (state_ == State::kVersionReadyButNotLoaded) { |
| OnVersionReadyToLoad(std::move(callback), version_.value()); |
| return; |
| } |
| |
| // Calculate `version_` before start loading. |
| // It's not calculated yet in case when lacros selection is defined by |
| // selection policy or stateful lacros is not installed. |
| state_ = State::kReadingVersion; |
| GetVersionInternal(base::BindOnce(&RootfsLacrosLoader::OnVersionReadyToLoad, |
| weak_factory_.GetWeakPtr(), |
| std::move(callback))); |
| } |
| |
| void RootfsLacrosLoader::Unload(base::OnceClosure callback) { |
| switch (state_) { |
| case State::kNotLoaded: |
| case State::kVersionReadyButNotLoaded: |
| case State::kUnloaded: |
| // Nothing to unload if it's not loaded or already unloaded. |
| state_ = State::kUnloaded; |
| std::move(callback).Run(); |
| break; |
| case State::kReadingVersion: |
| case State::kLoading: |
| case State::kUnloading: |
| // If loader is busy, wait Unload until the current task has finished. |
| pending_unload_ = |
| base::BindOnce(&RootfsLacrosLoader::Unload, |
| weak_factory_.GetWeakPtr(), std::move(callback)); |
| break; |
| case State::kLoaded: |
| state_ = State::kUnloading; |
| |
| upstart_client_->StartJob( |
| kLacrosUnmounterUpstartJob, {}, |
| base::BindOnce(&RootfsLacrosLoader::OnUnloadCompleted, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| } |
| |
| void RootfsLacrosLoader::GetVersion( |
| base::OnceCallback<void(const base::Version&)> callback) { |
| CHECK_EQ(state_, State::kNotLoaded) << state_; |
| state_ = State::kReadingVersion; |
| GetVersionInternal(std::move(callback)); |
| } |
| |
| bool RootfsLacrosLoader::IsUnloading() const { |
| return state_ == State::kUnloading; |
| } |
| |
| bool RootfsLacrosLoader::IsUnloaded() const { |
| return state_ == State::kUnloaded; |
| } |
| |
| void RootfsLacrosLoader::GetVersionInternal( |
| base::OnceCallback<void(const base::Version&)> callback) { |
| CHECK_EQ(state_, State::kReadingVersion) << state_; |
| CHECK(!version_); |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce(&browser_util::GetRootfsLacrosVersionMayBlock, |
| metadata_path_), |
| base::BindOnce(&RootfsLacrosLoader::OnGetVersion, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void RootfsLacrosLoader::OnGetVersion( |
| base::OnceCallback<void(const base::Version&)> callback, |
| base::Version version) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK_EQ(state_, State::kReadingVersion) << state_; |
| |
| version_ = version; |
| state_ = State::kVersionReadyButNotLoaded; |
| |
| if (pending_unload_) { |
| LOG(WARNING) << "Unload is requested during getting version of rootfs."; |
| std::move(callback).Run(base::Version()); |
| std::move(pending_unload_).Run(); |
| return; |
| } |
| |
| std::move(callback).Run(version_.value()); |
| } |
| |
| void RootfsLacrosLoader::OnVersionReadyToLoad(LoadCompletionCallback callback, |
| const base::Version& version) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK_EQ(state_, State::kVersionReadyButNotLoaded) << state_; |
| |
| if (pending_unload_) { |
| LOG(WARNING) << "Unload is requested during loading rootfs."; |
| std::move(callback).Run(base::Version(), base::FilePath()); |
| std::move(pending_unload_).Run(); |
| return; |
| } |
| |
| // `version_` must be already filled by `version`. |
| CHECK(version_.has_value() && |
| ((!version_.value().IsValid() && !version.IsValid()) || |
| (version_.value() == version))); |
| |
| state_ = State::kLoading; |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, {base::MayBlock()}, |
| base::BindOnce( |
| &base::PathExists, |
| base::FilePath(kRootfsLacrosMountPoint).Append(kLacrosChromeBinary)), |
| base::BindOnce(&RootfsLacrosLoader::OnMountCheckToLoad, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void RootfsLacrosLoader::OnMountCheckToLoad(LoadCompletionCallback callback, |
| bool already_mounted) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK_EQ(state_, State::kLoading) << state_; |
| |
| if (pending_unload_) { |
| LOG(WARNING) << "Unload is requested during loading rootfs."; |
| state_ = State::kVersionReadyButNotLoaded; |
| std::move(callback).Run(base::Version(), base::FilePath()); |
| std::move(pending_unload_).Run(); |
| return; |
| } |
| |
| if (already_mounted) { |
| OnUpstartLacrosMounter(std::move(callback), true); |
| return; |
| } |
| |
| std::vector<std::string> job_env; |
| if (user_manager::UserManager::Get()->IsLoggedInAsGuest()) { |
| job_env.emplace_back("USE_SESSION_NAMESPACE=true"); |
| } |
| |
| upstart_client_->StartJob( |
| kLacrosMounterUpstartJob, job_env, |
| base::BindOnce(&RootfsLacrosLoader::OnUpstartLacrosMounter, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void RootfsLacrosLoader::OnUpstartLacrosMounter(LoadCompletionCallback callback, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK_EQ(state_, State::kLoading) << state_; |
| state_ = State::kLoaded; |
| |
| LOG_IF(WARNING, !success) << "Upstart failed to mount rootfs lacros."; |
| |
| if (pending_unload_) { |
| LOG(WARNING) << "Unload is requested during loading rootfs."; |
| std::move(callback).Run(base::Version(), base::FilePath()); |
| std::move(pending_unload_).Run(); |
| return; |
| } |
| |
| // `version_` must be calculated before coming here. |
| CHECK(version_.has_value()); |
| std::move(callback).Run( |
| version_.value(), |
| // If mounting wasn't successful, return a empty mount point to indicate |
| // failure. `OnLoadComplete` handles empty mount points and forwards the |
| // errors on the return callbacks. |
| success ? base::FilePath(kRootfsLacrosMountPoint) : base::FilePath()); |
| } |
| |
| void RootfsLacrosLoader::OnUnloadCompleted(base::OnceClosure callback, |
| bool success) { |
| // Proceed anyway regardless of unload success. |
| if (!success) { |
| LOG(ERROR) << "Failed to unload rootfs lacros"; |
| } |
| |
| CHECK_EQ(state_, State::kUnloading) << state_; |
| state_ = State::kUnloaded; |
| std::move(callback).Run(); |
| } |
| |
| std::ostream& operator<<(std::ostream& ostream, |
| RootfsLacrosLoader::State state) { |
| switch (state) { |
| case RootfsLacrosLoader::State::kNotLoaded: |
| return ostream << "NotLoaded"; |
| case RootfsLacrosLoader::State::kReadingVersion: |
| return ostream << "ReadingVersion"; |
| case RootfsLacrosLoader::State::kVersionReadyButNotLoaded: |
| return ostream << "VersionReadyButNotLoaded"; |
| case RootfsLacrosLoader::State::kLoading: |
| return ostream << "Loading"; |
| case RootfsLacrosLoader::State::kLoaded: |
| return ostream << "Loaded"; |
| case RootfsLacrosLoader::State::kUnloading: |
| return ostream << "Unloading"; |
| case RootfsLacrosLoader::State::kUnloaded: |
| return ostream << "Unloaded"; |
| } |
| } |
| |
| } // namespace crosapi |