blob: 7e5731a49c4354b88b38bfd6a8497000336a1363 [file] [log] [blame]
// 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/message_loop/message_loop.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.
base::string16 PendingLogsService::LogsUploadRetryTaskName(
const base::string16& product_shortname) {
return product_shortname + L" logs upload retry";
}
// static.
void PendingLogsService::ScheduleLogsUploadTask(
const base::string16& product_shortname,
const ChromeCleanerReport& chrome_cleaner_report,
base::FilePath* log_file,
RegistryLogger* registry_logger) {
DCHECK(base::MessageLoopForUI::IsCurrent());
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::BindRepeating(
IgnoreResult(&base::DeleteFile), temp_file_path, false));
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 base::string16& 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, false))
LOG(ERROR) << "Failed to delete '" << SanitizePath(log_file) << "'.";
}
PendingLogsService::PendingLogsService() {
DCHECK(base::MessageLoopForUI::IsCurrent());
}
PendingLogsService::~PendingLogsService() = default;
void PendingLogsService::RetryNextPendingLogsUpload(
const base::string16& 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 base::string16& 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