chromium/chrome/updater/win/installer_api.cc

// Copyright 2020 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/updater/win/installer_api.h"

#include <algorithm>
#include <iterator>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check_op.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/win/registry.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/enum_traits.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/win_constants.h"
#include "components/update_client/update_client.h"

namespace updater {
namespace {

// Creates or opens the registry ClientState subkey for the `app_id`. `regsam`
// must contain the KEY_WRITE access right for the creation of the subkey to
// succeed.
std::optional<base::win::RegKey> ClientStateAppKeyCreate(
    UpdaterScope updater_scope,
    const std::string& app_id,
    REGSAM regsam) {
  std::wstring subkey;
  if (!base::UTF8ToWide(app_id.c_str(), app_id.size(), &subkey)) {
    return std::nullopt;
  }
  base::win::RegKey key(UpdaterScopeToHKeyRoot(updater_scope), CLIENT_STATE_KEY,
                        Wow6432(regsam));
  if (!key.Valid() ||
      key.CreateKey(subkey.c_str(), Wow6432(regsam)) != ERROR_SUCCESS) {
    return std::nullopt;
  }
  return key;
}

bool RegCopyValue(base::win::RegKey& from_key,
                  const std::wstring& from_value_name,
                  base::win::RegKey& to_key,
                  const std::wstring& to_value_name) {
  DWORD size = 0;
  if (from_key.ReadValue(from_value_name.c_str(), nullptr, &size, nullptr) !=
      ERROR_SUCCESS) {
    return false;
  }

  std::vector<char> raw_value(size);
  DWORD dtype = 0;
  if (from_key.ReadValue(from_value_name.c_str(), raw_value.data(), &size,
                         &dtype) != ERROR_SUCCESS) {
    return false;
  }

  if (to_key.WriteValue(to_value_name.c_str(), raw_value.data(), size, dtype) !=
      ERROR_SUCCESS) {
    PLOG(WARNING) << "could not write: " << to_value_name;
    return false;
  }
  return true;
}

bool RegRenameValue(base::win::RegKey& key,
                    const std::wstring& from_value_name,
                    const std::wstring& to_value_name) {
  if (!RegCopyValue(key, from_value_name, key, to_value_name)) {
    return false;
  }
  key.DeleteValue(from_value_name.c_str());
  return true;
}

void PersistLastInstallerResultValues(UpdaterScope updater_scope,
                                      const std::string& app_id) {
  std::optional<base::win::RegKey> key =
      ClientStateAppKeyOpen(updater_scope, app_id, KEY_READ | KEY_WRITE);
  if (!key) {
    return;
  }

  // Rename `InstallerResultXXX` values to `LastXXX`.
  for (const auto& [rename_from, rename_to] : {
           std::make_pair(kRegValueInstallerResult,
                          kRegValueLastInstallerResult),
           std::make_pair(kRegValueInstallerError, kRegValueLastInstallerError),
           std::make_pair(kRegValueInstallerExtraCode1,
                          kRegValueLastInstallerExtraCode1),
           std::make_pair(kRegValueInstallerResultUIString,
                          kRegValueLastInstallerResultUIString),
           std::make_pair(kRegValueInstallerSuccessLaunchCmdLine,
                          kRegValueLastInstallerSuccessLaunchCmdLine),
       }) {
    RegRenameValue(key.value(), rename_from, rename_to);
  }

  // The updater copies and retains the last installer result values as a backup
  // under the `UPDATER_KEY`, and the values under the `UPDATER_KEY` are not
  // deleted when the updater uninstalls itself. The MSI installer uses this
  // information to display the installer result in cases where an error occurs
  // during app installation and no other apps are installed, and the updater
  // immediately uninstalls itself and deletes the `ClientState` registry key
  // under which the last installer results are usually stored.
  if (base::win::RegKey updater_key(UpdaterScopeToHKeyRoot(updater_scope),
                                    UPDATER_KEY, Wow6432(KEY_WRITE));
      updater_key.Valid()) {
    for (const wchar_t* const reg_value_last_installer :
         kRegValuesLastInstaller) {
      RegCopyValue(key.value(), reg_value_last_installer, updater_key,
                   reg_value_last_installer);
    }
  }
}

bool ClientStateAppKeyExists(UpdaterScope updater_scope,
                             const std::string& app_id) {
  return base::win::RegKey(UpdaterScopeToHKeyRoot(updater_scope),
                           GetAppClientStateKey(app_id).c_str(),
                           Wow6432(KEY_QUERY_VALUE))
      .Valid();
}

std::optional<InstallerOutcome> GetLastInstallerOutcome(
    std::optional<base::win::RegKey> key) {
  if (!key) {
    return std::nullopt;
  }
  InstallerOutcome installer_outcome;
  {
    DWORD val = 0;
    if (key->ReadValueDW(kRegValueLastInstallerResult, &val) == ERROR_SUCCESS) {
      installer_outcome.installer_result =
          *CheckedCastToEnum<InstallerApiResult>(val);
    }
    if (key->ReadValueDW(kRegValueLastInstallerError, &val) == ERROR_SUCCESS) {
      installer_outcome.installer_error = val;
    }
    if (key->ReadValueDW(kRegValueLastInstallerExtraCode1, &val) ==
        ERROR_SUCCESS) {
      installer_outcome.installer_extracode1 = val;
    }
  }
  {
    std::wstring val;
    if (key->ReadValue(kRegValueLastInstallerResultUIString, &val) ==
        ERROR_SUCCESS) {
      std::string installer_text;
      if (base::WideToUTF8(val.c_str(), val.size(), &installer_text)) {
        installer_outcome.installer_text = installer_text;
      }
    }
    if (key->ReadValue(kRegValueLastInstallerSuccessLaunchCmdLine, &val) ==
        ERROR_SUCCESS) {
      std::string installer_cmd_line;
      if (base::WideToUTF8(val.c_str(), val.size(), &installer_cmd_line)) {
        installer_outcome.installer_cmd_line = installer_cmd_line;
      }
    }
  }

  return installer_outcome;
}

}  // namespace

InstallerOutcome::InstallerOutcome() = default;
InstallerOutcome::InstallerOutcome(const InstallerOutcome&) = default;
InstallerOutcome::~InstallerOutcome() = default;

std::optional<base::win::RegKey> ClientStateAppKeyOpen(
    UpdaterScope updater_scope,
    const std::string& app_id,
    REGSAM regsam) {
  std::wstring subkey;
  if (!base::UTF8ToWide(app_id.c_str(), app_id.size(), &subkey)) {
    return std::nullopt;
  }
  base::win::RegKey key(UpdaterScopeToHKeyRoot(updater_scope), CLIENT_STATE_KEY,
                        Wow6432(regsam));
  if (!key.Valid() ||
      key.OpenKey(subkey.c_str(), Wow6432(regsam)) != ERROR_SUCCESS) {
    return std::nullopt;
  }
  return key;
}

bool ClientStateAppKeyDelete(UpdaterScope updater_scope,
                             const std::string& app_id) {
  std::wstring subkey;
  if (!base::UTF8ToWide(app_id.c_str(), app_id.size(), &subkey)) {
    return false;
  }
  return base::win::RegKey(UpdaterScopeToHKeyRoot(updater_scope),
                           CLIENT_STATE_KEY, Wow6432(DELETE))
             .DeleteKey(subkey.c_str()) == ERROR_SUCCESS;
}

// Reads the installer progress from the registry value at:
// {HKLM|HKCU}\Software\Google\Update\ClientState\<appid>\InstallerProgress.
int GetInstallerProgress(UpdaterScope updater_scope,
                         const std::string& app_id) {
  std::optional<base::win::RegKey> key =
      ClientStateAppKeyOpen(updater_scope, app_id, KEY_READ);
  DWORD progress = 0;
  if (!key || key->ReadValueDW(kRegValueInstallerProgress, &progress) !=
                  ERROR_SUCCESS) {
    return -1;
  }
  return std::clamp(progress, DWORD{0}, DWORD{100});
}

bool SetInstallerProgressForTesting(UpdaterScope updater_scope,
                                    const std::string& app_id,
                                    int value) {
  std::optional<base::win::RegKey> key =
      ClientStateAppKeyCreate(updater_scope, app_id, KEY_WRITE);
  return key && key->WriteValue(kRegValueInstallerProgress,
                                static_cast<DWORD>(value)) == ERROR_SUCCESS;
}

bool DeleteInstallerProgress(UpdaterScope updater_scope,
                             const std::string& app_id) {
  if (!ClientStateAppKeyExists(updater_scope, app_id)) {
    return false;
  }
  std::optional<base::win::RegKey> key =
      ClientStateAppKeyOpen(updater_scope, app_id, KEY_SET_VALUE);
  return key && key->DeleteValue(kRegValueInstallerProgress) == ERROR_SUCCESS;
}

bool DeleteInstallerOutput(UpdaterScope updater_scope,
                           const std::string& app_id) {
  if (!ClientStateAppKeyExists(updater_scope, app_id)) {
    return false;
  }
  std::optional<base::win::RegKey> key = ClientStateAppKeyOpen(
      updater_scope, app_id, KEY_SET_VALUE | KEY_QUERY_VALUE);
  if (!key) {
    return false;
  }
  auto delete_value = [&key](const wchar_t* value) {
    return !key->HasValue(value) || key->DeleteValue(value) == ERROR_SUCCESS;
  };
  const bool results[] = {
      delete_value(kRegValueInstallerProgress),
      delete_value(kRegValueInstallerResult),
      delete_value(kRegValueInstallerError),
      delete_value(kRegValueInstallerExtraCode1),
      delete_value(kRegValueInstallerResultUIString),
      delete_value(kRegValueInstallerSuccessLaunchCmdLine),
  };
  return !base::Contains(results, false);
}

std::optional<InstallerOutcome> GetInstallerOutcome(UpdaterScope updater_scope,
                                                    const std::string& app_id) {
  std::optional<base::win::RegKey> key =
      ClientStateAppKeyOpen(updater_scope, app_id, KEY_READ);
  if (!key) {
    return std::nullopt;
  }
  InstallerOutcome installer_outcome;
  {
    DWORD val = 0;
    if (key->ReadValueDW(kRegValueInstallerResult, &val) == ERROR_SUCCESS) {
      installer_outcome.installer_result =
          *CheckedCastToEnum<InstallerApiResult>(val);
    }
    if (key->ReadValueDW(kRegValueInstallerError, &val) == ERROR_SUCCESS) {
      installer_outcome.installer_error = val;
    }
    if (key->ReadValueDW(kRegValueInstallerExtraCode1, &val) == ERROR_SUCCESS) {
      installer_outcome.installer_extracode1 = val;
    }
  }
  {
    std::wstring val;
    if (key->ReadValue(kRegValueInstallerResultUIString, &val) ==
        ERROR_SUCCESS) {
      std::string installer_text;
      if (base::WideToUTF8(val.c_str(), val.size(), &installer_text)) {
        installer_outcome.installer_text = installer_text;
      }
    }
    if (key->ReadValue(kRegValueInstallerSuccessLaunchCmdLine, &val) ==
        ERROR_SUCCESS) {
      std::string installer_cmd_line;
      if (base::WideToUTF8(val.c_str(), val.size(), &installer_cmd_line)) {
        installer_outcome.installer_cmd_line = installer_cmd_line;
      }
    }
  }

  PersistLastInstallerResultValues(updater_scope, app_id);

  return installer_outcome;
}

std::optional<InstallerOutcome> GetClientStateKeyLastInstallerOutcome(
    UpdaterScope updater_scope,
    const std::string& app_id) {
  return GetLastInstallerOutcome(
      ClientStateAppKeyOpen(updater_scope, app_id, KEY_READ));
}

std::optional<InstallerOutcome> GetUpdaterKeyLastInstallerOutcome(
    UpdaterScope updater_scope) {
  return GetLastInstallerOutcome(
      [&updater_scope]() -> std::optional<base::win::RegKey> {
        if (base::win::RegKey updater_key(UpdaterScopeToHKeyRoot(updater_scope),
                                          UPDATER_KEY, Wow6432(KEY_READ));
            updater_key.Valid()) {
          return updater_key;
        }
        return {};
      }());
}

bool SetInstallerOutcomeForTesting(UpdaterScope updater_scope,
                                   const std::string& app_id,
                                   const InstallerOutcome& installer_outcome) {
  std::optional<base::win::RegKey> key =
      ClientStateAppKeyCreate(updater_scope, app_id, KEY_WRITE);
  if (!key) {
    return false;
  }
  if (installer_outcome.installer_result) {
    if (key->WriteValue(
            kRegValueInstallerResult,
            static_cast<DWORD>(*installer_outcome.installer_result)) !=
        ERROR_SUCCESS) {
      return false;
    }
  }
  if (installer_outcome.installer_error) {
    if (key->WriteValue(kRegValueInstallerError,
                        *installer_outcome.installer_error) != ERROR_SUCCESS) {
      return false;
    }
  }
  if (installer_outcome.installer_extracode1) {
    if (key->WriteValue(kRegValueInstallerExtraCode1,
                        *installer_outcome.installer_extracode1) !=
        ERROR_SUCCESS) {
      return false;
    }
  }
  if (installer_outcome.installer_text) {
    if (key->WriteValue(
            kRegValueInstallerResultUIString,
            base::UTF8ToWide(*installer_outcome.installer_text).c_str()) !=
        ERROR_SUCCESS) {
      return false;
    }
  }
  if (installer_outcome.installer_cmd_line) {
    if (key->WriteValue(
            kRegValueInstallerSuccessLaunchCmdLine,
            base::UTF8ToWide(*installer_outcome.installer_cmd_line).c_str()) !=
        ERROR_SUCCESS) {
      return false;
    }
  }
  return true;
}

// As much as possible, the implementation of this function is intended to be
// backward compatible with the implementation of the Installer API in
// Omaha/Google Update. Some edge cases could be missing.
Installer::Result MakeInstallerResult(
    std::optional<InstallerOutcome> installer_outcome,
    int exit_code) {
  InstallerOutcome outcome;
  if (installer_outcome && installer_outcome->installer_result) {
    outcome = *installer_outcome;
  } else {
    // Set the installer result based on whether this is a success or an error.
    if (exit_code == 0) {
      outcome.installer_result = InstallerApiResult::kSuccess;
    } else {
      outcome.installer_result = InstallerApiResult::kExitCode;
      outcome.installer_error = exit_code;
    }
  }

  Installer::Result result;

  // Read and set the installer extra code in all cases if available. Installers
  // can use the `installer_extracode1` to transmit a custom value even in the
  // case of success.
  if (outcome.installer_extracode1) {
    result.result.extra_ = *outcome.installer_extracode1;
  }

  switch (*outcome.installer_result) {
    case InstallerApiResult::kSuccess:
      // This is unconditional success:
      // - use the command line if available, and ignore everything else.
      if (outcome.installer_cmd_line) {
        result.installer_cmd_line = *outcome.installer_cmd_line;
      }
      break;

    case InstallerApiResult::kCustomError:
    case InstallerApiResult::kMsiError:
    case InstallerApiResult::kSystemError:
    case InstallerApiResult::kExitCode:
      // These are usually unconditional errors:
      // - use the installer error, or the exit code, or report a generic
      //   error.
      // - use the installer extra code if available.
      // - use the text description of the error if available.
      result.result.code_ = outcome.installer_error.value_or(exit_code);
      if (!result.result.code_) {
        result.result.code_ = kErrorApplicationInstallerFailed;
      }

      // `update_client` needs to view the below codes as a success, otherwise
      // it will consider the app as not installed; set the error category to
      // kNone in this case.
      result.result.category_ =
          result.result.code_ == ERROR_SUCCESS_REBOOT_INITIATED ||
                  result.result.code_ == ERROR_SUCCESS_REBOOT_REQUIRED ||
                  result.result.code_ == ERROR_SUCCESS_RESTART_REQUIRED
              ? update_client::ErrorCategory::kNone
              : update_client::ErrorCategory::kInstaller;
      result.installer_text = outcome.installer_text.value_or("");
      CHECK_NE(result.result.code_, 0);
      break;
  }

  return result;
}

// Clears the previous installer output, runs the application installer,
// queries the installer progress, then collects the process exit code, if
// waiting for the installer does not time out.
//
// Reports the exit code of the installer process as -1 if waiting for the
// process to exit times out.
//
// The installer progress is written by the application installer as a value
// under the application's client state in the Windows registry and read by
// polling in a loop, while waiting for the installer to exit.
InstallerResult RunApplicationInstaller(
    const AppInfo& app_info,
    const base::FilePath& app_installer,
    const std::string& arguments,
    const std::optional<base::FilePath>& installer_data_file,
    bool usage_stats_enabled,
    const base::TimeDelta& timeout,
    InstallProgressCallback progress_callback) {
  if (!app_installer.MatchesExtension(L".exe") &&
      !app_installer.MatchesExtension(L".msi")) {
    return InstallerResult(GOOPDATEINSTALL_E_FILENAME_INVALID,
                           kErrorInvalidFileExtension);
  }

  DeleteInstallerOutput(app_info.scope, app_info.app_id);

  const std::wstring argsw = base::UTF8ToWide(arguments);
  const std::wstring cmdline =
      app_installer.MatchesExtension(L".msi")
          ? BuildMsiCommandLine(argsw, installer_data_file, app_installer)
          : BuildExeCommandLine(argsw, installer_data_file, app_installer);
  VLOG(1) << "Running application installer: " << cmdline;

  base::LaunchOptions options;
  options.start_hidden = true;
  options.environment = {
      {ENV_GOOGLE_UPDATE_IS_MACHINE,
       IsSystemInstall(app_info.scope) ? L"1" : L"0"},
      {base::UTF8ToWide(kUsageStatsEnabled),
       usage_stats_enabled ? base::UTF8ToWide(kUsageStatsEnabledValueEnabled)
                           : L"0"},
  };

  int num_tries = 0;
  base::TimeDelta retry_delay = kAlreadyRunningRetryInitialDelay;
  int exit_code = -1;
  const base::ElapsedTimer timer;
  base::Process process;
  do {
    if (!num_tries || exit_code == ERROR_INSTALL_ALREADY_RUNNING) {
      if (num_tries > 0) {
        VLOG(1) << "Retrying: " << num_tries;
        base::PlatformThread::Sleep(retry_delay);
        retry_delay *= 2;  // Double the retry delay each time.
      }
      ++num_tries;

      process = base::LaunchProcess(cmdline, options);
      if (!process.IsValid()) {
        return InstallerResult(GOOPDATEINSTALL_E_INSTALLER_FAILED_START,
                               HRESULTFromLastError());
      }
    }

    bool wait_result = process.WaitForExitWithTimeout(
        base::Seconds(kWaitForInstallerProgressSec), &exit_code);
    auto progress = GetInstallerProgress(app_info.scope, app_info.app_id);
    VLOG(3) << "installer progress: " << progress;
    progress_callback.Run(progress);
    if (wait_result) {
      const Installer::Result installer_result = MakeInstallerResult(
          GetInstallerOutcome(app_info.scope, app_info.app_id), exit_code);
      exit_code = installer_result.result.code_;
      VLOG(1) << "Installer exit code " << exit_code;
      if (exit_code == ERROR_INSTALL_ALREADY_RUNNING) {
        continue;
      }
      return installer_result;
    }
  } while (timer.Elapsed() < timeout && num_tries < kNumAlreadyRunningMaxTries);

  return InstallerResult(exit_code == ERROR_INSTALL_ALREADY_RUNNING
                             ? GOOPDATEINSTALL_E_INSTALL_ALREADY_RUNNING
                             : GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT);
}

std::string LookupString(const base::FilePath& path,
                         const std::string& keyname,
                         const std::string& default_value) {
  return default_value;
}

base::Version LookupVersion(const base::FilePath& path,
                            const std::string& keyname,
                            const base::Version& default_value) {
  return default_value;
}

}  // namespace updater