// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ASH_PLUGIN_VM_PLUGIN_VM_INSTALLER_H_
#define CHROME_BROWSER_ASH_PLUGIN_VM_PLUGIN_VM_INSTALLER_H_
#include <memory>
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/ash/guest_os/guest_os_dlc_helper.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_license_checker.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/vm_concierge/concierge_service.pb.h"
#include "components/download/public/background_service/download_params.h"
#include "components/keyed_service/core/keyed_service.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/wake_lock.mojom.h"
namespace download {
class BackgroundDownloadService;
struct CompletionInfo;
} // namespace download
class Profile;
namespace plugin_vm {
class PluginVmDriveImageDownloadService;
// PluginVmInstaller is responsible for installing Plugin VM, including
// downloading the image from url specified by the user policy, and importing
// the downloaded image archive using concierge D-Bus services.
//
// The installation flow is fairly linear. On top of cancelled and failed
// installs, the branches are:
// - OnListVmDisks() exits if a VM already exists (installed via vmc).
// - StartDownload() uses a PluginVmDriveImageDownloadService for images hosted
// on Drive, and the DownloadService for all other images.
// - OnFDPrepared() calls concierge's CreateDiskImage() or ImportDiskImage()
// depending on whether an .iso (new VM) or archive (prepared VM) is
// downloaded.
class PluginVmInstaller : public KeyedService,
public ash::ConciergeClient::DiskImageObserver {
public:
// FailureReasons values are logged to UMA and shown to users. Do not change
// or re-use enum values.
enum class FailureReason {
// LOGIC_ERROR = 0,
SIGNAL_NOT_CONNECTED = 1,
OPERATION_IN_PROGRESS = 2,
NOT_ALLOWED = 3,
INVALID_IMAGE_URL = 4,
UNEXPECTED_DISK_IMAGE_STATUS = 5,
INVALID_DISK_IMAGE_STATUS_RESPONSE = 6,
DOWNLOAD_FAILED_UNKNOWN = 7,
DOWNLOAD_FAILED_NETWORK = 8,
DOWNLOAD_FAILED_ABORTED = 9,
HASH_MISMATCH = 10,
DISPATCHER_NOT_AVAILABLE = 11,
CONCIERGE_NOT_AVAILABLE = 12,
COULD_NOT_OPEN_IMAGE = 13,
INVALID_IMPORT_RESPONSE = 14,
IMAGE_IMPORT_FAILED = 15,
// DLC_DOWNLOAD_FAILED = 16,
// DLC_DOWNLOAD_NOT_STARTED = 17,
DLC_INTERNAL = 18,
DLC_UNSUPPORTED = 19,
DLC_BUSY = 20,
DLC_NEED_REBOOT = 21,
DLC_NEED_SPACE = 22,
INSUFFICIENT_DISK_SPACE = 23, // Pre-check based on policy.
INVALID_LICENSE = 24,
OFFLINE = 25,
LIST_VM_DISKS_FAILED = 26,
OUT_OF_DISK_SPACE = 27, // Hard error, we actually ran out of space.
DOWNLOAD_FAILED_401 = 28, // Common HTTP status codes for errors.
DOWNLOAD_FAILED_403 = 29,
DOWNLOAD_FAILED_404 = 30,
// Download appeared to succeed but downloaded image size was unexpected
DOWNLOAD_SIZE_MISMATCH = 31,
// Image with the right name exists, but in a wrong location.
EXISTING_IMAGE_INVALID = 32,
kMaxValue = EXISTING_IMAGE_INVALID,
};
enum class InstallingState {
kInactive,
kCheckingLicense,
kCheckingForExistingVm,
kCheckingDiskSpace,
kDownloadingDlc,
kStartingDispatcher,
kDownloadingImage,
kImporting,
};
static constexpr int64_t kImageSizeUnknown = -1;
static constexpr int64_t kImageSizeError = -2;
// Observer for installation progress.
class Observer {
public:
virtual ~Observer() = default;
// Fired on transitions to any state aside from kInactive.
virtual void OnStateUpdated(InstallingState new_state) = 0;
virtual void OnProgressUpdated(double fraction_complete) = 0;
virtual void OnDownloadProgressUpdated(uint64_t bytes_downloaded,
int64_t content_length) = 0;
// Exactly one of these will be fired once installation has finished,
// successfully or otherwise.
virtual void OnVmExists() = 0;
virtual void OnCreated() = 0;
virtual void OnImported() = 0;
virtual void OnError(FailureReason reason) = 0;
virtual void OnCancelFinished() = 0;
};
explicit PluginVmInstaller(Profile* profile);
PluginVmInstaller(const PluginVmInstaller&) = delete;
PluginVmInstaller& operator=(const PluginVmInstaller&) = delete;
~PluginVmInstaller() override;
// Start the installation. Progress updates will be sent to the observer.
// Returns a FailureReason if the installation couldn't be started.
std::optional<FailureReason> Start();
// Cancel the installation, and calls OnCancelFinished() when done. Some steps
// cannot be directly cancelled, in which case we wait for the step to
// complete and then abort the installation.
// DLC will not be removed, but the downloaded image will be.
void Cancel();
// Returns whether the installer is already running.
bool IsProcessing();
void SetObserver(Observer* observer);
void RemoveObserver();
std::string GetCurrentDownloadGuid();
// Used by PluginVmImageDownloadClient and PluginVmDriveImageDownloadService,
// other classes should not call into here.
void OnDownloadStarted();
void OnDownloadProgressUpdated(uint64_t bytes_downloaded,
int64_t content_length);
void OnDownloadCompleted(const download::CompletionInfo& info);
void OnDownloadFailed(FailureReason reason);
// ConciergeClient::DiskImageObserver:
void OnDiskImageProgress(
const vm_tools::concierge::DiskImageStatusResponse& signal) override;
// Helper function that returns whether the hash of the downloaded image
// matches the hash specified in policy.
// Public for testing purposes.
bool VerifyDownload(const std::string& download_hash);
// Returns free disk space required to install Plugin VM in bytes.
int64_t RequiredFreeDiskSpace();
void SkipLicenseCheckForTesting() { skip_license_check_for_testing_ = true; }
void SetFreeDiskSpaceForTesting(int64_t bytes) {
free_disk_space_for_testing_ = bytes;
}
void SetDownloadServiceForTesting(
download::BackgroundDownloadService* download_service);
void SetDownloadedImageForTesting(const base::FilePath& downloaded_image);
void SetDriveDownloadServiceForTesting(
std::unique_ptr<PluginVmDriveImageDownloadService>
drive_download_service);
private:
enum class State {
kIdle,
kInstalling,
kCancelling,
};
// The entire installation flow!
void CheckLicense();
void OnLicenseChecked(bool license_is_valid);
void CheckForExistingVm();
void OnConciergeAvailable(bool success);
void OnListVmDisks(
std::optional<vm_tools::concierge::ListVmDisksResponse> response);
void CheckDiskSpace();
void OnAvailableDiskSpace(std::optional<int64_t> bytes);
void StartDlcDownload();
// Called repeatedly.
void OnDlcDownloadProgressUpdated(double progress);
void OnDlcDownloadCompleted(
guest_os::GuestOsDlcInstallation::Result install_result);
void StartDispatcher();
void OnDispatcherStarted(bool success);
void StartDownload();
// This is only called in the DownloadService flow.
void OnStartDownload(const std::string& download_guid,
download::DownloadParams::StartResult start_result);
// Download progress/completion happens in the public methods OnDownload*().
void StartImport();
void OnImageTypeDetected(bool is_iso_image);
// Calls CreateDiskImage or ImportDiskImage, depending on whether we are
// creating a new VM from an ISO, or importing a prepared VM image.
void OnFDPrepared(std::optional<base::ScopedFD> maybe_fd);
// Callback for the concierge CreateDiskImage/ImportDiskImage calls. The
// import has just started (unless that failed).
template <typename ReplyType>
void OnImportDiskImage(std::optional<ReplyType> reply);
// Progress updates are sent to OnDiskImageProgress(). After we get a signal
// that the import is finished successfully, we make one final call to
// concierge's DiskImageStatus method to get a final resolution.
void RequestFinalStatus();
void OnFinalDiskImageStatus(
std::optional<vm_tools::concierge::DiskImageStatusResponse> response);
// Finishes the processing of installation. If |failure_reason| has a value,
// then the import has failed, otherwise it was successful.
void OnImported(std::optional<FailureReason> failure_reason);
// End of the install flow!
void UpdateInstallingState(InstallingState installing_state);
// Only used on the long-running steps: kDownloadingDlc, kDownloadingImage,
// kImporting.
void UpdateProgress(double state_progress);
// One of InstallFailed() and InstallFinished() will be called at the end of
// each successfully started installation. These clean up state and log
// histograms.
void InstallFailed(FailureReason reason);
// Callers also need to call the appropriate observer functions indicating
// success type.
void InstallFinished();
// Cancels the image download. The partial download will be deleted.
void CancelDownload();
// Calls concierge to cancel the import.
void CancelImport();
// Callback for the concierge CancelDiskImageOperation call.
void OnImportDiskImageCancelled(
std::optional<vm_tools::concierge::CancelDiskImageResponse> response);
// Called once cancel is completed, firing the OnCancelFinished() observer
// event.
void CancelFinished();
// Stringify for logging purposes.
static std::string GetStateName(State state);
static std::string GetInstallingStateName(InstallingState state);
GURL GetPluginVmImageDownloadUrl();
download::DownloadParams GetDownloadParams(const GURL& url);
void RemoveTemporaryImageIfExists();
void OnTemporaryImageRemoved(bool success);
device::mojom::WakeLock* GetWakeLock();
raw_ptr<Profile> profile_ = nullptr;
raw_ptr<Observer, DanglingUntriaged> observer_ = nullptr;
raw_ptr<download::BackgroundDownloadService, DanglingUntriaged>
download_service_ = nullptr;
State state_ = State::kIdle;
InstallingState installing_state_ = InstallingState::kInactive;
std::string current_download_guid_;
base::FilePath downloaded_image_;
// Used to identify our running import with concierge.
std::string current_import_command_uuid_;
int64_t expected_image_size_;
int64_t downloaded_image_size_;
bool creating_new_vm_ = false;
double progress_ = 0;
std::unique_ptr<PluginVmDriveImageDownloadService> drive_download_service_;
std::unique_ptr<PluginVmLicenseChecker> license_checker_;
std::unique_ptr<guest_os::GuestOsDlcInstallation> dlc_installation_;
bool using_drive_download_service_ = false;
bool skip_license_check_for_testing_ = false;
// -1 indicates not set
int64_t free_disk_space_for_testing_ = -1;
std::optional<base::FilePath> downloaded_image_for_testing_;
// Keep the system awake during installation.
mojo::Remote<device::mojom::WakeLock> wake_lock_;
base::WeakPtrFactory<PluginVmInstaller> weak_ptr_factory_{this};
};
} // namespace plugin_vm
#endif // CHROME_BROWSER_ASH_PLUGIN_VM_PLUGIN_VM_INSTALLER_H_