// Copyright 2012 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/installer/setup/setup_main.h"
// clang-format off
#include <windows.h> // Must be included before msi.h.
#include <msi.h>
// clang-format on
#include <psapi.h>
#include <shellapi.h>
#include <shlobj.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <optional>
#include <string>
#include "base/at_exit.h"
#include "base/clang_profiling_buildflags.h"
#include "base/command_line.h"
#include "base/dcheck_is_on.h"
#include "base/debug/alias.h"
#include "base/debug/dump_without_crashing.h"
#include "base/debug/handle_hooks_win.h"
#include "base/file_version_info.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/persistent_histogram_storage.h"
#include "base/numerics/safe_conversions.h"
#include "base/path_service.h"
#include "base/process/launch.h"
#include "base/process/memory.h"
#include "base/process/process.h"
#include "base/strings/strcat_win.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_executor.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/types/expected_macros.h"
#include "base/values.h"
#include "base/version.h"
#include "base/win/current_module.h"
#include "base/win/process_startup_helper.h"
#include "base/win/registry.h"
#include "base/win/resource_exhaustion.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/core/network/win_key_network_delegate.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_manager.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/key_rotation_types.h"
#include "chrome/browser/enterprise/connectors/device_trust/key_management/installer/management_service/rotate_util.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/install_static/install_details.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/setup/archive_patch_helper.h"
#include "chrome/installer/setup/brand_behaviors.h"
#include "chrome/installer/setup/configure_app_container_sandbox.h"
#include "chrome/installer/setup/downgrade_cleanup.h"
#include "chrome/installer/setup/install.h"
#include "chrome/installer/setup/install_params.h"
#include "chrome/installer/setup/install_worker.h"
#include "chrome/installer/setup/installer_crash_reporting.h"
#include "chrome/installer/setup/installer_state.h"
#include "chrome/installer/setup/launch_chrome.h"
#include "chrome/installer/setup/modify_params.h"
#include "chrome/installer/setup/setup_constants.h"
#include "chrome/installer/setup/setup_install_details.h"
#include "chrome/installer/setup/setup_singleton.h"
#include "chrome/installer/setup/setup_util.h"
#include "chrome/installer/setup/uninstall.h"
#include "chrome/installer/setup/unpack_archive.h"
#include "chrome/installer/util/app_command.h"
#include "chrome/installer/util/conditional_work_item_list.h"
#include "chrome/installer/util/delete_after_reboot_helper.h"
#include "chrome/installer/util/delete_old_versions.h"
#include "chrome/installer/util/delete_tree_work_item.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/google_update_settings.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/html_dialog.h"
#include "chrome/installer/util/initial_preferences.h"
#include "chrome/installer/util/initial_preferences_constants.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/installation_state.h"
#include "chrome/installer/util/installer_util_strings.h"
#include "chrome/installer/util/l10n_string_util.h"
#include "chrome/installer/util/logging_installer.h"
#include "chrome/installer/util/lzma_util.h"
#include "chrome/installer/util/self_cleaning_temp_dir.h"
#include "chrome/installer/util/shell_util.h"
#include "chrome/installer/util/util_constants.h"
#include "components/crash/core/app/crash_switches.h"
#include "components/crash/core/app/run_as_crashpad_handler_win.h"
#include "content/public/common/content_switches.h"
#include "url/gurl.h"
#if BUILDFLAG(CLANG_PROFILING)
#include "base/test/clang_profiling.h"
#endif
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chrome/installer/util/google_update_util.h"
#endif
using installer::InitialPreferences;
using installer::InstallationState;
using installer::InstallerState;
using installer::ProductState;
namespace {
const wchar_t kSystemPrincipalSid[] = L"S-1-5-18";
const wchar_t kDisplayVersion[] = L"DisplayVersion";
// Overwrite an existing DisplayVersion as written by the MSI installer
// with the real version number of Chrome.
LONG OverwriteDisplayVersion(const std::wstring& path,
const std::wstring& value,
REGSAM wowkey) {
base::win::RegKey key;
LONG result = 0;
std::wstring existing;
if ((result = key.Open(HKEY_LOCAL_MACHINE, path.c_str(),
KEY_QUERY_VALUE | KEY_SET_VALUE | wowkey)) !=
ERROR_SUCCESS) {
VLOG(1) << "Skipping DisplayVersion update because registry key " << path
<< " does not exist in "
<< (wowkey == KEY_WOW64_64KEY ? "64" : "32") << "bit hive";
return result;
}
if ((result = key.ReadValue(kDisplayVersion, &existing)) != ERROR_SUCCESS) {
LOG(ERROR) << "Failed to set DisplayVersion: " << kDisplayVersion
<< " not found under " << path;
return result;
}
if ((result = key.WriteValue(kDisplayVersion, value.c_str())) !=
ERROR_SUCCESS) {
LOG(ERROR) << "Failed to set DisplayVersion: " << kDisplayVersion
<< " could not be written under " << path;
return result;
}
VLOG(1) << "Set DisplayVersion at " << path << " to " << value << " from "
<< existing;
return ERROR_SUCCESS;
}
LONG OverwriteDisplayVersions(const std::wstring& product,
const std::wstring& value) {
// The version is held in two places. First change it in the MSI Installer
// registry entry. It is held under a "squashed guid" key.
std::wstring reg_path = base::StrCat(
{L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UserData\\",
kSystemPrincipalSid, L"\\Products\\", InstallUtil::GuidToSquid(product),
L"\\InstallProperties"});
LONG result1 = OverwriteDisplayVersion(reg_path, value, KEY_WOW64_64KEY);
// The display version also exists under the Unininstall registry key with
// the original guid. Check both WOW64_64 and WOW64_32.
reg_path = base::StrCat(
{L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{", product,
L"}"});
// Consider the operation a success if either of these succeeds.
LONG result2 = OverwriteDisplayVersion(reg_path, value, KEY_WOW64_64KEY);
LONG result3 = OverwriteDisplayVersion(reg_path, value, KEY_WOW64_32KEY);
return result1 != ERROR_SUCCESS
? result1
: (result2 != ERROR_SUCCESS ? result3 : ERROR_SUCCESS);
}
// Launches a subprocess of `setup_exe` (the full path to this executable in the
// target installation directory) that will wait for msiexec to finish its work
// and then overwrite the DisplayVersion values in the Windows registry. `id` is
// the MSI product ID and `version` is the new Chrome version. The child will
// run with verbose logging enabled if `verbose_logging` is true.
void DelayedOverwriteDisplayVersions(const base::FilePath& setup_exe,
const std::string& id,
const base::Version& version,
bool verbose_logging) {
DCHECK(install_static::IsSystemInstall());
// Create an event to be given to the child process that it will signal
// immediately before blocking on msiexec's mutex.
SECURITY_ATTRIBUTES attributes = {};
attributes.nLength = sizeof(attributes);
attributes.bInheritHandle = TRUE;
base::win::ScopedHandle start_event(::CreateEventW(
&attributes, /*bManualReset=*/TRUE, /*bInitialState=*/FALSE,
/*lpName=*/nullptr));
PLOG_IF(ERROR, !start_event.IsValid()) << "Failed to create child event";
base::CommandLine command_line(setup_exe);
command_line.AppendSwitchASCII(installer::switches::kSetDisplayVersionProduct,
id);
command_line.AppendSwitchASCII(installer::switches::kSetDisplayVersionValue,
version.GetString());
if (start_event.IsValid()) {
command_line.AppendSwitchNative(
installer::switches::kStartupEventHandle,
base::NumberToWString(base::win::HandleToUint32(start_event.Get())));
}
InstallUtil::AppendModeAndChannelSwitches(&command_line);
command_line.AppendSwitch(installer::switches::kSystemLevel);
if (verbose_logging) {
command_line.AppendSwitch(installer::switches::kVerboseLogging);
}
base::LaunchOptions launch_options;
if (start_event.IsValid()) {
launch_options.handles_to_inherit.push_back(start_event.Get());
}
launch_options.force_breakaway_from_job_ = true;
base::Process writer = base::LaunchProcess(command_line, launch_options);
if (!writer.IsValid()) {
PLOG(ERROR) << "Failed to set DisplayVersion: "
<< "could not launch subprocess to make desired changes."
<< " <<" << command_line.GetCommandLineString() << ">>";
return;
}
if (!start_event.IsValid()) {
return;
}
// Wait up to 30 seconds for either the start event to be signaled or for the
// child process to terminate (i.e., in case it crashes).
constexpr DWORD kWaitForStartTimeoutMs = 30 * 1000;
const HANDLE handles[] = {start_event.Get(), writer.Handle()};
auto wait_result =
::WaitForMultipleObjects(std::size(handles), &handles[0],
/*bWaitAll=*/FALSE, kWaitForStartTimeoutMs);
if (wait_result == WAIT_OBJECT_0) {
VLOG(1) << "Proceeding after waiting for DisplayVersion overwrite child.";
} else if (wait_result == WAIT_OBJECT_0 + 1) {
LOG(ERROR) << "Proceeding after unexpected DisplayVersion overwrite "
"child termination.";
} else if (wait_result == WAIT_TIMEOUT) {
LOG(ERROR) << "Proceeding after unexpected timeout waiting for "
"DisplayVersion overwrite child.";
} else {
DCHECK_EQ(wait_result, WAIT_FAILED);
PLOG(ERROR) << "Proceeding after failing to wait for DisplayVersion "
"overwrite child";
}
}
// Signals `event` if it is valid and then closes it.
void SignalAndCloseEvent(base::win::ScopedHandle event) {
if (event.IsValid() && !::SetEvent(event.Get())) {
// Failure to signal the event likely means that the handle is invalid.
// Clear the ScopedHandle to prevent a crash upon close and proceed with the
// operation. The parent process will wait for 30s in this case (see
// DelayedOverwriteDisplayVersions) and will then continue on its merry way.
if (auto error = ::GetLastError(); error != ERROR_INVALID_HANDLE) {
// It is highly unexpected that this would fail for any other reason. Send
// diagnostics for analysis just in case.
// TODO(grt): Check for data and remove this in June 2024.
base::debug::Alias(&error);
base::debug::DumpWithoutCrashing();
}
(void)event.release();
}
}
// Waits for msiexec to release its mutex and then overwrites DisplayVersion in
// the Windows registry.
LONG OverwriteDisplayVersionsAfterMsiexec(base::win::ScopedHandle startup_event,
const std::wstring& product,
const std::wstring& value) {
bool adjusted_priority = false;
bool acquired_mutex = false;
base::win::ScopedHandle msi_handle(::OpenMutexW(
SYNCHRONIZE, /*bInheritHandle=*/FALSE, L"Global\\_MSIExecute"));
if (msi_handle.IsValid()) {
VLOG(1) << "Blocking to acquire MSI mutex.";
// Raise the priority class for the process so that it can do its work as
// soon as possible after acquiring the mutex.
adjusted_priority =
::SetPriorityClass(::GetCurrentProcess(), REALTIME_PRIORITY_CLASS) != 0;
// Notify the parent process that this one is ready to go.
SignalAndCloseEvent(std::move(startup_event));
const auto wait_result = ::WaitForSingleObject(msi_handle.Get(), INFINITE);
if (wait_result == WAIT_FAILED) {
// The handle is valid and was opened with SYNCHRONIZE, so the wait should
// never fail. If it does, wait ten seconds and proceed with the overwrite
// to match the old behavior.
PLOG(ERROR) << "Overwriting DisplayVersion in 10s after failing to wait "
"for the MSI mutex";
base::PlatformThread::Sleep(base::Seconds(10));
} else {
CHECK(wait_result == WAIT_ABANDONED || wait_result == WAIT_OBJECT_0)
<< "WaitForSingleObject: " << wait_result;
VLOG(1) << "Acquired MSI mutex; overwriting DisplayVersion.";
acquired_mutex = true;
}
} else {
// The mutex should still be held by msiexec since the parent setup.exe
// (which is run in the context of a Windows Installer operation) is
// blocking on this process.
PLOG(ERROR) << "Overwriting DisplayVersion immediately after failing to "
"open the MSI mutex";
// Notify the parent process that this one is ready to go.
SignalAndCloseEvent(std::move(startup_event));
}
auto result = OverwriteDisplayVersions(product, value);
if (adjusted_priority) {
::SetPriorityClass(::GetCurrentProcess(), NORMAL_PRIORITY_CLASS);
}
if (acquired_mutex) {
::ReleaseMutex(msi_handle.Get());
}
return result;
}
// Repetitively attempts to delete all files that belong to old versions of
// Chrome from |install_dir|. Waits 15 seconds before the first attempt and 5
// minutes after each unsuccessful attempt. Returns when no files that belong to
// an old version of Chrome remain or when another process tries to acquire the
// SetupSingleton.
installer::InstallStatus RepeatDeleteOldVersions(
const base::FilePath& install_dir,
const installer::SetupSingleton& setup_singleton) {
// The 99th percentile of the number of attempts it takes to successfully
// delete old versions is 2.75. The 75th percentile is 1.77. 98% of calls to
// this function will successfully delete old versions.
// Source: 30 days of UMA data on June 25, 2019.
constexpr int kMaxNumAttempts = 3;
int num_attempts = 0;
while (num_attempts < kMaxNumAttempts) {
// Wait 15 seconds before the first attempt because trying to delete old
// files right away is likely to fail. Indeed, this is called in 2
// occasions:
// - When the installer fails to delete old files after a not-in-use update:
// retrying immediately is likely to fail again.
// - When executables are successfully renamed on Chrome startup or
// shutdown: old files can't be deleted because Chrome is still in use.
// Wait 5 minutes after an unsuccessful attempt because retrying immediately
// is likely to fail again.
const base::TimeDelta max_wait_time =
num_attempts == 0 ? base::Seconds(15) : base::Minutes(5);
if (setup_singleton.WaitForInterrupt(max_wait_time)) {
VLOG(1) << "Exiting --delete-old-versions process because another "
"process tries to acquire the SetupSingleton.";
return installer::SETUP_SINGLETON_RELEASED;
}
// SetPriorityClass with PROCESS_MODE_BACKGROUND_BEGIN will cap the process
// working set to 32 MiB. This was experimentally determined after being
// reported in https://crbug.com/1475179. This can lead to extreme
// inefficiency as most CPU time is spent faulting in pages and then
// immediately trimming the working set. In one trace 99% of CPU time was
// spent handling page faults, so avoid SetPriorityClass with
// PROCESS_MODE_BACKGROUND_BEGIN.
base::ScopedClosureRunner restore_priority;
if (::SetThreadPriority(::GetCurrentThread(),
THREAD_MODE_BACKGROUND_BEGIN) != 0) {
// Be aware that a thread restoring itself to normal priority from
// background priority is inherently somewhat of a priority inversion.
restore_priority.ReplaceClosure(base::BindOnce([]() {
::SetThreadPriority(::GetCurrentThread(), THREAD_MODE_BACKGROUND_END);
}));
}
const bool delete_old_versions_success =
installer::DeleteOldVersions(install_dir);
++num_attempts;
if (delete_old_versions_success) {
VLOG(1) << "Successfully deleted all old files from "
"--delete-old-versions process.";
return installer::DELETE_OLD_VERSIONS_SUCCESS;
} else if (num_attempts == 1) {
VLOG(1) << "Failed to delete all old files from --delete-old-versions "
"process. Will retry every five minutes.";
}
}
VLOG(1) << "Exiting --delete-old-versions process after retrying too many "
"times to delete all old files.";
DCHECK_EQ(num_attempts, kMaxNumAttempts);
return installer::DELETE_OLD_VERSIONS_TOO_MANY_ATTEMPTS;
}
// This function is called when --rename-chrome-exe option is specified on
// setup.exe command line. This function assumes an in-use update has happened
// for Chrome so there should be files called new_chrome.exe and
// new_chrome_proxy.exe on the file system and a key called 'opv' in the
// registry. This function will move new_chrome.exe to chrome.exe,
// new_chrome_proxy.exe to chrome_proxy.exe and delete 'opv' key in one atomic
// operation. This function also deletes elevation policies associated with the
// old version if they exist. |setup_exe| is the path to the current executable.
installer::InstallStatus RenameChromeExecutables(
const base::FilePath& setup_exe,
const InstallationState& original_state,
InstallerState* installer_state) {
const base::FilePath& target_path = installer_state->target_path();
base::FilePath chrome_exe(target_path.Append(installer::kChromeExe));
base::FilePath chrome_new_exe(target_path.Append(installer::kChromeNewExe));
base::FilePath chrome_old_exe(target_path.Append(installer::kChromeOldExe));
base::FilePath chrome_proxy_exe(
target_path.Append(installer::kChromeProxyExe));
base::FilePath chrome_proxy_new_exe(
target_path.Append(installer::kChromeProxyNewExe));
base::FilePath chrome_proxy_old_exe(
target_path.Append(installer::kChromeProxyOldExe));
// Create a temporary backup directory on the same volume as chrome.exe so
// that moving in-use files doesn't lead to trouble.
installer::SelfCleaningTempDir temp_path;
if (!temp_path.Initialize(target_path.DirName(),
installer::kInstallTempDir)) {
PLOG(ERROR)
<< "Failed to create Temp directory "
<< target_path.DirName().Append(installer::kInstallTempDir).value();
return installer::RENAME_FAILED;
}
std::unique_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList());
// Move chrome.exe to old_chrome.exe, then move new_chrome.exe to chrome.exe.
install_list->AddMoveTreeWorkItem(chrome_exe, chrome_old_exe,
temp_path.path(), WorkItem::ALWAYS_MOVE);
install_list->AddMoveTreeWorkItem(chrome_new_exe, chrome_exe,
temp_path.path(), WorkItem::ALWAYS_MOVE);
install_list->AddDeleteTreeWorkItem(chrome_new_exe, temp_path.path());
// Move chrome_proxy.exe to old_chrome_proxy.exe if it exists (a previous
// installation may not have included it), then move new_chrome_proxy.exe to
// chrome_proxy.exe.
std::unique_ptr<WorkItemList> existing_proxy_rename_list(
WorkItem::CreateConditionalWorkItemList(
new ConditionRunIfFileExists(chrome_proxy_exe)));
existing_proxy_rename_list->set_log_message("ExistingProxyRenameItemList");
existing_proxy_rename_list->AddMoveTreeWorkItem(
chrome_proxy_exe, chrome_proxy_old_exe, temp_path.path(),
WorkItem::ALWAYS_MOVE);
install_list->AddWorkItem(existing_proxy_rename_list.release());
install_list->AddMoveTreeWorkItem(chrome_proxy_new_exe, chrome_proxy_exe,
temp_path.path(), WorkItem::ALWAYS_MOVE);
install_list->AddDeleteTreeWorkItem(chrome_proxy_new_exe, temp_path.path());
AddFinalizeUpdateWorkItems(base::Version(chrome::kChromeVersion),
*installer_state, setup_exe, install_list.get());
// Add work items to delete Chrome's "opv", "cpv", and "cmd" values.
// TODO(grt): Clean this up; https://crbug.com/577816.
const HKEY reg_root = installer_state->root_key();
const std::wstring clients_key = install_static::GetClientsKeyPath();
install_list->AddDeleteRegValueWorkItem(reg_root, clients_key,
KEY_WOW64_32KEY,
google_update::kRegOldVersionField);
install_list->AddDeleteRegValueWorkItem(
reg_root, clients_key, KEY_WOW64_32KEY,
google_update::kRegCriticalVersionField);
installer::AppCommand(installer::kCmdRenameChromeExe, {})
.AddDeleteAppCommandWorkItems(reg_root, install_list.get());
installer::AppCommand(installer::kCmdAlternateRenameChromeExe, {})
.AddDeleteAppCommandWorkItems(reg_root, install_list.get());
if (!installer_state->system_install()) {
install_list->AddDeleteRegValueWorkItem(
reg_root, clients_key, KEY_WOW64_32KEY, installer::kCmdRenameChromeExe);
}
// If a channel was specified by policy, update the "channel" registry value
// with it so that the browser knows which channel to use, otherwise delete
// whatever value that key holds.
installer::AddChannelWorkItems(reg_root, clients_key, install_list.get());
// old_chrome.exe is still in use in most cases, so ignore failures here.
install_list->AddDeleteTreeWorkItem(chrome_old_exe, temp_path.path())
->set_best_effort(true);
install_list->AddDeleteTreeWorkItem(chrome_proxy_old_exe, temp_path.path())
->set_best_effort(true);
installer::InstallStatus ret = installer::RENAME_SUCCESSFUL;
if (install_list->Do()) {
installer::LaunchDeleteOldVersionsProcess(setup_exe, *installer_state);
} else {
LOG(ERROR) << "Renaming of executables failed. Rolling back any changes.";
install_list->Rollback();
ret = installer::RENAME_FAILED;
}
// temp_path's dtor will take care of deleting or scheduling itself for
// deletion at reboot when this scope closes.
VLOG(1) << "Deleting temporary directory " << temp_path.path().value();
return ret;
}
// Checks for compatibility between the current state of the system and the
// desired operation.
// Also blocks simultaneous user-level and system-level installs. In the case
// of trying to install user-level Chrome when system-level exists, the
// existing system-level Chrome is launched.
// When the pre-install conditions are not satisfied, the result is written to
// the registry (via WriteInstallerResult), |status| is set appropriately, and
// false is returned.
bool CheckPreInstallConditions(const InstallationState& original_state,
const InstallerState& installer_state,
installer::InstallStatus* status) {
if (!installer_state.system_install()) {
// This is a user-level installation. Make sure that we are not installing
// on top of an existing system-level installation.
const ProductState* user_level_product_state =
original_state.GetProductState(false);
const ProductState* system_level_product_state =
original_state.GetProductState(true);
// Allow upgrades to proceed so that out-of-date versions are not left
// around.
if (user_level_product_state) {
return true;
}
// This is a new user-level install...
if (system_level_product_state) {
// ... and the product already exists at system-level.
LOG(ERROR) << "Already installed version "
<< system_level_product_state->version().GetString()
<< " at system-level conflicts with this one at user-level.";
// Instruct Google Update to launch the existing system-level Chrome.
// There should be no error dialog.
base::FilePath install_path(
installer::GetInstalledDirectory(/*system_install=*/true));
if (install_path.empty()) {
// Give up if we failed to construct the install path.
*status = installer::OS_ERROR;
installer_state.WriteInstallerResult(*status, IDS_INSTALL_OS_ERROR_BASE,
nullptr);
} else {
*status = installer::EXISTING_VERSION_LAUNCHED;
base::FilePath chrome_exe = install_path.Append(installer::kChromeExe);
base::CommandLine cmd(chrome_exe);
cmd.AppendSwitch(switches::kForceFirstRun);
installer_state.WriteInstallerResult(
*status, IDS_INSTALL_EXISTING_VERSION_LAUNCHED_BASE, nullptr);
VLOG(1) << "Launching existing system-level chrome instead.";
base::LaunchProcess(cmd, base::LaunchOptions());
}
return false;
}
}
return true;
}
// Initializes |temp_path| to "Temp" within the target directory, and
// |unpack_path| to a random directory beginning with "source" within
// |temp_path|. Returns false on error.
bool CreateTemporaryAndUnpackDirectories(
const InstallerState& installer_state,
installer::SelfCleaningTempDir* temp_path,
base::FilePath* unpack_path) {
DCHECK(temp_path && unpack_path);
if (!temp_path->Initialize(installer_state.target_path().DirName(),
installer::kInstallTempDir)) {
PLOG(ERROR) << "Could not create temporary path.";
return false;
}
VLOG(1) << "Created path " << temp_path->path().value();
if (!base::CreateTemporaryDirInDir(
temp_path->path(), installer::kInstallSourceDir, unpack_path)) {
PLOG(ERROR) << "Could not create temporary path for unpacked archive.";
return false;
}
return true;
}
installer::InstallStatus UninstallProducts(InstallationState& original_state,
InstallerState& installer_state,
const base::FilePath& setup_exe,
const base::CommandLine& cmd_line) {
// System-level Chrome will be launched via this command if its program gets
// set below.
base::CommandLine system_level_cmd(base::CommandLine::NO_PROGRAM);
if (cmd_line.HasSwitch(installer::switches::kSelfDestruct) &&
!installer_state.system_install()) {
const base::FilePath system_install_dir(
installer::GetInstalledDirectory(/*system_install=*/true));
if (!system_install_dir.empty()) {
system_level_cmd.SetProgram(
system_install_dir.Append(installer::kChromeExe));
}
}
installer::InstallStatus install_status = installer::UNINSTALL_SUCCESSFUL;
const bool force = cmd_line.HasSwitch(installer::switches::kForceUninstall);
const bool remove_all =
!cmd_line.HasSwitch(installer::switches::kDoNotRemoveSharedItems);
const base::Version current_version(
installer_state.GetCurrentVersion(original_state));
const installer::ModifyParams modify_params = {
installer_state,
original_state,
setup_exe,
current_version,
};
install_status = UninstallProduct(modify_params, remove_all, force, cmd_line);
installer::CleanUpInstallationDirectoryAfterUninstall(
installer_state.target_path(), setup_exe, &install_status);
// The app and vendor dirs may now be empty. Make a last-ditch attempt to
// delete them.
installer::DeleteChromeDirectoriesIfEmpty(installer_state.target_path());
// Trigger Active Setup if it was requested for the chrome product. This needs
// to be done after the UninstallProduct calls as some of them might
// otherwise terminate the process launched by TriggerActiveSetupCommand().
if (cmd_line.HasSwitch(installer::switches::kTriggerActiveSetup)) {
InstallUtil::TriggerActiveSetupCommand();
}
if (!system_level_cmd.GetProgram().empty()) {
base::LaunchProcess(system_level_cmd, base::LaunchOptions());
}
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Tell Google Update that an uninstall has taken place if this install did
// not originate from the MSI. Google Update has its own logic relating to
// MSI-driven uninstalls that conflicts with this. Ignore the return value:
// success or failure of Google Update has no bearing on the success or
// failure of Chrome's uninstallation.
if (!installer_state.is_msi()) {
google_update::UninstallGoogleUpdate(installer_state.system_install());
}
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
return install_status;
}
installer::InstallStatus InstallProducts(InstallationState& original_state,
const base::FilePath& setup_exe,
const base::CommandLine& cmd_line,
const InitialPreferences& prefs,
InstallerState* installer_state) {
DCHECK(installer_state);
installer::InstallStatus install_status = installer::UNKNOWN_STATUS;
installer::ArchiveType archive_type = installer::UNKNOWN_ARCHIVE_TYPE;
installer_state->SetStage(installer::PRECONDITIONS);
// Remove any legacy "-stage:*" values from the product's "ap" value.
installer::UpdateInstallStatus(archive_type, install_status);
// Drop to background processing mode if the process was started below the
// normal process priority class. This is done here because InstallProducts-
// Helper has read-only access to the state and because the action also
// affects everything else that runs below.
const bool entered_background_mode = installer::AdjustThreadPriority();
VLOG_IF(1, entered_background_mode) << "Entered background processing mode.";
if (CheckPreInstallConditions(original_state, *installer_state,
&install_status)) {
VLOG(1) << "Installing to " << installer_state->target_path().value();
install_status =
InstallProductsHelper(original_state, setup_exe, cmd_line, prefs,
*installer_state, &archive_type);
} else {
// CheckPreInstallConditions must set the status on failure.
DCHECK_NE(install_status, installer::UNKNOWN_STATUS);
}
// Delete the initial preferences file if present. Note that we do not care
// about rollback here and we schedule for deletion on reboot if the delete
// fails. As such, we do not use DeleteTreeWorkItem.
if (cmd_line.HasSwitch(installer::switches::kInstallerData)) {
base::FilePath prefs_path(
cmd_line.GetSwitchValuePath(installer::switches::kInstallerData));
if (!base::DeleteFile(prefs_path)) {
LOG(ERROR) << "Failed deleting initial preferences file "
<< prefs_path.value()
<< ", scheduling for deletion after reboot.";
ScheduleFileSystemEntityForDeletion(prefs_path);
}
}
UpdateInstallStatus(archive_type, install_status);
return install_status;
}
installer::InstallStatus ShowEulaDialog(const std::wstring& inner_frame) {
VLOG(1) << "About to show EULA";
std::wstring eula_path = installer::GetLocalizedEulaResource();
if (eula_path.empty()) {
LOG(ERROR) << "No EULA path available";
return installer::EULA_REJECTED;
}
// Newer versions of the caller pass an inner frame parameter that must
// be given to the html page being launched.
installer::EulaHTMLDialog dlg(eula_path, inner_frame);
installer::EulaHTMLDialog::Outcome outcome = dlg.ShowModal();
if (installer::EulaHTMLDialog::REJECTED == outcome) {
LOG(ERROR) << "EULA rejected or EULA failure";
return installer::EULA_REJECTED;
}
if (installer::EulaHTMLDialog::ACCEPTED_OPT_IN == outcome) {
VLOG(1) << "EULA accepted (opt-in)";
return installer::EULA_ACCEPTED_OPT_IN;
}
VLOG(1) << "EULA accepted (no opt-in)";
return installer::EULA_ACCEPTED;
}
// Creates the sentinel indicating that the EULA was required and has been
// accepted.
bool CreateEulaSentinel() {
base::FilePath eula_sentinel;
if (!InstallUtil::GetEulaSentinelFilePath(&eula_sentinel)) {
return false;
}
return (base::CreateDirectory(eula_sentinel.DirName()) &&
base::WriteFile(eula_sentinel, ""));
}
installer::InstallStatus RegisterDevChrome(
const installer::ModifyParams& modify_params,
const base::CommandLine& cmd_line) {
const InstallationState& original_state = *modify_params.installation_state;
const base::FilePath& setup_exe = *modify_params.setup_path;
// Only proceed with registering a dev chrome if no real Chrome installation
// of the same install mode is present on this system.
const ProductState* existing_chrome = original_state.GetProductState(false);
if (!existing_chrome) {
existing_chrome = original_state.GetProductState(true);
}
if (existing_chrome) {
const std::wstring name = InstallUtil::GetDisplayName();
const std::wstring message = base::StrCat(
{L"You already have a full-installation (non-dev) of ", name,
L", please uninstall it first using Add/Remove Programs in the "
L"control panel."});
LOG(ERROR) << "Aborting operation: another installation of " << name
<< " was found, as a last resort (if the product is not present "
"in Add/Remove Programs), try executing: "
<< existing_chrome->uninstall_command().GetCommandLineString();
MessageBox(nullptr, message.c_str(), nullptr, MB_ICONERROR);
return installer::INSTALL_FAILED;
}
base::FilePath chrome_exe(
cmd_line.GetSwitchValuePath(installer::switches::kRegisterDevChrome));
if (chrome_exe.empty()) {
chrome_exe = setup_exe.DirName().Append(installer::kChromeExe);
}
if (!chrome_exe.IsAbsolute()) {
chrome_exe = base::MakeAbsoluteFilePath(chrome_exe);
}
installer::InstallStatus status = installer::FIRST_INSTALL_SUCCESS;
if (base::PathExists(chrome_exe)) {
// Create the Start menu shortcut and pin it to the Win7+ taskbar.
ShellUtil::ShortcutProperties shortcut_properties(ShellUtil::CURRENT_USER);
ShellUtil::AddDefaultShortcutProperties(chrome_exe, &shortcut_properties);
shortcut_properties.set_pin_to_taskbar(true);
ShellUtil::CreateOrUpdateShortcut(
ShellUtil::SHORTCUT_LOCATION_START_MENU_ROOT, shortcut_properties,
ShellUtil::SHELL_SHORTCUT_CREATE_ALWAYS);
// Register Chrome at user-level and make it default.
if (ShellUtil::CanMakeChromeDefaultUnattended()) {
ShellUtil::MakeChromeDefault(ShellUtil::CURRENT_USER, chrome_exe, true);
} else {
ShellUtil::ShowMakeChromeDefaultSystemUI(chrome_exe);
}
} else {
LOG(ERROR) << "Path not found: " << chrome_exe.value();
status = installer::INSTALL_FAILED;
}
return status;
}
installer::InstallStatus CreateShortcutsInChildProc(
const InstallerState& installer_state,
const InitialPreferences& prefs,
installer::InstallShortcutLevel install_level,
installer::InstallShortcutOperation install_operation) {
// Create shortcut in a child process so that shell crashes don't make the
// install/update fail. Pass install operation on the command line since
// it can't be deduced by the child process;
// Creates shortcuts for Chrome.
const base::FilePath chrome_exe(
installer_state.target_path().Append(installer::kChromeExe));
// Install per-user shortcuts on user-level installs and all-users shortcuts
// on system-level installs. Note that Active Setup will take care of
// installing missing per-user shortcuts on system-level install (i.e.,
// quick launch, taskbar pin, and possibly deleted all-users shortcuts).
CreateOrUpdateShortcuts(chrome_exe, prefs, install_level, install_operation);
// TODO(): Plumb shortcut creation failure through and return a
// failure exit code.
return installer::CREATE_SHORTCUTS_SUCCESS;
}
// This method processes any command line options that make setup.exe do
// various tasks other than installation (renaming chrome.exe, showing eula
// among others). This function returns true if any such command line option
// has been found and processed (so setup.exe should exit at that point).
bool HandleNonInstallCmdLineOptions(installer::ModifyParams& modify_params,
const base::CommandLine& cmd_line,
const InitialPreferences& prefs,
int* exit_code) {
installer::InstallerState* installer_state =
&(*modify_params.installer_state);
installer::InstallationState* original_state =
&(*modify_params.installation_state);
const base::FilePath& setup_exe = *modify_params.setup_path;
// TODO(gab): Add a local |status| variable which each block below sets;
// only determine the |exit_code| from |status| at the end (this will allow
// this method to validate that
// (!handled || status != installer::UNKNOWN_STATUS)).
bool handled = true;
// TODO(tommi): Split these checks up into functions and use a data driven
// map of switch->function.
if (cmd_line.HasSwitch(installer::switches::kUpdateSetupExe)) {
installer_state->SetStage(installer::UPDATING_SETUP);
installer::InstallStatus status = installer::SETUP_PATCH_FAILED;
// If --update-setup-exe command line option is given, we apply the given
// patch to current exe, and store the resulting binary in the path
// specified by --new-setup-exe. But we need to first unpack the file
// given in --update-setup-exe.
const base::FilePath compressed_archive(
cmd_line.GetSwitchValuePath(installer::switches::kUpdateSetupExe));
VLOG(1) << "Opening archive " << compressed_archive.value();
// The top unpack failure result with 28 days aggregation (>=0.01%)
// Setup.Install.LzmaUnPackResult_SetupExePatch
// 0.02% PATH_NOT_FOUND
//
// More information can also be found with metric:
// Setup.Install.LzmaUnPackNTSTATUS_SetupExePatch
// We use the `new_setup_exe` directory as the working directory for
// `ArchivePatchHelper::UncompressAndPatch`. For System installs, this
// directory would be under %ProgramFiles% (a directory that only admins can
// write to by default) and hence a secure location.
const base::FilePath new_setup_exe(
cmd_line.GetSwitchValuePath(installer::switches::kNewSetupExe));
if (installer::ArchivePatchHelper::UncompressAndPatch(
new_setup_exe.DirName(), compressed_archive, setup_exe,
new_setup_exe, installer::UnPackConsumer::SETUP_EXE_PATCH)) {
status = installer::NEW_VERSION_UPDATED;
}
*exit_code = InstallUtil::GetInstallReturnCode(status);
if (*exit_code) {
LOG(WARNING) << "setup.exe patching failed.";
installer_state->WriteInstallerResult(status, IDS_SETUP_PATCH_FAILED_BASE,
nullptr);
}
} else if (cmd_line.HasSwitch(installer::switches::kShowEula)) {
// Check if we need to show the EULA. If it is passed as a command line
// then the dialog is shown and regardless of the outcome setup exits here.
std::wstring inner_frame =
cmd_line.GetSwitchValueNative(installer::switches::kShowEula);
*exit_code = ShowEulaDialog(inner_frame);
if (installer::EULA_REJECTED != *exit_code) {
if (GoogleUpdateSettings::SetEulaConsent(*original_state, true)) {
CreateEulaSentinel();
}
}
} else if (cmd_line.HasSwitch(installer::switches::kConfigureUserSettings)) {
// NOTE: Should the work done here, on kConfigureUserSettings, change:
// kActiveSetupVersion in install_worker.cc needs to be increased for Active
// Setup to invoke this again for all users of this install.
installer::InstallStatus status = installer::INVALID_STATE_FOR_OPTION;
if (installer_state->system_install()) {
bool force =
cmd_line.HasSwitch(installer::switches::kForceConfigureUserSettings);
installer::HandleActiveSetupForBrowser(*installer_state, setup_exe,
force);
status = installer::INSTALL_REPAIRED;
} else {
LOG(DFATAL)
<< "--configure-user-settings is incompatible with user-level";
}
*exit_code = InstallUtil::GetInstallReturnCode(status);
} else if (cmd_line.HasSwitch(installer::switches::kRegisterDevChrome)) {
installer::InstallStatus status =
RegisterDevChrome(modify_params, cmd_line);
*exit_code = InstallUtil::GetInstallReturnCode(status);
} else if (cmd_line.HasSwitch(installer::switches::kRegisterChromeBrowser)) {
installer::InstallStatus status = installer::UNKNOWN_STATUS;
// If --register-chrome-browser option is specified, register all Chrome
// protocol/file associations, as well as register it as a valid browser for
// Start Menu->Internet shortcut. This switch will also register Chrome as a
// valid handler for a set of URL protocols that Chrome may become the
// default handler for, either by the user marking Chrome as the default
// browser, through the Windows Default Programs control panel settings, or
// through website use of registerProtocolHandler. These protocols are found
// in ShellUtil::kPotentialProtocolAssociations. The
// --register-url-protocol will additionally register Chrome as a potential
// handler for the supplied protocol, and is used if a website registers a
// handler for a protocol not found in
// ShellUtil::kPotentialProtocolAssociations. These options should only be
// used when setup.exe is launched with admin rights. We do not make any
// user specific changes with this option.
DCHECK(IsUserAnAdmin());
base::FilePath chrome_exe(cmd_line.GetSwitchValuePath(
installer::switches::kRegisterChromeBrowser));
std::wstring suffix;
if (cmd_line.HasSwitch(installer::switches::kRegisterChromeBrowserSuffix)) {
suffix = cmd_line.GetSwitchValueNative(
installer::switches::kRegisterChromeBrowserSuffix);
}
if (cmd_line.HasSwitch(installer::switches::kRegisterURLProtocol)) {
const std::wstring protocol_associations_value =
cmd_line.GetSwitchValueNative(
installer::switches::kRegisterURLProtocol);
std::optional<ShellUtil::ProtocolAssociations> protocol_associations =
ShellUtil::ProtocolAssociations::FromCommandLineArgument(
protocol_associations_value);
// ShellUtil::RegisterChromeForProtocol performs all registration
// done by ShellUtil::RegisterChromeBrowser, as well as registering
// with Windows as capable of handling the supplied protocol.
if (protocol_associations.has_value() &&
ShellUtil::RegisterChromeForProtocols(
chrome_exe, suffix, protocol_associations.value(), false)) {
status = installer::IN_USE_UPDATED;
}
} else {
if (ShellUtil::RegisterChromeBrowser(chrome_exe, suffix,
/*elevate_if_not_admin=*/false)) {
status = installer::IN_USE_UPDATED;
}
}
*exit_code = InstallUtil::GetInstallReturnCode(status);
} else if (cmd_line.HasSwitch(installer::switches::kDeleteOldVersions) ||
cmd_line.HasSwitch(installer::switches::kRenameChromeExe)) {
std::unique_ptr<installer::SetupSingleton> setup_singleton(
installer::SetupSingleton::Acquire(
cmd_line, InitialPreferences::ForCurrentProcess(), original_state,
installer_state));
if (!setup_singleton) {
*exit_code = installer::SETUP_SINGLETON_ACQUISITION_FAILED;
} else if (cmd_line.HasSwitch(installer::switches::kDeleteOldVersions)) {
*exit_code = RepeatDeleteOldVersions(installer_state->target_path(),
*setup_singleton);
} else {
DCHECK(cmd_line.HasSwitch(installer::switches::kRenameChromeExe));
*exit_code =
RenameChromeExecutables(setup_exe, *original_state, installer_state);
}
} else if (cmd_line.HasSwitch(
installer::switches::kCleanupForDowngradeVersion)) {
// The version being downgraded to.
std::string new_version = cmd_line.GetSwitchValueASCII(
installer::switches::kCleanupForDowngradeVersion);
std::wstring operation = cmd_line.GetSwitchValueNative(
installer::switches::kCleanupForDowngradeOperation);
if (operation == L"cleanup" || operation == L"revert") {
*exit_code = installer::ProcessCleanupForDowngrade(
base::Version(new_version), /*revert=*/operation == L"revert");
} else {
LOG(ERROR) << "Ignoring \"" << cmd_line.GetCommandLineString()
<< "\" because of invalid \"operation\" argument.";
*exit_code = installer::DOWNGRADE_CLEANUP_UNKNOWN_OPERATION;
}
} else if (cmd_line.HasSwitch(
installer::switches::kRemoveChromeRegistration)) {
// This is almost reverse of --register-chrome-browser option above.
// Here we delete Chrome browser registration. This option should only
// be used when setup.exe is launched with admin rights. We do not
// make any user specific changes in this option.
std::wstring suffix;
if (cmd_line.HasSwitch(installer::switches::kRegisterChromeBrowserSuffix)) {
suffix = cmd_line.GetSwitchValueNative(
installer::switches::kRegisterChromeBrowserSuffix);
}
installer::InstallStatus tmp = installer::UNKNOWN_STATUS;
installer::DeleteChromeRegistrationKeys(*installer_state,
HKEY_LOCAL_MACHINE, suffix, &tmp);
*exit_code = tmp;
} else if (cmd_line.HasSwitch(installer::switches::kOnOsUpgrade)) {
installer::InstallStatus status = installer::INVALID_STATE_FOR_OPTION;
std::unique_ptr<FileVersionInfo> version_info(
FileVersionInfo::CreateFileVersionInfo(setup_exe));
const base::Version installed_version(
base::UTF16ToUTF8(version_info->product_version()));
if (installed_version.IsValid()) {
installer::HandleOsUpgradeForBrowser(*installer_state, installed_version,
setup_exe);
status = installer::INSTALL_REPAIRED;
} else {
LOG(DFATAL) << "Failed to extract product version from "
<< setup_exe.value();
}
*exit_code = InstallUtil::GetInstallReturnCode(status);
} else if (cmd_line.HasSwitch(installer::switches::kReenableAutoupdates)) {
// setup.exe has been asked to attempt to reenable updates for Chrome.
bool updates_enabled = GoogleUpdateSettings::ReenableAutoupdates();
*exit_code = updates_enabled ? installer::REENABLE_UPDATES_SUCCEEDED
: installer::REENABLE_UPDATES_FAILED;
} else if (cmd_line.HasSwitch(
installer::switches::kSetDisplayVersionProduct)) {
const std::wstring registry_product(cmd_line.GetSwitchValueNative(
installer::switches::kSetDisplayVersionProduct));
const std::wstring registry_value(cmd_line.GetSwitchValueNative(
installer::switches::kSetDisplayVersionValue));
uint32_t startup_event_handle_value = 0;
base::win::ScopedHandle startup_event;
if (base::StringToUint(cmd_line.GetSwitchValueNative(
installer::switches::kStartupEventHandle),
&startup_event_handle_value) &&
startup_event_handle_value) {
startup_event.Set(base::win::Uint32ToHandle(startup_event_handle_value));
}
*exit_code = OverwriteDisplayVersionsAfterMsiexec(
std::move(startup_event), registry_product, registry_value);
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
} else if (cmd_line.HasSwitch(installer::switches::kStoreDMToken)) {
// Write the specified token to the registry, overwriting any already
// existing value.
std::wstring token_switch_value =
cmd_line.GetSwitchValueNative(installer::switches::kStoreDMToken);
auto token = installer::DecodeDMTokenSwitchValue(token_switch_value);
*exit_code = token && installer::StoreDMToken(*token)
? installer::STORE_DMTOKEN_SUCCESS
: installer::STORE_DMTOKEN_FAILED;
} else if (cmd_line.HasSwitch(installer::switches::kDeleteDMToken)) {
// Delete any existing DMToken from the registry.
*exit_code = installer::DeleteDMToken() ? installer::DELETE_DMTOKEN_SUCCESS
: installer::DELETE_DMTOKEN_FAILED;
} else if (cmd_line.HasSwitch(installer::switches::kRotateDeviceTrustKey)) {
// RotateDeviceTrustKey() expects a single
// threaded task runner so creating one here.
base::SingleThreadTaskExecutor executor;
const auto result = enterprise_connectors::RotateDeviceTrustKey(
enterprise_connectors::KeyRotationManager::Create(
std::make_unique<enterprise_connectors::WinKeyNetworkDelegate>()),
cmd_line, install_static::GetChromeChannel());
switch (result) {
case enterprise_connectors::KeyRotationResult::kSucceeded:
*exit_code = installer::ROTATE_DTKEY_SUCCESS;
break;
case enterprise_connectors::KeyRotationResult::kInsufficientPermissions:
*exit_code = installer::ROTATE_DTKEY_FAILED_PERMISSIONS;
break;
case enterprise_connectors::KeyRotationResult::kFailedKeyConflict:
*exit_code = installer::ROTATE_DTKEY_FAILED_CONFLICT;
break;
case enterprise_connectors::KeyRotationResult::kFailed:
*exit_code = installer::ROTATE_DTKEY_FAILED;
break;
}
#endif
} else if (cmd_line.HasSwitch(installer::switches::kCreateShortcuts)) {
std::string install_op_arg =
cmd_line.GetSwitchValueASCII(installer::switches::kCreateShortcuts);
std::string shortcut_level_arg =
cmd_line.GetSwitchValueASCII(installer::switches::kInstallLevel);
int install_op;
int install_level_op;
if (!base::StringToInt(install_op_arg, &install_op) ||
install_op < installer::INSTALL_SHORTCUT_FIRST ||
install_op > installer::INSTALL_SHORTCUT_LAST) {
LOG(ERROR) << "Invalid shortcut operation " << install_op_arg;
*exit_code = installer::UNSUPPORTED_OPTION;
} else if (!base::StringToInt(shortcut_level_arg, &install_level_op) ||
install_level_op < installer::INSTALL_SHORTCUT_LEVEL_FIRST ||
install_level_op > installer::INSTALL_SHORTCUT_LEVEL_LAST) {
LOG(ERROR) << "Invalid shortcut level " << shortcut_level_arg;
*exit_code = installer::UNSUPPORTED_OPTION;
} else {
*exit_code = CreateShortcutsInChildProc(
*installer_state, prefs,
static_cast<installer::InstallShortcutLevel>(install_level_op),
static_cast<installer::InstallShortcutOperation>(install_op));
}
} else if (cmd_line.HasSwitch(
installer::switches::kConfigureBrowserInDirectory)) {
base::FilePath path = cmd_line.GetSwitchValuePath(
installer::switches::kConfigureBrowserInDirectory);
if (path.empty()) {
LOG(ERROR) << "Empty directory specified in --"
<< installer::switches::kConfigureBrowserInDirectory;
*exit_code = installer::CONFIGURE_APP_CONTAINER_SANDBOX_FAILED;
} else if (!path.IsAbsolute()) {
LOG(ERROR) << "--" << installer::switches::kConfigureBrowserInDirectory
<< " must contain an absolute path";
*exit_code = installer::CONFIGURE_APP_CONTAINER_SANDBOX_FAILED;
} else if (installer::ConfigureAppContainerSandbox(
std::array<const base::FilePath*, 1>{&path})) {
*exit_code = installer::CONFIGURE_APP_CONTAINER_SANDBOX_SUCCESS;
} else {
*exit_code = installer::CONFIGURE_APP_CONTAINER_SANDBOX_FAILED;
}
} else {
handled = false;
}
return handled;
}
} // namespace
namespace installer {
InstallStatus InstallProductsHelper(InstallationState& original_state,
const base::FilePath& setup_exe,
const base::CommandLine& cmd_line,
const InitialPreferences& prefs,
InstallerState& installer_state,
ArchiveType* archive_type) {
DCHECK(archive_type);
const bool system_install = installer_state.system_install();
// Create a temp folder where we will unpack Chrome archive. If it fails,
// then we are doomed, so return immediately and no cleanup is required.
SelfCleaningTempDir temp_path;
base::FilePath unpack_path;
if (!CreateTemporaryAndUnpackDirectories(installer_state, &temp_path,
&unpack_path)) {
installer_state.WriteInstallerResult(
TEMP_DIR_FAILED, IDS_INSTALL_TEMP_DIR_FAILED_BASE, nullptr);
return TEMP_DIR_FAILED;
}
base::FilePath uncompressed_archive;
RETURN_IF_ERROR(UnpackAndMaybePatchChromeArchive(
unpack_path, original_state, setup_exe, cmd_line, installer_state,
archive_type, uncompressed_archive));
VLOG(1) << "unpacked to " << unpack_path.value();
InstallStatus install_status = UNKNOWN_STATUS;
base::FilePath src_path(unpack_path.Append(kInstallSourceChromeDir));
std::unique_ptr<base::Version> installer_version(
GetMaxVersionFromArchiveDir(src_path));
if (!installer_version.get()) {
LOG(ERROR) << "Did not find any valid version in installer.";
install_status = INVALID_ARCHIVE;
installer_state.WriteInstallerResult(
install_status, IDS_INSTALL_INVALID_ARCHIVE_BASE, nullptr);
} else {
VLOG(1) << "version to install: " << installer_version->GetString();
bool proceed_with_installation = true;
if (!IsDowngradeAllowed(prefs)) {
const ProductState* product_state =
original_state.GetProductState(system_install);
if (product_state != nullptr &&
(product_state->version().CompareTo(*installer_version) > 0)) {
LOG(ERROR) << "Higher version of Chrome is already installed.";
int message_id = IDS_INSTALL_HIGHER_VERSION_BASE;
proceed_with_installation = false;
install_status = HIGHER_VERSION_EXISTS;
installer_state.WriteInstallerResult(install_status, message_id,
nullptr);
}
}
if (proceed_with_installation) {
base::FilePath prefs_source_path(
cmd_line.GetSwitchValueNative(switches::kInstallerData));
const base::Version current_version(
installer_state.GetCurrentVersion(original_state));
InstallParams install_params = {
installer_state, original_state, setup_exe,
current_version, uncompressed_archive, src_path,
temp_path.path(), *installer_version,
};
install_status =
InstallOrUpdateProduct(install_params, prefs_source_path, prefs);
int install_msg_base = IDS_INSTALL_FAILED_BASE;
base::FilePath chrome_exe;
std::wstring quoted_chrome_exe;
if (install_status == SAME_VERSION_REPAIR_FAILED) {
install_msg_base = IDS_SAME_VERSION_REPAIR_FAILED_BASE;
} else if (install_status != INSTALL_FAILED) {
if (installer_state.target_path().empty()) {
// If we failed to construct install path, it means the OS call to
// get %ProgramFiles% or %AppData% failed. Report this as failure.
install_msg_base = IDS_INSTALL_OS_ERROR_BASE;
install_status = OS_ERROR;
} else {
chrome_exe = installer_state.target_path().Append(kChromeExe);
quoted_chrome_exe =
GetPostInstallLaunchCommand(installer_state.target_path())
.GetCommandLineString();
install_msg_base = 0;
}
}
installer_state.SetStage(FINISHING);
bool do_not_register_for_update_launch = false;
prefs.GetBool(initial_preferences::kDoNotRegisterForUpdateLaunch,
&do_not_register_for_update_launch);
bool write_chrome_launch_string = (!do_not_register_for_update_launch &&
install_status != IN_USE_UPDATED);
installer_state.WriteInstallerResult(
install_status, install_msg_base,
write_chrome_launch_string ? "ed_chrome_exe : nullptr);
if (install_status == FIRST_INSTALL_SUCCESS) {
VLOG(1) << "First install successful.";
// We never want to launch Chrome in system level install mode.
bool do_not_launch_chrome = false;
prefs.GetBool(initial_preferences::kDoNotLaunchChrome,
&do_not_launch_chrome);
if (!system_install && !do_not_launch_chrome) {
LaunchChromeBrowser(installer_state.target_path());
}
} else if ((install_status == NEW_VERSION_UPDATED) ||
(install_status == IN_USE_UPDATED)) {
DCHECK_NE(chrome_exe.value(), std::wstring());
RemoveChromeLegacyRegistryKeys(chrome_exe);
}
}
}
// If the installation completed successfully...
if (InstallUtil::GetInstallReturnCode(install_status) == 0) {
// Update the DisplayVersion created by an MSI-based install.
std::string install_id;
if (prefs.GetString(installer::initial_preferences::kMsiProductId,
&install_id)) {
// A currently active MSI install will have specified the initial-
// preferences file on the command-line that includes the product-id.
// We must delay the setting of the DisplayVersion until after the
// grandparent "msiexec" process has exited.
base::FilePath new_setup =
installer_state.GetInstallerDirectory(*installer_version)
.Append(kSetupExe);
DelayedOverwriteDisplayVersions(new_setup, install_id, *installer_version,
installer_state.verbose_logging());
} else if (const auto* product_state =
original_state.GetProductState(system_install);
product_state) {
// Only when called by the MSI installer do we need to delay setting
// the DisplayVersion. In other runs, such as those done by the auto-
// update action, we set the value immediately.
// Get the app's MSI Product-ID from an entry in ClientState.
if (const std::wstring& app_guid = product_state->product_guid();
!app_guid.empty()) {
OverwriteDisplayVersions(
app_guid, base::UTF8ToWide(installer_version->GetString()));
}
}
}
// temp_path's dtor will take care of deleting or scheduling itself for
// deletion at reboot when this scope closes.
VLOG(1) << "Deleting temporary directory " << temp_path.path().value();
return install_status;
}
} // namespace installer
namespace {
class ScopedIgnoreResourceExhaustion {
public:
ScopedIgnoreResourceExhaustion() {
base::win::SetOnResourceExhaustedFunction(&DoNothing);
}
~ScopedIgnoreResourceExhaustion() {
base::win::SetOnResourceExhaustedFunction(nullptr);
}
private:
static void DoNothing() {}
};
int SetupMain() {
// Check to see if the CPU is supported before doing anything else. There's
// very little than can safely be accomplished if the CPU isn't supported
// since dependent libraries (e.g., base) may use invalid instructions.
if (!installer::IsProcessorSupported()) {
return installer::CPU_NOT_SUPPORTED;
}
// Persist histograms so they can be uploaded later. The storage directory is
// created during installation when the main WorkItemList is evaluated so
// disable storage directory creation in PersistentHistogramStorage.
base::PersistentHistogramStorage persistent_histogram_storage(
installer::kSetupHistogramAllocatorName,
base::PersistentHistogramStorage::StorageDirManagement::kUseExisting);
// The exit manager is in charge of calling the dtors of singletons.
base::AtExitManager exit_manager;
base::CommandLine::Init(0, nullptr);
std::string process_type =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kProcessType);
if (process_type == crash_reporter::switches::kCrashpadHandler) {
// Histogram storage is enabled at the very top of this wWinMain. Disable it
// when this process is decicated to crashpad as there is no directory in
// which to write them nor a browser to subsequently upload them.
persistent_histogram_storage.Disable();
return crash_reporter::RunAsCrashpadHandler(
*base::CommandLine::ForCurrentProcess(), base::FilePath(),
switches::kProcessType, switches::kUserDataDir);
}
// install_util uses chrome paths.
chrome::RegisterPathProvider();
const InitialPreferences& prefs = InitialPreferences::ForCurrentProcess();
installer::InitInstallerLogging(prefs);
const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
VLOG(1) << "Command Line: " << cmd_line.GetCommandLineString();
InitializeInstallDetails(cmd_line, prefs);
bool system_install = false;
prefs.GetBool(installer::initial_preferences::kSystemLevel, &system_install);
VLOG(1) << "system install is " << system_install;
InstallationState original_state;
original_state.Initialize();
InstallerState installer_state;
installer_state.Initialize(cmd_line, prefs, original_state);
persistent_histogram_storage.set_storage_base_dir(
installer_state.target_path());
installer::ConfigureCrashReporting(installer_state);
installer::SetInitialCrashKeys(installer_state);
installer::SetCrashKeysFromCommandLine(cmd_line);
// Make sure the process exits cleanly on unexpected errors.
base::EnableTerminationOnHeapCorruption();
base::EnableTerminationOnOutOfMemory();
logging::RegisterAbslAbortHook();
base::win::RegisterInvalidParamHandler();
base::win::SetupCRT(cmd_line);
// HandleVerifier detects and reports incorrect handle manipulations. It
// tracks handle operations on builds that support DCHECK only.
#if !DCHECK_IS_ON()
base::win::DisableHandleVerifier();
#elif !defined(COMPONENT_BUILD)
// Patch the main EXE on non-component builds when DCHECKs are enabled. This
// allows detection of third party code that might attempt to meddle with the
// process's handles. This must be done when single-threaded to avoid other
// threads attempting to make calls through the hooks while they are being
// emplaced.
base::debug::HandleHooks::AddIATPatch(CURRENT_MODULE());
#endif // !defined(COMPONENT_BUILD)
const bool is_uninstall = cmd_line.HasSwitch(installer::switches::kUninstall);
// Histogram storage is enabled at the very top of this wWinMain. Disable it
// during uninstall since there's neither a directory in which to write them
// nor a browser to subsequently upload them.
if (is_uninstall) {
persistent_histogram_storage.Disable();
}
// Check to make sure current system is Win10 or later. If not, log
// error message and get out.
if (!InstallUtil::IsOSSupported()) {
LOG(ERROR) << "Chrome only supports Windows 10 or later.";
installer_state.WriteInstallerResult(installer::OS_NOT_SUPPORTED,
IDS_INSTALL_OS_NOT_SUPPORTED_BASE,
nullptr);
return installer::OS_NOT_SUPPORTED;
}
// Initialize COM for use later.
std::optional<base::win::ScopedCOMInitializer> com_initializer;
{
// Temporarily ignore resource exhaustion to suppress crashes in case there
// are no ATOMs left -- the error is handled here by exiting gracefully.
ScopedIgnoreResourceExhaustion ignore_resource_exhaustion;
com_initializer.emplace();
if (!com_initializer->Succeeded()) {
installer_state.WriteInstallerResult(installer::OS_ERROR,
IDS_INSTALL_OS_ERROR_BASE, nullptr);
return installer::OS_ERROR;
}
}
// Make sure system_level is supported if requested. For historical reasons,
// system-level installs have never been supported for Chrome canary (SxS).
// This is a brand-specific policy for this particular mode. In general,
// system-level installation of secondary install modes is fully supported.
if (!install_static::InstallDetails::Get().supports_system_level() &&
(system_install ||
cmd_line.HasSwitch(installer::switches::kSelfDestruct) ||
cmd_line.HasSwitch(installer::switches::kRemoveChromeRegistration))) {
return installer::SXS_OPTION_NOT_SUPPORTED;
}
// Some switches only apply for modes that can be made the user's default
// browser.
if (!install_static::SupportsSetAsDefaultBrowser() &&
(cmd_line.HasSwitch(installer::switches::kMakeChromeDefault) ||
cmd_line.HasSwitch(installer::switches::kRegisterChromeBrowser))) {
return installer::SXS_OPTION_NOT_SUPPORTED;
}
// Some command line options are no longer supported and must error out.
if (installer::ContainsUnsupportedSwitch(cmd_line)) {
return installer::UNSUPPORTED_OPTION;
}
// A variety of installer operations require the path to the current
// executable. Get it once here for use throughout these operations. Note that
// the path service is the authoritative source for this path. One might think
// that CommandLine::GetProgram would suffice, but it won't since
// CreateProcess may have been called with a command line that is somewhat
// ambiguous (e.g., an unquoted path with spaces, or a path lacking the file
// extension), in which case CommandLineToArgv will not yield an argv with the
// true path to the program at position 0.
base::FilePath setup_exe;
base::PathService::Get(base::FILE_EXE, &setup_exe);
const base::Version current_version(
installer_state.GetCurrentVersion(original_state));
installer::ModifyParams modify_params = {
installer_state,
original_state,
setup_exe,
current_version,
};
// Histogram storage is enabled at the very top of this function. We disable
// it for kConfigureBrowserInDirectory because this switch intended for use
// by Chrome for Testing, which does not perform any metrics processing. If it
// someday does, this should be changed to set the storage directory to the
// value of the kConfigureBrowserInDirectory switch (the path to the directory
// containing chrome.exe).
if (cmd_line.HasSwitch(installer::switches::kConfigureBrowserInDirectory)) {
persistent_histogram_storage.Disable();
}
int exit_code = 0;
if (HandleNonInstallCmdLineOptions(modify_params, cmd_line, prefs,
&exit_code)) {
return exit_code;
}
if (system_install && !IsUserAnAdmin()) {
if (!cmd_line.HasSwitch(installer::switches::kRunAsAdmin)) {
base::CommandLine new_cmd(base::CommandLine::NO_PROGRAM);
new_cmd.AppendArguments(cmd_line, true);
// Append --run-as-admin flag to let the new instance of setup.exe know
// that we already tried to launch ourselves as admin.
new_cmd.AppendSwitch(installer::switches::kRunAsAdmin);
// If system_install became true due to an environment variable, append
// it to the command line here since env vars may not propagate past the
// elevation.
if (!new_cmd.HasSwitch(installer::switches::kSystemLevel)) {
new_cmd.AppendSwitch(installer::switches::kSystemLevel);
}
DWORD exe_exit_code = installer::UNKNOWN_STATUS;
InstallUtil::ExecuteExeAsAdmin(new_cmd, &exe_exit_code);
return exe_exit_code;
} else {
LOG(ERROR) << "Non admin user can not install system level Chrome.";
installer_state.WriteInstallerResult(installer::INSUFFICIENT_RIGHTS,
IDS_INSTALL_INSUFFICIENT_RIGHTS_BASE,
nullptr);
return installer::INSUFFICIENT_RIGHTS;
}
}
std::unique_ptr<installer::SetupSingleton> setup_singleton(
installer::SetupSingleton::Acquire(cmd_line, prefs, &original_state,
&installer_state));
if (!setup_singleton) {
installer_state.WriteInstallerResult(
installer::SETUP_SINGLETON_ACQUISITION_FAILED,
IDS_INSTALL_SINGLETON_ACQUISITION_FAILED_BASE, nullptr);
return installer::SETUP_SINGLETON_ACQUISITION_FAILED;
}
installer::InstallStatus install_status = installer::UNKNOWN_STATUS;
// If --uninstall option is given, uninstall the identified product(s)
if (is_uninstall) {
install_status =
UninstallProducts(original_state, installer_state, setup_exe, cmd_line);
} else {
// If --uninstall option is not specified, we assume it is install case.
install_status = InstallProducts(original_state, setup_exe, cmd_line, prefs,
&installer_state);
DoLegacyCleanups(installer_state, install_status);
}
UMA_HISTOGRAM_ENUMERATION("Setup.Install.Result", install_status,
installer::MAX_INSTALL_STATUS);
// Dump peak memory usage.
PROCESS_MEMORY_COUNTERS pmc;
if (::GetProcessMemoryInfo(::GetCurrentProcess(), &pmc, sizeof(pmc))) {
UMA_HISTOGRAM_MEMORY_KB("Setup.Install.PeakPagefileUsage",
base::saturated_cast<base::HistogramBase::Sample>(
pmc.PeakPagefileUsage / 1024));
UMA_HISTOGRAM_MEMORY_KB("Setup.Install.PeakWorkingSetSize",
base::saturated_cast<base::HistogramBase::Sample>(
pmc.PeakWorkingSetSize / 1024));
}
int return_code = 0;
// MSI demands that custom actions always return 0 (ERROR_SUCCESS) or it will
// rollback the action. If we're uninstalling we want to avoid this, so always
// report success, squashing any more informative return codes.
if (!(installer_state.is_msi() && is_uninstall)) {
// Note that we allow the status installer::UNINSTALL_REQUIRES_REBOOT
// to pass through, since this is only returned on uninstall which is
// never invoked directly by Google Update.
return_code = InstallUtil::GetInstallReturnCode(install_status);
}
VLOG(1) << "Installation complete, returning: " << return_code;
return return_code;
}
} // namespace
int WINAPI wWinMain(HINSTANCE instance,
HINSTANCE prev_instance,
wchar_t* command_line,
int show_command) {
const auto process_exit_code = SetupMain();
// https://crbug.com/896565: Graceful shutdown sometimes fails for reasons out
// of the installer's control. Crashes from such failures are inactionable, so
// terminate the process forthwith. Do not use
// base::Process::TerminateCurrentProcessImmediately because it will crash the
// process with int 3 in cases where ::TerminateProcess returns; see
// https://crbug.com/1489598. It is better for the installer to try to return
// the actual exit code (risking the original crash).
#if BUILDFLAG(CLANG_PROFILING)
base::WriteClangProfilingProfile();
#endif
::SetLastError(ERROR_SUCCESS);
const auto terminate_result = ::TerminateProcess(
::GetCurrentProcess(), static_cast<UINT>(process_exit_code));
// It is unexpected that this code is reached. In the event that it is,
// capture error information left behind by TerminateProcess in case the
// process crashes during exit and put it on the stack in the hopes that
// we can find it in a post-return crash dump.
const auto terminate_error_code = ::GetLastError();
DWORD exit_codes[] = {
0xDEADBECF,
static_cast<DWORD>(process_exit_code),
static_cast<DWORD>(terminate_result),
terminate_error_code,
0xDEADBEDF,
};
base::debug::Alias(exit_codes);
return process_exit_code;
}