blob: ef241720608d25a8d561fd375fb13840e0fca494 [file] [log] [blame]
// Copyright 2019 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/chrome_cleaner/engines/controllers/main_controller.h"
#include <shellapi.h>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/process/process_handle.h"
#include "base/process/process_metrics.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/simple_thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/chrome_cleaner/components/component_manager.h"
#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
#include "chrome/chrome_cleaner/engines/common/engine_digest_verifier.h"
#include "chrome/chrome_cleaner/logging/logging_service_api.h"
#include "chrome/chrome_cleaner/logging/registry_logger.h"
#include "chrome/chrome_cleaner/logging/scoped_timed_task_logger.h"
#include "chrome/chrome_cleaner/os/early_exit.h"
#include "chrome/chrome_cleaner/os/process.h"
#include "chrome/chrome_cleaner/os/rebooter.h"
#include "chrome/chrome_cleaner/os/rebooter_api.h"
#include "chrome/chrome_cleaner/os/registry_util.h"
#include "chrome/chrome_cleaner/os/shutdown_watchdog.h"
#include "chrome/chrome_cleaner/os/system_util_cleaner.h"
#include "chrome/chrome_cleaner/os/task_scheduler.h"
#include "chrome/chrome_cleaner/scanner/signature_matcher_api.h"
#include "chrome/chrome_cleaner/settings/settings.h"
#include "chrome/chrome_cleaner/ui/chrome_proxy_main_dialog.h"
#include "chrome/chrome_cleaner/ui/silent_main_dialog.h"
#include "components/chrome_cleaner/public/constants/result_codes.h"
namespace chrome_cleaner {
namespace {
const base::TimeDelta kUserResponseWatchdogTimeout =
base::TimeDelta::FromHours(2);
const base::TimeDelta kCleanerWatchdogTimeout = base::TimeDelta::FromHours(2);
// Log memory usage, CPU usage and various IO counters.
void LogSystemResourceUsage() {
base::ProcessHandle process_handle = base::GetCurrentProcessHandle();
SystemResourceUsage stats;
if (!GetSystemResourceUsage(process_handle, &stats))
return;
LoggingServiceAPI::GetInstance()->LogProcessInformation(
SandboxType::kNonSandboxed, stats);
// TODO(veranika): remove raw logging of resource usage.
LOG(INFO) << "User time: '" << stats.user_time.InSeconds() << "' seconds.";
LOG(INFO) << "Kernel time: '" << stats.kernel_time.InSeconds()
<< "' seconds.";
// Log memory usage.
LOG(INFO) << "Peak memory used: '" << stats.peak_working_set_size / 1024
<< "' KB.";
// Log IO counters.
LOG(INFO) << "IO counters: "
<< "ReadOperationCount = '" << stats.io_counters.ReadOperationCount
<< "', WriteOperationCount = '"
<< stats.io_counters.WriteOperationCount
<< "', OtherOperationCount = '"
<< stats.io_counters.OtherOperationCount
<< "', ReadTransferCount = '" << stats.io_counters.ReadTransferCount
<< "', WriteTransferCount = '"
<< stats.io_counters.WriteTransferCount
<< "', OtherTransferCount = '"
<< stats.io_counters.OtherTransferCount << "'";
std::map<SandboxType, SystemResourceUsage> sbox_process_usage =
GetSandboxSystemResourceUsage();
for (const auto& type_usage : sbox_process_usage) {
LoggingServiceAPI::GetInstance()->LogProcessInformation(type_usage.first,
type_usage.second);
}
}
} // namespace
class MainController::ChromePromptConnectionErrorHandler
: public ChromePromptIPC::ErrorHandler {
public:
ChromePromptConnectionErrorHandler(
base::WeakPtr<MainController> main_controller,
scoped_refptr<base::SingleThreadTaskRunner> task_runner);
~ChromePromptConnectionErrorHandler() override;
void OnConnectionClosed() override;
void OnConnectionClosedAfterDone() override;
private:
// Task runner where IPC connection error handling tasks must be posted to.
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Pointer to the main controller that created this object that will
// respond to connection error. The weak pointer will be invalidated once
// the main controller is destroyed, so we don't try to access it from
// OnConnectionClosed().
base::WeakPtr<MainController> main_controller_;
};
MainController::MainController(RebooterAPI* rebooter,
RegistryLogger* registry_logger,
ChromePromptIPC* chrome_prompt_ipc)
: user_response_watchdog_timeout_(kUserResponseWatchdogTimeout),
cleaning_watchdog_timeout_(kCleanerWatchdogTimeout),
rebooter_(rebooter),
component_manager_(this),
result_code_(RESULT_CODE_INVALID),
registry_logger_(registry_logger),
weak_factory_(this) {
Settings* settings = Settings::GetInstance();
ExecutionMode execution_mode = settings->execution_mode();
DCHECK(execution_mode == ExecutionMode::kScanning ||
execution_mode == ExecutionMode::kCleanup);
// The IPC object should only be available if execution mode is kScanning.
DCHECK_EQ(chrome_prompt_ipc != nullptr,
execution_mode == ExecutionMode::kScanning);
LoggingServiceAPI::GetInstance()->EnableUploads(
settings->logs_upload_allowed(), registry_logger_);
if (execution_mode == ExecutionMode::kScanning) {
// Connection handler object is leaked since it must outlive the
// ChromePromptIPC object. The weak pointer to the main controller is
// invalidated on object destruction, so it's not accessed once this
// main controller object no longer exists.
chrome_prompt_ipc->Initialize(new ChromePromptConnectionErrorHandler(
weak_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get()));
main_dialog_.reset(new ChromeProxyMainDialog(this, chrome_prompt_ipc));
} else if (execution_mode == ExecutionMode::kCleanup) {
main_dialog_.reset(new SilentMainDialog(this));
} else {
NOTREACHED();
}
if (settings->scanning_timeout_overridden())
scanning_watchdog_timeout_ = settings->scanning_timeout();
if (settings->user_response_timeout_overridden())
user_response_watchdog_timeout_ = settings->user_response_timeout();
if (settings->cleaning_timeout_overridden())
cleaning_watchdog_timeout_ = settings->cleaning_timeout();
}
SandboxConnectionErrorCallback
MainController::GetSandboxConnectionErrorCallback() {
// Returns a callback bound with the current task runner and the weak pointer
// of main controller.
return base::BindRepeating(MainController::CallSandboxConnectionClosed,
base::SequencedTaskRunnerHandle::Get(),
weak_factory_.GetWeakPtr());
}
MainDialogAPI* MainController::main_dialog() {
return main_dialog_.get();
}
MainController::~MainController() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(joenotcharles): Cleanup RunUntilIdle usage in loops.
CHECK(!base::RunLoop::IsRunningOnCurrentThread());
if (engine_facade_) {
while (!cleaner()->IsCompletelyDone() || !scanner()->IsCompletelyDone())
base::RunLoop().RunUntilIdle();
}
// We must make sure to close the components before we destroy the manager.
component_manager_.CloseAllComponents(result_code_);
}
void MainController::AddComponent(std::unique_ptr<ComponentAPI> component) {
component_manager_.AddComponent(std::move(component));
}
void MainController::SetEngineFacade(EngineFacadeInterface* engine_facade) {
engine_facade_ = engine_facade;
if (!Settings::GetInstance()->scanning_timeout_overridden()) {
scanning_watchdog_timeout_ = engine_facade_->GetScanningWatchdogTimeout();
}
}
Scanner* MainController::scanner() {
DCHECK(engine_facade_);
return engine_facade_->GetScanner();
}
Cleaner* MainController::cleaner() {
DCHECK(engine_facade_);
return engine_facade_->GetCleaner();
}
ResultCode MainController::ScanAndClean() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Enable watchdog for non-legacy execution modes. If this process is in
// scanning execution mode, then we will set a timeout for the scanner, that
// will be disarmed once scan has finished. If this is in cleanup mode, the
// timeout will only be disarmed once cleanup has finished.
if (Settings::GetInstance()->execution_mode() == ExecutionMode::kScanning &&
!scanning_watchdog_timeout_.is_zero()) {
watchdog_ = std::make_unique<ShutdownWatchdog>(
scanning_watchdog_timeout_,
base::BindOnce(&MainController::WatchdogTimeoutCallback,
base::Unretained(this), TimedOutStage::kScanning));
watchdog_->Arm();
} else if (Settings::GetInstance()->execution_mode() ==
ExecutionMode::kCleanup &&
!cleaning_watchdog_timeout_.is_zero()) {
watchdog_ = std::make_unique<ShutdownWatchdog>(
cleaning_watchdog_timeout_,
base::BindOnce(&MainController::WatchdogTimeoutCallback,
base::Unretained(this), TimedOutStage::kCleaning));
watchdog_->Arm();
}
if (!main_dialog()->Create())
return RESULT_CODE_FAILED;
scan_start_time_ = base::Time::Now();
component_manager_.PreScan();
base::RunLoop run_loop;
DCHECK(!quit_closure_);
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
// By this point, result_code_ should have been set.
DCHECK_NE(result_code_, RESULT_CODE_INVALID);
return result_code_;
}
ResultCode MainController::ValidateCleanup() {
ScopedTimedTaskLogger scoped_timed_task_logger("ValidateCleanup");
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(HasAdminRights())
<< "Implementing scan without admin rights? Please validate that the "
"Event code at startup works correctly.";
scanner()->Start(
base::BindRepeating(&MainController::FoundUwSCallback,
base::Unretained(this)),
base::BindOnce(&MainController::DoneValidating, base::Unretained(this)));
base::RunLoop run_loop;
DCHECK(!quit_closure_);
quit_closure_ = run_loop.QuitClosure();
run_loop.Run();
// |result_code_| was set by DoneValidating.
return result_code_;
}
MainController::ChromePromptConnectionErrorHandler::
ChromePromptConnectionErrorHandler(
base::WeakPtr<MainController> main_controller,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner), main_controller_(std::move(main_controller)) {}
MainController::ChromePromptConnectionErrorHandler::
~ChromePromptConnectionErrorHandler() = default;
void MainController::ChromePromptConnectionErrorHandler::OnConnectionClosed() {
// Posts an OnConnectionClosed() task to the main controller's thread,
// if the main controller still exists. If the weak pointer to the main
// controller has been invalidated, this will be a no-op.
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&MainController::OnChromePromptConnectionClosed,
main_controller_));
}
void MainController::ChromePromptConnectionErrorHandler::
OnConnectionClosedAfterDone() {
// No action required if the pipe is disconnected when it's no longer needed.
// Changing this method to access the |main_controller_| will require
// changing the error handler's lifetime.
}
void MainController::FoundUwSCallback(UwSId pup_id) {
if (PUPData::HasRemovalFlag(PUPData::GetPUP(pup_id)->signature().flags))
removable_uws_found_ = true;
}
void MainController::DoneScanning(ResultCode result_code,
const std::vector<UwSId>& found_pups) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
found_pups_ = found_pups;
if (result_code != RESULT_CODE_SUCCESS)
result_code_ = result_code;
component_manager_.PostScan(found_pups);
}
void MainController::DoneValidating(ResultCode result_code,
const std::vector<UwSId>& found_pups) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Get PUPs to remove.
std::vector<UwSId> found_pups_to_remove;
PUPData::ChoosePUPs(found_pups, &PUPData::HasRemovalFlag,
&found_pups_to_remove);
if (result_code == RESULT_CODE_SUCCESS && !found_pups_to_remove.empty()) {
cleaner()->StartPostReboot(
found_pups_to_remove,
base::BindOnce(&MainController::DoneCleanupValidation,
base::Unretained(this)));
} else {
DoneCleanupValidation(result_code);
}
}
void MainController::DoneCleanupValidation(ResultCode validation_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(Rebooter::IsPostReboot());
// If scanner didn't find removable uws during the post-reboot run,
// |validation_code| is its return code. Otherwise, it's the cleaner's return
// code. Confused? Me too!
if (validation_code == RESULT_CODE_SUCCESS ||
validation_code == RESULT_CODE_NO_PUPS_FOUND ||
validation_code == RESULT_CODE_REPORT_ONLY_PUPS_FOUND) {
result_code_ = RESULT_CODE_POST_REBOOT_SUCCESS;
} else {
// TODO(veranika): This hides the actual error code. Post-reboot-specific
// exit codes should be deprecated.
LOG(ERROR) << "CleanupValidation failed: " << validation_code;
result_code_ = RESULT_CODE_POST_CLEANUP_VALIDATION_FAILED;
}
rebooter_->UnregisterPostRebootRun();
component_manager_.PostValidation(result_code_);
// Unlike most of the ComponentManager API, this call is synchronous, so we
// don't need to wait for a ComponentManagerDelegate API call to be made
// before continuing, and we can simply quit here. :-)
Quit();
}
void MainController::OnChromePromptConnectionClosed() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
scanner()->Stop();
// There's no need to stop the cleaner since the Chrome prompt connection is
// already closed in the cleaning phase.
DCHECK(cleaner()->IsCompletelyDone());
result_code_ = RESULT_CODE_CHROME_PROMPT_IPC_DISCONNECTED_TOO_SOON;
Quit();
}
// static
// Posts a task with the weak pointer of main controller to report error.
void MainController::CallSandboxConnectionClosed(
scoped_refptr<base::SequencedTaskRunner> task_runner,
base::WeakPtr<MainController> main_controller,
SandboxType sandbox_type) {
// Make sure the error handler runs on the correct thread.
task_runner->PostTask(
FROM_HERE, base::BindOnce(&MainController::OnSandboxConnectionClosed,
main_controller, sandbox_type));
}
void MainController::OnSandboxConnectionClosed(SandboxType sandbox_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ResultCode result_code = GetResultCodeForSandboxConnectionError(sandbox_type);
WriteEarlyExitMetrics(result_code);
EarlyExit(result_code);
}
void MainController::CleanupDone(ResultCode clean_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
result_code_ = clean_result;
component_manager_.PostCleanup(result_code_, rebooter_);
}
void MainController::PreScanDone() {
scanner()->Start(
base::BindRepeating(&MainController::FoundUwSCallback,
base::Unretained(this)),
base::BindOnce(&MainController::DoneScanning, base::Unretained(this)));
}
void MainController::PostScanDone() {
DCHECK(!scan_start_time_.is_null());
finished_scanning_ = true;
base::TimeDelta scan_time = base::Time::Now() - scan_start_time_;
LOG(INFO) << "Scanning took '" << scan_time.InSeconds() << "' seconds.";
// Split PUPs into report and remove vectors.
std::vector<UwSId> found_pups_to_remove;
PUPData::ChoosePUPs(found_pups_, &PUPData::HasRemovalFlag,
&found_pups_to_remove);
bool report_only_found =
PUPData::HasFlaggedPUP(found_pups_, &PUPData::HasReportOnlyFlag);
bool malicious_found =
PUPData::HasFlaggedPUP(found_pups_, &PUPData::HasConfirmedUwSFlag);
ResultCode fallback_logs_result_code = RESULT_CODE_INVALID;
bool removable_pups_found = false;
if (result_code_ != RESULT_CODE_INVALID) {
// The result code is set before scan is done only if there was an error.
// Do not offer cleanup.
fallback_logs_result_code = result_code_;
} else if (found_pups_to_remove.empty()) {
if (malicious_found) {
result_code_ = RESULT_CODE_EXAMINED_FOR_REMOVAL_ONLY;
} else if (report_only_found) {
result_code_ = RESULT_CODE_REPORT_ONLY_PUPS_FOUND;
} else {
result_code_ = RESULT_CODE_NO_PUPS_FOUND;
}
fallback_logs_result_code = result_code_;
} else {
if (!cleaner()->CanClean(found_pups_to_remove)) {
LOG(ERROR) << "GetCleanupRequirements failure, skip cleanup.";
result_code_ = RESULT_CODE_CLEANUP_REQUIREMENTS_FAILED;
fallback_logs_result_code = result_code_;
} else {
removable_pups_found = true;
found_pups_ = found_pups_to_remove;
}
}
// Stop the watchdog set for the scanning phase if this process is running in
// scanning mode.
if (Settings::GetInstance()->execution_mode() == ExecutionMode::kScanning &&
watchdog_) {
watchdog_->Disarm();
watchdog_.reset();
}
if (!removable_pups_found) {
main_dialog()->NoPUPsFound();
} else {
if (Settings::GetInstance()->execution_mode() == ExecutionMode::kScanning &&
!user_response_watchdog_timeout_.is_zero()) {
// Sets a timeout for the user response. This will be disarmed once we
// receive a response from Chrome.
watchdog_ = std::make_unique<ShutdownWatchdog>(
user_response_watchdog_timeout_,
base::BindOnce(&MainController::WatchdogTimeoutCallback,
base::Unretained(this),
TimedOutStage::kWaitingForPrompt));
watchdog_->Arm();
}
main_dialog()->ConfirmCleanupIfNeeded(found_pups_to_remove,
GetDigestVerifier());
fallback_logs_result_code = RESULT_CODE_FAILED_TO_START_CLEANUP;
}
// Now that the user had the opportunity to opt-out of logs upload, schedule a
// fall back logs upload in case something goes wrong later on.
LoggingServiceAPI::GetInstance()->ScheduleFallbackLogsUpload(
registry_logger_, fallback_logs_result_code);
}
void MainController::PreCleanupDone() {
cleaner()->Start(found_pups_, base::BindOnce(&MainController::CleanupDone,
base::Unretained(this)));
}
void MainController::PostCleanupDone() {
// Don't call CleanupDone when the user already canceled.
if (result_code_ == RESULT_CODE_CANCELED) {
Quit();
return;
}
// Disable the watchdog that was set when the process in cleanup mode
// started, which includes scanning and cleaning.
Settings* settings = Settings::GetInstance();
if (settings->execution_mode() == ExecutionMode::kCleanup && watchdog_) {
watchdog_->Disarm();
watchdog_.reset();
}
DCHECK(!clean_start_time_.is_null());
finished_cleaning_ = true;
base::TimeDelta clean_time = base::Time::Now() - clean_start_time_;
LOG(INFO) << "Cleaning took '" << clean_time.InSeconds() << "' seconds.";
// TODO(joenotcharles): Add proper tests for the main controller's interaction
// with the rebooter class
if (result_code_ == RESULT_CODE_PENDING_REBOOT &&
settings->execution_mode() != ExecutionMode::kScanning) {
// TODO(csharp): Find a way to handle Rebooter errors.
// We currently ignore failure to run again after restart, as long as a
// restart happen, at least we can hope that the job will be done, we just
// won't be able to confirm it.
if (!rebooter_->RegisterPostRebootRun(
base::CommandLine::ForCurrentProcess(), settings->cleanup_id(),
settings->execution_mode(),
LoggingServiceAPI::GetInstance()->uploads_enabled()))
PLOG(ERROR) << "Failed to RegisterPostRebootRun().";
}
main_dialog()->CleanupDone(result_code_);
}
void MainController::AcceptedCleanup(bool accepted) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Disable the watchdog that was set when the user was prompted.
if (Settings::GetInstance()->execution_mode() == ExecutionMode::kScanning &&
watchdog_) {
watchdog_->Disarm();
watchdog_.reset();
}
if (!accepted) {
result_code_ = RESULT_CODE_CLEANUP_PROMPT_DENIED;
Quit();
return;
}
registry_logger_->WriteExitCode(RESULT_CODE_PENDING);
UploadLogs(L"-intermediate", false);
// Resets the global indication that a cleanup completed. This will be
// set again once the current cleanup completes and the information will
// be used by Chrome to reset settings for the profile that accepted the
// prompt.
registry_logger_->ResetCompletedCleanup();
clean_start_time_ = base::Time::Now();
component_manager_.PreCleanup();
}
void MainController::OnClose() {
// In case the window get closed while scanning/cleaning.
scanner()->Stop();
cleaner()->Stop();
if (result_code_ == RESULT_CODE_INVALID)
result_code_ = RESULT_CODE_CANCELED;
if (!scan_start_time_.is_null() && !finished_scanning_) {
base::TimeDelta scan_time = base::Time::Now() - scan_start_time_;
LOG(INFO) << "Scanning was canceled after '" << scan_time.InSeconds()
<< "' seconds spent in the scanning phase.";
}
if (!clean_start_time_.is_null() && !finished_cleaning_) {
base::TimeDelta clean_time = base::Time::Now() - clean_start_time_;
LOG(INFO) << "Cleaning was canceled after '" << clean_time.InSeconds()
<< "' seconds spent in the cleaning phase.";
}
LogSystemResourceUsage();
LoggingServiceAPI* logging_service = LoggingServiceAPI::GetInstance();
logging_service->SetExitCode(result_code_);
UploadLogs(base::string16(), true);
// This must be called after we uploaded logs to make sure none is added
// after the user had a chance to opt-out.
component_manager_.CloseAllComponents(result_code_);
}
void MainController::UploadLogs(const base::string16& tag,
bool quit_when_done) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LoggingServiceAPI* logging_service = LoggingServiceAPI::GetInstance();
DCHECK(logs_upload_complete_.find(tag) == logs_upload_complete_.end())
<< "tag is not unique";
logs_upload_complete_[tag] = false;
if (quit_when_done)
quit_when_logs_upload_complete_ = true;
logging_service->MaybeSaveLogsToFile(tag);
if (Settings::GetInstance()->logs_upload_allowed()) {
LoggingServiceAPI* logging_service = LoggingServiceAPI::GetInstance();
logging_service->SendLogsToSafeBrowsing(
base::BindRepeating(&MainController::LogsUploadComplete,
base::Unretained(this), tag),
registry_logger_);
} else {
LogsUploadComplete(tag, false); // false since no logs were uploaded.
}
}
void MainController::Quit() {
main_dialog()->Close();
}
void MainController::WriteEarlyExitMetrics(ResultCode exit_code) {
registry_logger_->WriteExitCode(exit_code);
registry_logger_->WriteEndTime();
}
int MainController::WatchdogTimeoutCallback(TimedOutStage timed_out_stage) {
ResultCode exit_code = RESULT_CODE_INVALID;
switch (timed_out_stage) {
case TimedOutStage::kScanning:
exit_code = removable_uws_found_
? RESULT_CODE_WATCHDOG_TIMEOUT_WITH_REMOVABLE_UWS
: RESULT_CODE_WATCHDOG_TIMEOUT_WITHOUT_REMOVABLE_UWS;
break;
case TimedOutStage::kWaitingForPrompt:
exit_code = RESULT_CODE_WATCHDOG_TIMEOUT_WAITING_FOR_PROMPT_RESPONSE;
break;
case TimedOutStage::kCleaning:
exit_code = RESULT_CODE_WATCHDOG_TIMEOUT_CLEANING;
break;
default:
NOTREACHED();
}
WriteEarlyExitMetrics(exit_code);
return exit_code;
}
void MainController::LogsUploadComplete(const base::string16& tag,
bool success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
registry_logger_->AppendLogUploadResult(success);
std::map<base::string16, bool>::iterator it = logs_upload_complete_.find(tag);
DCHECK(it != logs_upload_complete_.end());
it->second = true;
if (quit_when_logs_upload_complete_) {
for (const std::pair<base::string16, bool>& entry : logs_upload_complete_) {
if (!entry.second) {
LOG(INFO) << "Waiting for the upload of logs with tag \"" << entry.first
<< "\" to complete before exiting";
return;
}
}
if (quit_closure_)
std::move(quit_closure_).Run();
} else {
// When we complete a logs upload, and it's not the last one (identified
// by |quit_when_logs_upload_complete_|), we schedule a fallback logs upload
// in case the process dies before we get to the next log upload.
LoggingServiceAPI::GetInstance()->ScheduleFallbackLogsUpload(
registry_logger_, RESULT_CODE_FAILED_TO_COMPLETE_CLEANUP);
}
}
} // namespace chrome_cleaner