| // Copyright 2022 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/bruschetta/bruschetta_launcher.h" |
| |
| #include <memory> |
| #include <optional> |
| |
| #include "base/functional/callback.h" |
| #include "base/logging.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/strcat.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ash/bruschetta/bruschetta_service.h" |
| #include "chrome/browser/ash/bruschetta/bruschetta_util.h" |
| #include "chrome/browser/ash/guest_os/guest_os_dlc_helper.h" |
| #include "chrome/browser/ash/guest_os/guest_os_pref_names.h" |
| #include "chrome/browser/ash/guest_os/guest_os_session_tracker.h" |
| #include "chrome/browser/ash/guest_os/public/types.h" |
| #include "chrome/browser/ash/profiles/profile_helper.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chromeos/ash/components/dbus/concierge/concierge_client.h" |
| #include "chromeos/ash/components/dbus/vm_concierge/concierge_service.pb.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace bruschetta { |
| |
| namespace { |
| |
| // TODO(b/233289313): Once we have an installer and multiple Bruschettas this |
| // needs to be dynamic, but for now we hardcode the same path that the go/brua |
| // instructions have people using for the alpha, and the same disk name that |
| // people following the instructions will have (base64 encoded "bru"). |
| const char kDiskName[] = "YnJ1.img"; |
| |
| } // namespace |
| |
| // Wrapper class for dispatching `OnTimeout` calls so we can cancel the timeout |
| // by destroying the wrapper instance. |
| class BruschettaLauncher::Timeout { |
| public: |
| explicit Timeout(BruschettaLauncher* launcher) : launcher_(launcher) {} |
| void OnTimeout() { launcher_->OnTimeout(); } |
| |
| // BruschettaLauncher owns us so it will always outlive us, hence raw_ptr is |
| // fine. |
| raw_ptr<BruschettaLauncher> launcher_; |
| |
| // Must be last. |
| base::WeakPtrFactory<Timeout> weak_factory_{this}; |
| }; |
| |
| BruschettaLauncher::BruschettaLauncher(std::string vm_name, Profile* profile) |
| : vm_name_(vm_name), profile_(profile) {} |
| BruschettaLauncher::~BruschettaLauncher() = default; |
| |
| void BruschettaLauncher::EnsureRunning( |
| base::OnceCallback<void(BruschettaResult)> callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| bool launch_in_progress = false; |
| if (!callbacks_.empty()) { |
| launch_in_progress = true; |
| } |
| callbacks_.AddUnsafe(std::move(callback)); |
| if (!launch_in_progress) { |
| EnsureToolsDlcInstalled(); |
| timeout_ = std::make_unique<Timeout>(this); |
| // If we're not complete after 4 minutes time out the entire launch. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&BruschettaLauncher::Timeout::OnTimeout, |
| timeout_->weak_factory_.GetWeakPtr()), |
| base::Seconds(240)); |
| } |
| } |
| |
| void BruschettaLauncher::EnsureToolsDlcInstalled() { |
| in_progress_dlc_ = std::make_unique<guest_os::GuestOsDlcInstallation>( |
| kToolsDlc, |
| base::BindOnce(&BruschettaLauncher::OnMountToolsDlc, |
| weak_factory_.GetWeakPtr()), |
| base::DoNothing()); |
| } |
| |
| void BruschettaLauncher::OnMountToolsDlc( |
| guest_os::GuestOsDlcInstallation::Result install_result) { |
| in_progress_dlc_.reset(); |
| if (!install_result.has_value()) { |
| LOG(ERROR) << "Error installing tools DLC: " << install_result.error(); |
| Finish(BruschettaResult::kDlcInstallError); |
| return; |
| } |
| |
| EnsureFirmwareDlcInstalled(); |
| } |
| |
| void BruschettaLauncher::EnsureFirmwareDlcInstalled() { |
| in_progress_dlc_ = std::make_unique<guest_os::GuestOsDlcInstallation>( |
| kUefiDlc, |
| base::BindOnce(&BruschettaLauncher::OnMountFirmwareDlc, |
| weak_factory_.GetWeakPtr()), |
| base::DoNothing()); |
| } |
| |
| void BruschettaLauncher::OnMountFirmwareDlc( |
| guest_os::GuestOsDlcInstallation::Result install_result) { |
| if (!install_result.has_value()) { |
| LOG(ERROR) << "Error installing firmware DLC: " << install_result.error(); |
| Finish(BruschettaResult::kDlcInstallError); |
| return; |
| } |
| |
| EnsureConciergeAvailable(); |
| } |
| |
| void BruschettaLauncher::EnsureConciergeAvailable() { |
| auto* client = ash::ConciergeClient::Get(); |
| if (!client) { |
| LOG(ERROR) << "Error connecting to concierge. Client is NULL."; |
| Finish(BruschettaResult::kConciergeUnavailable); |
| return; |
| } |
| |
| client->WaitForServiceToBeAvailable(base::BindOnce( |
| &BruschettaLauncher::OnConciergeAvailable, weak_factory_.GetWeakPtr())); |
| } |
| |
| void BruschettaLauncher::OnConciergeAvailable(bool service_is_available) { |
| if (!service_is_available) { |
| LOG(ERROR) << "Error connecting to concierge. Service is not available."; |
| Finish(BruschettaResult::kConciergeUnavailable); |
| return; |
| } |
| |
| StartVm(); |
| } |
| |
| void BruschettaLauncher::StartVm() { |
| auto* client = ash::ConciergeClient::Get(); |
| if (!client) { |
| LOG(ERROR) << "Error connecting to concierge. Client is NULL."; |
| Finish(BruschettaResult::kStartVmFailed); |
| return; |
| } |
| |
| const std::string config_id = |
| GetContainerPrefValue(profile_, MakeBruschettaId(vm_name_), |
| guest_os::prefs::kBruschettaConfigId) |
| ->GetString(); |
| RunningVmPolicy launch_policy; |
| auto opt = GetLaunchPolicyForConfig(profile_, config_id); |
| if (!opt.has_value()) { |
| // Policy prohibits starting the VM, so don't. |
| LOG(ERROR) << "Starting VM prohibited by policy"; |
| Finish(BruschettaResult::kForbiddenByPolicy); |
| return; |
| } else { |
| launch_policy = *opt; |
| } |
| |
| std::string user_hash = |
| ash::ProfileHelper::GetUserIdHashFromProfile(profile_); |
| std::string vm_username = GetVmUsername(profile_); |
| vm_tools::concierge::StartVmRequest request; |
| request.set_start_termina(false); |
| request.set_name(vm_name_); |
| request.mutable_vm()->set_tools_dlc_id(kToolsDlc); |
| request.mutable_vm()->set_bios_dlc_id(kUefiDlc); |
| request.set_owner_id(user_hash); |
| request.set_vm_username(vm_username); |
| request.set_start_termina(false); |
| request.set_timeout(240); |
| request.set_vtpm_proxy(launch_policy.vtpm_enabled); |
| |
| auto* disk = request.mutable_disks()->Add(); |
| *disk->mutable_path() = |
| base::StrCat({"/run/daemon-store/crosvm/", user_hash, "/", kDiskName}); |
| disk->set_writable(true); |
| disk->set_do_mount(false); |
| |
| client->StartVm(request, |
| base::BindOnce(&BruschettaLauncher::OnStartVm, |
| weak_factory_.GetWeakPtr(), launch_policy)); |
| } |
| |
| void BruschettaLauncher::OnStartVm( |
| RunningVmPolicy launch_policy, |
| std::optional<vm_tools::concierge::StartVmResponse> response) { |
| if (!response || !response->success()) { |
| if (response) { |
| LOG(ERROR) << "Error starting VM, got status: " << response->status() |
| << " and reason " << response->failure_reason(); |
| } else { |
| LOG(ERROR) << "Error starting VM: no response from Concierge"; |
| } |
| Finish(BruschettaResult::kStartVmFailed); |
| return; |
| } |
| |
| BruschettaService::GetForProfile(profile_)->RegisterVmLaunch(vm_name_, |
| launch_policy); |
| |
| auto* tracker = guest_os::GuestOsSessionTracker::GetForProfile(profile_); |
| subscription_ = tracker->RunOnceContainerStarted( |
| guest_os::GuestId{guest_os::VmType::BRUSCHETTA, vm_name_, "penguin"}, |
| base::BindOnce(&BruschettaLauncher::OnContainerRunning, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void BruschettaLauncher::OnContainerRunning(guest_os::GuestInfo info) { |
| Finish(BruschettaResult::kSuccess); |
| } |
| |
| void BruschettaLauncher::OnTimeout() { |
| subscription_.reset(); |
| Finish(BruschettaResult::kTimeout); |
| |
| // We don't actually abort or cancel the launch, let it keep going in the |
| // background in case it's really slow for some reason then the next time they |
| // try it might succeed. |
| } |
| |
| void BruschettaLauncher::Finish(BruschettaResult result) { |
| base::UmaHistogramEnumeration("Bruschetta.LaunchResult", result); |
| callbacks_.Notify(result); |
| timeout_.reset(); |
| } |
| |
| base::WeakPtr<BruschettaLauncher> BruschettaLauncher::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| } // namespace bruschetta |