| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/win/conflicts/module_inspector.h" |
| |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/path_service.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/win/conflicts/module_info_util.h" |
| #include "chrome/browser/win/util_win_service.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace { |
| |
| // The maximum amount of time a stale entry is kept in the cache before it is |
| // deleted. |
| constexpr base::TimeDelta kMaxEntryAge = base::Days(30); |
| |
| constexpr int kConnectionErrorRetryCount = 10; |
| |
| StringMapping GetPathMapping() { |
| return GetEnvironmentVariablesMapping({ |
| L"LOCALAPPDATA", |
| L"ProgramFiles", |
| L"ProgramData", |
| L"USERPROFILE", |
| L"SystemRoot", |
| L"TEMP", |
| L"TMP", |
| L"CommonProgramFiles", |
| }); |
| } |
| |
| // Reads the inspection results cache. |
| InspectionResultsCache ReadInspectionResultsCacheOnBackgroundSequence( |
| const base::FilePath& file_path) { |
| InspectionResultsCache inspection_results_cache; |
| |
| uint32_t min_time_stamp = |
| CalculateTimeStamp(base::Time::Now() - kMaxEntryAge); |
| ReadInspectionResultsCache(file_path, min_time_stamp, |
| &inspection_results_cache); |
| |
| return inspection_results_cache; |
| } |
| |
| // Writes the inspection results cache to disk. |
| void WriteInspectionResultCacheOnBackgroundSequence( |
| const base::FilePath& file_path, |
| const InspectionResultsCache& inspection_results_cache) { |
| WriteInspectionResultsCache(file_path, inspection_results_cache); |
| } |
| |
| } // namespace |
| |
| // static |
| constexpr base::TimeDelta ModuleInspector::kFlushInspectionResultsTimerTimeout; |
| |
| ModuleInspector::ModuleInspector( |
| const OnModuleInspectedCallback& on_module_inspected_callback) |
| : on_module_inspected_callback_(on_module_inspected_callback), |
| is_started_(false), |
| util_win_factory_callback_( |
| base::BindRepeating(&LaunchUtilWinServiceInstance)), |
| path_mapping_(GetPathMapping()), |
| cache_task_runner_(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})), |
| inspection_results_cache_read_(false), |
| flush_inspection_results_timer_( |
| FROM_HERE, |
| kFlushInspectionResultsTimerTimeout, |
| base::BindRepeating( |
| &ModuleInspector::MaybeUpdateInspectionResultsCache, |
| base::Unretained(this))), |
| has_new_inspection_results_(false), |
| connection_error_retry_count_(kConnectionErrorRetryCount), |
| is_waiting_on_util_win_service_(false) {} |
| |
| ModuleInspector::~ModuleInspector() = default; |
| |
| void ModuleInspector::StartInspection() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // This function can be invoked multiple times. |
| if (is_started_) { |
| return; |
| } |
| |
| is_started_ = true; |
| |
| // Read the inspection cache now that it is needed. |
| cache_task_runner_->PostTaskAndReplyWithResult( |
| FROM_HERE, |
| base::BindOnce(&ReadInspectionResultsCacheOnBackgroundSequence, |
| GetInspectionResultsCachePath()), |
| base::BindOnce(&ModuleInspector::OnInspectionResultsCacheRead, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| 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 (inspection_results_cache_read_ && was_queue_empty) |
| StartInspectingModule(); |
| } |
| |
| bool ModuleInspector::IsIdle() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return queue_.empty(); |
| } |
| |
| void ModuleInspector::OnModuleDatabaseIdle() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| MaybeUpdateInspectionResultsCache(); |
| } |
| |
| // static |
| base::FilePath ModuleInspector::GetInspectionResultsCachePath() { |
| base::FilePath user_data_dir; |
| if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) |
| return base::FilePath(); |
| |
| return user_data_dir.Append(L"Module Info Cache"); |
| } |
| |
| void ModuleInspector::SetUtilWinFactoryCallbackForTesting( |
| UtilWinFactoryCallback util_win_factory_callback) { |
| util_win_factory_callback_ = std::move(util_win_factory_callback); |
| } |
| |
| void ModuleInspector::EnsureUtilWinServiceBound() { |
| if (remote_util_win_) |
| return; |
| |
| remote_util_win_ = util_win_factory_callback_.Run(); |
| remote_util_win_.reset_on_idle_timeout(base::Seconds(5)); |
| remote_util_win_.set_disconnect_handler( |
| base::BindOnce(&ModuleInspector::OnUtilWinServiceConnectionError, |
| base::Unretained(this))); |
| } |
| |
| void ModuleInspector::OnInspectionResultsCacheRead( |
| InspectionResultsCache inspection_results_cache) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(is_started_); |
| DCHECK(!inspection_results_cache_read_); |
| |
| inspection_results_cache_read_ = true; |
| inspection_results_cache_ = std::move(inspection_results_cache); |
| |
| if (!queue_.empty()) |
| StartInspectingModule(); |
| } |
| |
| void ModuleInspector::OnUtilWinServiceConnectionError() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| // Disconnect from the service. |
| remote_util_win_.reset(); |
| |
| // If the retry limit was reached, give up. |
| if (connection_error_retry_count_ == 0) |
| return; |
| --connection_error_retry_count_; |
| |
| bool was_waiting_on_util_win_service = is_waiting_on_util_win_service_; |
| is_waiting_on_util_win_service_ = false; |
| |
| // If this connection error happened while the ModuleInspector was waiting on |
| // the service, restart the inspection process. |
| if (was_waiting_on_util_win_service) |
| StartInspectingModule(); |
| } |
| |
| void ModuleInspector::StartInspectingModule() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(inspection_results_cache_read_); |
| DCHECK(!queue_.empty()); |
| |
| const ModuleInfoKey& module_key = queue_.front(); |
| |
| // First check if the cache already contains the inspection result. |
| auto inspection_result = |
| GetInspectionResultFromCache(module_key, &inspection_results_cache_); |
| if (inspection_result) { |
| // Send asynchronously or this might cause a stack overflow. |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&ModuleInspector::OnInspectionFinished, |
| weak_ptr_factory_.GetWeakPtr(), module_key, |
| std::move(*inspection_result))); |
| return; |
| } |
| |
| EnsureUtilWinServiceBound(); |
| |
| is_waiting_on_util_win_service_ = true; |
| remote_util_win_->InspectModule( |
| module_key.module_path, |
| base::BindOnce(&ModuleInspector::OnModuleNewlyInspected, |
| weak_ptr_factory_.GetWeakPtr(), module_key)); |
| } |
| |
| void ModuleInspector::OnModuleNewlyInspected( |
| const ModuleInfoKey& module_key, |
| ModuleInspectionResult inspection_result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| is_waiting_on_util_win_service_ = false; |
| |
| // 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); |
| |
| has_new_inspection_results_ = true; |
| if (!flush_inspection_results_timer_.IsRunning()) |
| flush_inspection_results_timer_.Reset(); |
| AddInspectionResultToCache(module_key, inspection_result, |
| &inspection_results_cache_); |
| |
| OnInspectionFinished(module_key, std::move(inspection_result)); |
| } |
| |
| void ModuleInspector::OnInspectionFinished( |
| const ModuleInfoKey& module_key, |
| ModuleInspectionResult inspection_result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| DCHECK(!queue_.empty()); |
| DCHECK(queue_.front() == module_key); |
| |
| // 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)); |
| |
| // Continue the work. |
| if (!queue_.empty()) |
| StartInspectingModule(); |
| } |
| |
| void ModuleInspector::MaybeUpdateInspectionResultsCache() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!has_new_inspection_results_) |
| return; |
| |
| has_new_inspection_results_ = false; |
| cache_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&WriteInspectionResultCacheOnBackgroundSequence, |
| GetInspectionResultsCachePath(), |
| inspection_results_cache_)); |
| } |