blob: a3f8fd6cfb61fa1aacd022457055c114867253b1 [file] [log] [blame]
// Copyright 2017 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/browser/conflicts/module_inspector_win.h"
#include <utility>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chrome/browser/after_startup_task_utils.h"
#include "chrome/browser/conflicts/module_info_util_win.h"
#include "chrome/services/util_win/public/mojom/constants.mojom.h"
#include "content/public/common/service_manager_connection.h"
#include "services/service_manager/public/cpp/connector.h"
namespace {
constexpr base::Feature kWinOOPInspectModuleFeature = {
"WinOOPInspectModule", base::FEATURE_DISABLED_BY_DEFAULT};
constexpr int kConnectionErrorRetryCount = 10;
StringMapping GetPathMapping() {
return GetEnvironmentVariablesMapping({
L"LOCALAPPDATA", L"ProgramFiles", L"ProgramData", L"USERPROFILE",
L"SystemRoot", L"TEMP", L"TMP", L"CommonProgramFiles",
});
}
// Returns a bound pointer to the UtilWin service.
chrome::mojom::UtilWinPtr ConnectToUtilWinService(
base::OnceClosure connection_error_handler) {
chrome::mojom::UtilWinPtr util_win_ptr;
content::ServiceManagerConnection::GetForProcess()
->GetConnector()
->BindInterface(chrome::mojom::kUtilWinServiceName, &util_win_ptr);
util_win_ptr.set_connection_error_handler(
std::move(connection_error_handler));
return util_win_ptr;
}
void ReportConnectionError(bool value) {
base::UmaHistogramBoolean("Windows.InspectModule.ConnectionError", value);
}
} // namespace
ModuleInspector::ModuleInspector(
const OnModuleInspectedCallback& on_module_inspected_callback)
: on_module_inspected_callback_(on_module_inspected_callback),
is_after_startup_(false),
task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
path_mapping_(GetPathMapping()),
connection_error_retry_count_(kConnectionErrorRetryCount),
weak_ptr_factory_(this) {
// Use AfterStartupTaskUtils to be notified when startup is finished.
AfterStartupTaskUtils::PostTask(
FROM_HERE, base::SequencedTaskRunnerHandle::Get(),
base::BindOnce(&ModuleInspector::OnStartupFinished,
weak_ptr_factory_.GetWeakPtr()));
}
ModuleInspector::~ModuleInspector() = default;
void ModuleInspector::AddModule(const ModuleInfoKey& module_key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
bool was_queue_empty = queue_.empty();
queue_.push(module_key);
// If the queue was empty before adding the current module, then the
// inspection must be started.
if (is_after_startup_ && was_queue_empty)
StartInspectingModule();
}
void ModuleInspector::IncreaseInspectionPriority() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Create a task runner with higher priority so that future inspections are
// done faster.
task_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN});
// Assume startup is finished to immediately begin inspecting modules.
OnStartupFinished();
}
bool ModuleInspector::IsIdle() {
return queue_.empty();
}
void ModuleInspector::OnStartupFinished() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// This function will be invoked twice if IncreaseInspectionPriority() is
// called.
if (is_after_startup_)
return;
is_after_startup_ = true;
if (!queue_.empty())
StartInspectingModule();
}
void ModuleInspector::OnUtilWinServiceConnectionError() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ReportConnectionError(true);
// Reset the pointer to the service.
util_win_ptr_ = nullptr;
// Restart inspection for the current module, only if the retry limit wasn't
// reached.
if (connection_error_retry_count_--)
StartInspectingModule();
}
void ModuleInspector::StartInspectingModule() {
DCHECK(is_after_startup_);
DCHECK(!queue_.empty());
const ModuleInfoKey& module_key = queue_.front();
// There is a small priority inversion that happens when
// IncreaseInspectionPriority() is called while a module is currently being
// inspected.
//
// This is because all the subsequent tasks will be posted at a higher
// priority, but they are waiting on the current task that is currently
// running at a lower priority.
//
// In practice, this is not an issue because the only caller of
// IncreaseInspectionPriority() (chrome://conflicts) does not depend on the
// inspection to finish synchronously and is not blocking anything else.
if (base::FeatureList::IsEnabled(kWinOOPInspectModuleFeature)) {
// Make sure the pointer is bound to the service first.
if (!util_win_ptr_) {
util_win_ptr_ = ConnectToUtilWinService(
base::BindOnce(&ModuleInspector::OnUtilWinServiceConnectionError,
base::Unretained(this)));
ReportConnectionError(false);
}
util_win_ptr_->InspectModule(
module_key.module_path,
base::BindOnce(&ModuleInspector::OnInspectionFinished,
weak_ptr_factory_.GetWeakPtr(), module_key));
} else {
base::PostTaskAndReplyWithResult(
task_runner_.get(), FROM_HERE,
base::BindOnce(&InspectModule, module_key.module_path),
base::BindOnce(&ModuleInspector::OnInspectionFinished,
weak_ptr_factory_.GetWeakPtr(), module_key));
}
}
void ModuleInspector::OnInspectionFinished(
const ModuleInfoKey& module_key,
ModuleInspectionResult inspection_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Convert the prefix of known Windows directories to their environment
// variable mappings (ie, %systemroot$). This makes i18n localized paths
// easily comparable.
CollapseMatchingPrefixInPath(path_mapping_, &inspection_result.location);
// Pop first, because the callback may want to know if there is any work left
// to be done, which is caracterized by a non-empty queue.
queue_.pop();
on_module_inspected_callback_.Run(module_key, std::move(inspection_result));
// Free the pointer to the UtilWin service to clean up the utility process
// when it is no longer needed. While this code is only ever needed in the
// case the WinOOPInspectModule feature is enabled, it's faster to check the
// value of the pointer than to check the feature status.
if (queue_.empty() && util_win_ptr_)
util_win_ptr_ = nullptr;
// Continue the work.
if (!queue_.empty())
StartInspectingModule();
}