chromium/chrome/browser/component_updater/cros_component_installer_chromeos.h

// Copyright 2017 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_COMPONENT_UPDATER_CROS_COMPONENT_INSTALLER_CHROMEOS_H_
#define CHROME_BROWSER_COMPONENT_UPDATER_CROS_COMPONENT_INSTALLER_CHROMEOS_H_

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/component_updater/ash/component_manager_ash.h"
#include "components/component_updater/component_installer.h"
#include "components/component_updater/component_updater_service.h"
#include "components/update_client/update_client.h"

namespace component_updater {

// A command-line switch that can also be set from chrome://flags for opting in
// or out of DCHECK binaries for Lacros (where available).
extern const char kPreferDcheckSwitch[];
extern const char kPreferDcheckOptIn[];
extern const char kPreferDcheckOptOut[];

// The name of the directory under DIR_COMPONENT_USER that cros component
// installers puts all of the installed components.
extern const char kComponentsRootPath[];

class ComponentUpdateService;
class MetadataTable;
class CrOSComponentInstaller;

// Describes all metadata needed to dynamically install ChromeOS components.
struct ComponentConfig {
  // This is a client-only identifier for the component.
  const char* name;
  // ComponentInstallerPolicy to use.
  enum class PolicyType {
    kEnvVersion,       // Checks env_version, see below.
    kLacros,           // Uses special lacros compatibility rules.
    kDemoApp,          // Adds demo-mode-specific install attributes
    kGrowthCampaigns,  // Adds growth campaigns install attributes
  };
  PolicyType policy_type;
  // This is used for ABI compatibility checks. It is compared against the
  // 'min_env_version' key in the component's manifest.json file. It uses
  // standard major.minor compat rules, where ABI is compatible if and only if
  // major is matching. The client will send this string to the omaha server,
  // which will filter for a compatible update. Likewise, the client will
  // avoid registering a component if there is an ABI mismatch between the
  // already downloaded component and the expected major version. Must be
  // non-empty for PolicyType::kEnvVersion.
  const char* env_version;
  // This is the app-id of the component, converted from [a-p] hex to [0-f] hex.
  const char* sha2hash;
};

// Base class for all Chrome OS components.
class CrOSComponentInstallerPolicy : public ComponentInstallerPolicy {
 public:
  CrOSComponentInstallerPolicy(
      const ComponentConfig& config,
      CrOSComponentInstaller* cros_component_installer);

  CrOSComponentInstallerPolicy(const CrOSComponentInstallerPolicy&) = delete;
  CrOSComponentInstallerPolicy& operator=(const CrOSComponentInstallerPolicy&) =
      delete;

  ~CrOSComponentInstallerPolicy() override;

  // ComponentInstallerPolicy:
  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
  bool RequiresNetworkEncryption() const override;
  update_client::CrxInstaller::Result OnCustomInstall(
      const base::Value::Dict& manifest,
      const base::FilePath& install_dir) override;
  void OnCustomUninstall() override;
  bool VerifyInstallation(const base::Value::Dict& manifest,
                          const base::FilePath& install_dir) const override;
  base::FilePath GetRelativeInstallDir() const override;
  void GetHash(std::vector<uint8_t>* hash) const override;
  std::string GetName() const override;
  bool AllowUpdates() const override;

 protected:
  const raw_ptr<CrOSComponentInstaller, DanglingUntriaged>
      cros_component_installer_;

 private:
  const std::string name_;
  std::vector<uint8_t> sha2_hash_;
};

// An installer policy that does ABI compatibility checks based on
// ComponentConfig::env_version, see above.
class EnvVersionInstallerPolicy : public CrOSComponentInstallerPolicy {
 public:
  EnvVersionInstallerPolicy(const ComponentConfig& config,
                            CrOSComponentInstaller* cros_component_installer);
  EnvVersionInstallerPolicy(const EnvVersionInstallerPolicy&) = delete;
  EnvVersionInstallerPolicy& operator=(const EnvVersionInstallerPolicy&) =
      delete;
  ~EnvVersionInstallerPolicy() override;

  // ComponentInstallerPolicy:
  void ComponentReady(const base::Version& version,
                      const base::FilePath& path,
                      base::Value::Dict manifest) override;
  update_client::InstallerAttributes GetInstallerAttributes() const override;

