| // 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::SuccessFailureResponse> 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_ |