| // Copyright 2018 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/logging/pending_logs_service.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/files/file_util.h" |
| #include "base/logging.h" |
| #include "base/task/current_thread.h" |
| #include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h" |
| #include "chrome/chrome_cleaner/logging/logging_service_api.h" |
| #include "chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.pb.h" |
| #include "chrome/chrome_cleaner/logging/registry_logger.h" |
| #include "chrome/chrome_cleaner/os/disk_util.h" |
| #include "chrome/chrome_cleaner/os/file_path_sanitization.h" |
| #include "chrome/chrome_cleaner/os/pre_fetched_paths.h" |
| #include "chrome/chrome_cleaner/os/task_scheduler.h" |
| #include "chrome/chrome_cleaner/settings/settings.h" |
| #include "components/chrome_cleaner/public/constants/result_codes.h" |
| |
| namespace chrome_cleaner { |
| |
| bool PendingLogsService::retrying_ = false; |
| |
| // static. |
| std::wstring PendingLogsService::LogsUploadRetryTaskName( |
| const std::wstring& product_shortname) { |
| return product_shortname + L" logs upload retry"; |
| } |
| |
| // static. |
| void PendingLogsService::ScheduleLogsUploadTask( |
| const std::wstring& product_shortname, |
| const ChromeCleanerReport& chrome_cleaner_report, |
| base::FilePath* log_file, |
| RegistryLogger* registry_logger) { |
| DCHECK(base::CurrentUIThread::IsSet()); |
| DCHECK(log_file); |
| DCHECK(registry_logger); |
| // This can happen when we fail while retrying. The logging service is not |
| // aware of this state and will call us again. Simply ignore the call. |
| if (retrying_) |
| return; |
| |
| std::string chrome_cleaner_report_string; |
| if (!chrome_cleaner_report.SerializeToString(&chrome_cleaner_report_string)) { |
| PLOG(ERROR) << "SerializeToString failed!"; |
| return; |
| } |
| |
| base::FilePath product_app_data_path; |
| base::FilePath temp_file_path; |
| if (!GetAppDataProductDirectory(&product_app_data_path) || |
| !CreateTemporaryFileInDir(product_app_data_path, &temp_file_path)) { |
| PLOG(ERROR) << "Create a temporary log file failed!"; |
| return; |
| } |
| |
| // To get rid of the temporary file if we are not going to use it. |
| base::ScopedClosureRunner delete_file_closure( |
| base::BindOnce(base::GetDeleteFileCallback(), temp_file_path)); |
| |
| if (base::WriteFile(temp_file_path, chrome_cleaner_report_string.c_str(), |
| chrome_cleaner_report_string.size()) <= 0) { |
| PLOG(ERROR) << "Failed to write logging report to " |
| << SanitizePath(temp_file_path); |
| return; |
| } |
| |
| // AppendLogFilePath already logs sufficiently on failures. |
| if (!registry_logger->AppendLogFilePath(temp_file_path)) |
| return; |
| |
| base::CommandLine scheduled_command_line( |
| PreFetchedPaths::GetInstance()->GetExecutablePath()); |
| scheduled_command_line.AppendSwitch(kUploadLogFileSwitch); |
| // Propage the cleanup id for the current process, so we can identify the |
| // corresponding pending logs. |
| scheduled_command_line.AppendSwitchASCII( |
| kCleanupIdSwitch, Settings::GetInstance()->cleanup_id()); |
| scheduled_command_line.AppendArguments( |
| *base::CommandLine::ForCurrentProcess(), false); |
| std::unique_ptr<TaskScheduler> task_scheduler( |
| TaskScheduler::CreateInstance()); |
| if (!task_scheduler->RegisterTask( |
| LogsUploadRetryTaskName(product_shortname).c_str(), |
| /*task_description=*/L"", scheduled_command_line, |
| TaskScheduler::TRIGGER_TYPE_EVERY_SIX_HOURS, false)) { |
| LOG(ERROR) << "Failed to register logs upload retry task."; |
| registry_logger->RemoveLogFilePath(temp_file_path); |
| return; |
| } |
| // TODO(csharp): when we add support to upload pending logs in regular |
| // unscheduled runs, this should move up before the scheduling. |
| // Prevent the file deletion now that we confirmed it will be used. |
| delete_file_closure.Release().Reset(); |
| *log_file = temp_file_path; |
| } |
| |
| // static. |
| void PendingLogsService::ClearPendingLogFile( |
| const std::wstring& product_shortname, |
| const base::FilePath& log_file, |
| RegistryLogger* registry_logger) { |
| DCHECK(registry_logger); |
| // RemoveLogFilePath returns false when there are no log files left in the |
| // registry, in which case we must delete the scheduled task too. |
| if (!registry_logger->RemoveLogFilePath(log_file)) { |
| std::unique_ptr<TaskScheduler> task_scheduler( |
| TaskScheduler::CreateInstance()); |
| if (!task_scheduler->DeleteTask( |
| LogsUploadRetryTaskName(product_shortname).c_str())) |
| LOG(ERROR) << "Failed to delete logs upload retry task."; |
| } |
| |
| if (!base::DeleteFile(log_file)) |
| LOG(ERROR) << "Failed to delete '" << SanitizePath(log_file) << "'."; |
| } |
| |
| PendingLogsService::PendingLogsService() { |
| DCHECK(base::CurrentUIThread::IsSet()); |
| } |
| |
| PendingLogsService::~PendingLogsService() = default; |
| |
| void PendingLogsService::RetryNextPendingLogsUpload( |
| const std::wstring& product_shortname, |
| base::OnceCallback<void(bool)> done_callback, |
| RegistryLogger* registry_logger) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(registry_logger); |
| DCHECK(!retrying_); |
| retrying_ = true; |
| |
| LoggingServiceAPI* logging_service = LoggingServiceAPI::GetInstance(); |
| |
| // TODO(csharp): Loop through all the pending log files in one shot instead of |
| // having to wait for another scheduled task interval for the next one. |
| registry_logger->GetNextLogFilePath(&log_file_); |
| if (log_file_.empty()) { |
| LOG(ERROR) << "Got no log file path from registry"; |
| logging_service->SetExitCode(RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE); |
| } else if (!logging_service->ReadContentFromFile(log_file_)) { |
| LOG(ERROR) << "Error reading log content from file"; |
| ClearPendingLogFile(product_shortname, log_file_, registry_logger); |
| log_file_.clear(); |
| |
| // Send a plain log with just the proper result code in case it can work. |
| logging_service->SetExitCode(RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE); |
| } else { |
| // Add one more log before we upload, to specify that we are sending this |
| // log from a retry task. TODO(csharp): Maybe add this as a protobuf field. |
| LOG(INFO) << "Uploading persisted pending logs."; |
| } |
| |
| done_callback_ = std::move(done_callback); |
| logging_service->SendLogsToSafeBrowsing( |
| base::BindRepeating(&PendingLogsService::UploadResultCallback, |
| base::Unretained(this), product_shortname, |
| registry_logger), |
| registry_logger); |
| } |
| |
| void PendingLogsService::UploadResultCallback( |
| const std::wstring& product_shortname, |
| RegistryLogger* registry_logger, |
| bool success) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(retrying_); |
| if (success && !log_file_.empty()) |
| ClearPendingLogFile(product_shortname, log_file_, registry_logger); |
| |
| log_file_.clear(); |
| retrying_ = false; |
| std::move(done_callback_).Run(success); |
| } |
| |
| } // namespace chrome_cleaner |