 private:
  FRIEND_TEST_ALL_PREFIXES(CrOSComponentInstallerTest, IsCompatibleOrNot);

  static bool IsCompatible(const std::string& env_version_str,
                           const std::string& min_env_version_str);

  const std::string env_version_;
};

// An installer policy for Lacros components, which have unusual version
// compatibility rules. See ComponentReady() implementation.
class LacrosInstallerPolicy : public CrOSComponentInstallerPolicy {
 public:
  LacrosInstallerPolicy(const ComponentConfig& config,
                        CrOSComponentInstaller* cros_component_installer);
  LacrosInstallerPolicy(const LacrosInstallerPolicy&) = delete;
  LacrosInstallerPolicy& operator=(const LacrosInstallerPolicy&) = delete;
  ~LacrosInstallerPolicy() override;

  // ComponentInstallerPolicy:
  void ComponentReady(const base::Version& version,
                      const base::FilePath& path,
                      base::Value::Dict manifest) override;
  update_client::InstallerAttributes GetInstallerAttributes() const override;
  bool SupportsGroupPolicyEnabledComponentUpdates() const override;
  bool AllowUpdates() const override;

  static void SetAshVersionForTest(const char* version);
};

// An installer policy for the ChromeOS Demo Mode app, which includes special
// system-sourced installer attributes in the request to receive customized
// app versions
class DemoAppInstallerPolicy : public CrOSComponentInstallerPolicy {
 public:
  DemoAppInstallerPolicy(const ComponentConfig& config,
                         CrOSComponentInstaller* cros_component_installer);
  DemoAppInstallerPolicy(const DemoAppInstallerPolicy&) = delete;
  DemoAppInstallerPolicy& operator=(const DemoAppInstallerPolicy&) = delete;
  ~DemoAppInstallerPolicy() override;

  // ComponentInstallerPolicy:
  void ComponentReady(const base::Version& version,
                      const base::FilePath& path,
                      base::Value::Dict manifest) override;
  update_client::InstallerAttributes GetInstallerAttributes() const override;
};

// An installer policy for the ChromeOS growth campaigns, which includes special
// system-sourced installer attributes in the request to receive customized
// campaigns versions.
class GrowthCampaignsInstallerPolicy : public CrOSComponentInstallerPolicy {
 public:
  GrowthCampaignsInstallerPolicy(
      const ComponentConfig& config,
      CrOSComponentInstaller* cros_component_installer);
  GrowthCampaignsInstallerPolicy(const GrowthCampaignsInstallerPolicy&) =
      delete;
  GrowthCampaignsInstallerPolicy& operator=(
      const GrowthCampaignsInstallerPolicy&) = delete;
  ~GrowthCampaignsInstallerPolicy() override;

  // ComponentInstallerPolicy:
  void ComponentReady(const base::Version& version,
                      const base::FilePath& path,
                      base::Value::Dict manifest) override;
  update_client::InstallerAttributes GetInstallerAttributes() const override;
};

// This class contains functions used to register and install a component.
class CrOSComponentInstaller : public ComponentManagerAsh {
 public:
  CrOSComponentInstaller(std::unique_ptr<MetadataTable> metadata_table,
                         ComponentUpdateService* component_updater);

  CrOSComponentInstaller(const CrOSComponentInstaller&) = delete;
  CrOSComponentInstaller& operator=(const CrOSComponentInstaller&) = delete;

  // ComponentManagerAsh:
  void SetDelegate(Delegate* delegate) override;
  void Load(const std::string& name,
            MountPolicy mount_policy,
            UpdatePolicy update_policy,
            LoadCallback load_callback) override;
  bool Unload(const std::string& name) override;
  void GetVersion(const std::string& name,
                  base::OnceCallback<void(const base::Version&)>
                      version_callback) const override;
  void RegisterCompatiblePath(const std::string& name,
                              CompatibleComponentInfo info) override;
  void RegisterInstalled() override;

  void UnregisterCompatiblePath(const std::string& name) override;
  base::FilePath GetCompatiblePath(const std::string& name) const override;
  bool IsRegisteredMayBlock(const std::string& name) override;

  // Called when a component is installed/updated.
  // Broadcasts a D-Bus signal for a successful component installation.
  void EmitInstalledSignal(const std::string& component);

