blob: 53354488134a93e4f6ca979317ef0c49ec4aeed1 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// 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 <string>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/cxx17_backports.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/enum_traits.h"
#include "chrome/updater/win/win_constants.h"
#include "chrome/updater/win/win_util.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace updater {
namespace {
// Opens the registry ClientState subkey for the `app_id`.
absl::optional<base::win::RegKey> ClientStateAppKeyOpen(
const std::string& app_id,
REGSAM regsam) {
std::wstring subkey;
if (!base::UTF8ToWide(app_id.c_str(), app_id.size(), &subkey))
return absl::nullopt;
base::win::RegKey key(HKEY_CURRENT_USER, CLIENT_STATE_KEY, Wow6432(regsam));
if (key.OpenKey(subkey.c_str(), Wow6432(regsam)) != ERROR_SUCCESS)
return absl::nullopt;
return key;
}
// 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.
absl::optional<base::win::RegKey> ClientStateAppKeyCreate(
const std::string& app_id,
REGSAM regsam) {
std::wstring subkey;
if (!base::UTF8ToWide(app_id.c_str(), app_id.size(), &subkey))
return absl::nullopt;
base::win::RegKey key(HKEY_CURRENT_USER, CLIENT_STATE_KEY, Wow6432(regsam));
if (key.CreateKey(subkey.c_str(), Wow6432(regsam)) != ERROR_SUCCESS)
return absl::nullopt;
return key;
}
} // namespace
InstallerOutcome::InstallerOutcome() = default;
InstallerOutcome::InstallerOutcome(const InstallerOutcome&) = default;
InstallerOutcome::~InstallerOutcome() = default;
bool ClientStateAppKeyDelete(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(HKEY_CURRENT_USER, CLIENT_STATE_KEY,
Wow6432(KEY_WRITE))
.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(const std::string& app_id) {
absl::optional<base::win::RegKey> key =
ClientStateAppKeyOpen(app_id, KEY_READ);
DWORD progress = 0;
if (!key || key->ReadValueDW(kRegValueInstallerProgress, &progress) !=
ERROR_SUCCESS) {
return -1;
}
return base::clamp(progress, DWORD{0}, DWORD{100});
}
bool SetInstallerProgressForTesting(const std::string& app_id, int value) {
absl::optional<base::win::RegKey> key =
ClientStateAppKeyCreate(app_id, KEY_WRITE);
return key && key->WriteValue(kRegValueInstallerProgress,
static_cast<DWORD>(value)) == ERROR_SUCCESS;
}
bool DeleteInstallerProgress(const std::string& app_id) {
absl::optional<base::win::RegKey> key =
ClientStateAppKeyOpen(app_id, KEY_SET_VALUE);
return key && key->DeleteValue(kRegValueInstallerProgress) == ERROR_SUCCESS;
}
bool DeleteInstallerOutput(const std::string& app_id) {
absl::optional<base::win::RegKey> key =
ClientStateAppKeyOpen(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
: true;
};
const bool results[] = {
delete_value(kRegValueInstallerProgress),
delete_value(kRegValueInstallerResult),
delete_value(kRegValueInstallerError),
delete_value(kRegValueInstallerExtraCode1),
delete_value(kRegValueInstallerResultUIString),
delete_value(kRegValueInstallerSuccessLaunchCmdLine),
};
return std::all_of(std::begin(results), std::end(results),
[](auto result) { return result; });
}
absl::optional<InstallerOutcome> GetInstallerOutcome(
const std::string& app_id) {
absl::optional<base::win::RegKey> key =
ClientStateAppKeyOpen(app_id, KEY_READ);
if (!key)
return absl::nullopt;
InstallerOutcome installer_outcome;
{
DWORD val = 0;
if (key->ReadValueDW(kRegValueInstallerResult, &val) == ERROR_SUCCESS) {
installer_outcome.installer_result =
*CheckedCastToEnum<InstallerResult>(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;
}
}
}
return installer_outcome;
}
bool SetInstallerOutcomeForTesting(const std::string& app_id,
const InstallerOutcome& installer_outcome) {
absl::optional<base::win::RegKey> key =
ClientStateAppKeyCreate(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;
}
std::string GetTextForSystemError(int error) {
wchar_t* system_allocated_buffer = nullptr;
constexpr DWORD kFormatOptions =
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK;
const DWORD chars_written = ::FormatMessage(
kFormatOptions, nullptr, error, 0,
reinterpret_cast<wchar_t*>(&system_allocated_buffer), 0, nullptr);
auto free_buffer = base::ScopedClosureRunner(
base::BindOnce(base::IgnoreResult(&LocalFree), system_allocated_buffer));
return chars_written > 0 ? base::WideToUTF8(system_allocated_buffer)
: std::string();
}
// 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.
// TODO(crbug.com/1172866): remove the hardcoded assumption that error must
// be zero to indicate success.
Installer::Result MakeInstallerResult(
absl::optional<InstallerOutcome> installer_outcome,
int exit_code) {
if (installer_outcome && installer_outcome->installer_result) {
Installer::Result result;
switch (*installer_outcome->installer_result) {
case InstallerResult::kSuccess:
// This is unconditional success:
// - use the command line if available, and ignore everything else.
result.error = 0;
if (installer_outcome->installer_cmd_line)
result.installer_cmd_line = *installer_outcome->installer_cmd_line;
DCHECK_EQ(result.error, 0);
break;
case InstallerResult::kCustomError:
// This is an unconditional error:
// - 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.error = installer_outcome->installer_error
? *installer_outcome->installer_error
: exit_code;
if (!result.error)
result.error = kErrorApplicationInstallerFailed;
if (installer_outcome->installer_extracode1)
result.extended_error = *installer_outcome->installer_extracode1;
if (installer_outcome->installer_text)
result.installer_text = *installer_outcome->installer_text;
DCHECK_NE(result.error, 0);
break;
case InstallerResult::kMsiError:
case InstallerResult::kSystemError:
// This is an unconditional error:
// - same as the case above but use a system-provided text.
result.error = installer_outcome->installer_error
? *installer_outcome->installer_error
: exit_code;
if (!result.error)
result.error = kErrorApplicationInstallerFailed;
if (installer_outcome->installer_extracode1)
result.extended_error = *installer_outcome->installer_extracode1;
result.installer_text = GetTextForSystemError(result.error);
DCHECK_NE(result.error, 0);
break;
case InstallerResult::kExitCode:
// This is could be a success or an error.
// - if success, then use the command line if available.
// - if an error, then ignore everything.
result.error = exit_code;
if (result.error == 0 && installer_outcome->installer_cmd_line)
result.installer_cmd_line = *installer_outcome->installer_cmd_line;
break;
}
return result;
}
return exit_code == 0
? Installer::Result(update_client::InstallError::NONE)
: Installer::Result(kErrorApplicationInstallerFailed, exit_code);
}
// 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.
Installer::Result Installer::RunApplicationInstaller(
const base::FilePath& app_installer,
const std::string& arguments,
ProgressCallback progress_callback) {
DeleteInstallerOutput(app_id());
base::LaunchOptions options;
options.start_hidden = true;
const auto cmdline =
base::StrCat({base::CommandLine(app_installer).GetCommandLineString(),
L" ", base::UTF8ToWide(arguments)});
VLOG(1) << "Running application installer: " << cmdline;
auto process = base::LaunchProcess(cmdline, options);
int exit_code = -1;
const auto time_begin = base::Time::NowFromSystemTime();
do {
bool wait_result = process.WaitForExitWithTimeout(
base::TimeDelta::FromSeconds(kWaitForInstallerProgressSec), &exit_code);
auto progress = GetInstallerProgress(app_id());
DVLOG(3) << "installer progress: " << progress;
progress_callback.Run(progress);
if (wait_result) {
VLOG(1) << "Installer exit code " << exit_code;
break;
}
} while (base::Time::NowFromSystemTime() - time_begin <=
base::TimeDelta::FromSeconds(kWaitForAppInstallerSec));
return MakeInstallerResult(GetInstallerOutcome(app_id()), exit_code);
}
} // namespace updater