  // The load cache contains three pieces of information:
  //   (1) For a given component, whether the load request was successful, a
  //   failure, or in-progress.
  //   (2) If the load request was successful, the file path to the loaded
  //   image.
  //   (3) If the load request is in progress, the callbacks to invoke after the
  //   load request finishes.
  struct LoadInfo {
    LoadInfo();
    ~LoadInfo();
    // If null, then the request is pending.
    std::optional<bool> success;
    // Only populated on success.
    base::FilePath path;
    // Only populated if request is pending. Includes all subsequent callbacks
    // after the first.
    std::vector<LoadCallback> callbacks;
  };

  // Removes the load cache entry for `component_name`. Currently this is done
  // to avoid dispatching loads for old component versions. This can occur when
  // the old version has loaded successfully and is now in the load cache.
  void RemoveLoadCacheEntry(const std::string& component_name);

  // Test-only method for introspection.
  std::map<std::string, LoadInfo>& GetLoadCacheForTesting();

 protected:
  ~CrOSComponentInstaller() override;

 private:
  FRIEND_TEST_ALL_PREFIXES(CrOSComponentInstallerTest, RegisterComponent);
  FRIEND_TEST_ALL_PREFIXES(CrOSComponentInstallerTest,
                           BPPPCompatibleCrOSComponent);
  FRIEND_TEST_ALL_PREFIXES(CrOSComponentInstallerTest, CompatibilityOK);
  FRIEND_TEST_ALL_PREFIXES(CrOSComponentInstallerTest,
                           CompatibilityMissingManifest);
  FRIEND_TEST_ALL_PREFIXES(CrOSComponentInstallerTest, IsCompatibleOrNot);
  FRIEND_TEST_ALL_PREFIXES(CrOSComponentInstallerTest, CompatibleCrOSComponent);

  // Registers a component with a dedicated ComponentUpdateService instance.
  void Register(const ComponentConfig& config,
                base::OnceClosure register_callback);

  // Installs a component with a dedicated ComponentUpdateService instance.
  void Install(const std::string& name,
               UpdatePolicy update_policy,
               MountPolicy mount_policy,
               LoadCallback load_callback);

  // Calls OnDemandUpdate to install the component right after being registered.
  // |id| is the component id generated from its sha2 hash.
  void StartInstall(const std::string& name,
                    const std::string& id,
                    UpdatePolicy update_policy,
                    update_client::Callback install_callback);

  // Calls LoadInternal to load the installed component.
  void FinishInstall(const std::string& name,
                     MountPolicy mount_policy,
                     UpdatePolicy update_policy,
                     LoadCallback load_callback,
                     update_client::Error error);

  // Internal function to load a component.
  void LoadInternal(const std::string& name, LoadCallback load_callback);

  // Calls load_callback and pass in the parameter |result| (component mount
  // point).
  void FinishLoad(LoadCallback load_callback,
                  const std::string& name,
                  std::optional<base::FilePath> result);

  // Calls `version_callback` and pass in the parameter `result` (component
  // version).
  void FinishGetVersion(
      base::OnceCallback<void(const base::Version&)> version_callback,
      std::optional<std::string> result) const;

  // Registers component |configs| to be updated.
  void RegisterN(const std::vector<ComponentConfig>& configs);

  // Checks if the current installed component is compatible given a component
  // |name|.
  bool IsCompatible(const std::string& name) const;

  // Posts a task with the response information for |callback|.
  void DispatchLoadCallback(LoadCallback callback,
                            base::FilePath path,
                            bool success);
  // Repeatedly calls DispatchLoadCallback with failure parameters.
  void DispatchFailedLoads(std::vector<LoadCallback> callbacks);

  // Maps from a compatible component name to its info.
  base::flat_map<std::string, CompatibleComponentInfo> compatible_components_;

  // A weak pointer to a Delegate for emitting D-Bus signal.
  raw_ptr<Delegate> delegate_ = nullptr;

  // Table storing metadata (installs, usage, etc.).
  std::unique_ptr<MetadataTable> metadata_table_;

  // The load cache stores ongoing load requests, as well as the finished
  // results.
  std::map<std::string, LoadInfo> load_cache_;

  const raw_ptr<ComponentUpdateService> component_updater_;

  base::WeakPtrFactory<CrOSComponentInstaller> weak_factory_{this};
};

}  // namespace component_updater

#endif  // CHROME_BROWSER_COMPONENT_UPDATER_CROS_COMPONENT_INSTALLER_CHROMEOS_H_