blob: 4f5901c0ea75643ce35c1d86994d346f8004213c [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/chromeos/system_logs/single_log_file_log_source.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/process/process_info.h"
#include "base/strings/string_split.h"
#include "base/task_scheduler/post_task.h"
#include "base/time/time.h"
#include "content/public/browser/browser_thread.h"
namespace system_logs {
namespace {
using SupportedSource = SingleLogFileLogSource::SupportedSource;
constexpr char kDefaultSystemLogDirPath[] = "/var/log";
constexpr int kMaxNumAllowedLogRotationsDuringFileRead = 3;
// A custom timestamp for when the current Chrome session started. Used during
// testing to override the actual time.
const base::Time* g_chrome_start_time_for_test = nullptr;
// Converts a logs source type to the corresponding file path, relative to the
// base system log directory path. In the future, if non-file source types are
// added, this function should return an empty file path.
base::FilePath::StringType GetLogFileSourceRelativeFilePathValue(
SingleLogFileLogSource::SupportedSource source) {
switch (source) {
case SupportedSource::kMessages:
return "messages";
case SupportedSource::kUiLatest:
return "ui/ui.LATEST";
case SupportedSource::kAtrusLog:
return "atrus.log";
case SupportedSource::kNetLog:
return "net.log";
case SupportedSource::kEventLog:
return "eventlog.txt";
case SupportedSource::kUpdateEngineLog:
return "update_engine.log";
case SupportedSource::kPowerdLatest:
return "power_manager/powerd.LATEST";
case SupportedSource::kPowerdPrevious:
return "power_manager/powerd.PREVIOUS";
}
NOTREACHED();
return base::FilePath::StringType();
}
// Returns the inode value of file at |path|, or 0 if it doesn't exist or is
// otherwise unable to be accessed for file system info.
ino_t GetInodeValue(const base::FilePath& path) {
struct stat file_stats;
if (stat(path.value().c_str(), &file_stats) != 0)
return 0;
return file_stats.st_ino;
}
// Attempts to store a string |value| in |*response| under |key|. If there is
// already a string in |*response| under |key|, appends |value| to the existing
// string value.
void AppendToSystemLogsResponse(SystemLogsResponse* response,
const std::string& key,
const std::string& value) {
auto iter = response->find(key);
if (iter == response->end())
response->emplace(key, value);
else
iter->second += value;
}
} // namespace
SingleLogFileLogSource::SingleLogFileLogSource(SupportedSource source_type)
: SystemLogsSource(GetLogFileSourceRelativeFilePathValue(source_type)),
source_type_(source_type),
log_file_dir_path_(kDefaultSystemLogDirPath),
num_bytes_read_(0),
file_inode_(0),
weak_ptr_factory_(this) {}
SingleLogFileLogSource::~SingleLogFileLogSource() {}
// static
void SingleLogFileLogSource::SetChromeStartTimeForTesting(
const base::Time* start_time) {
g_chrome_start_time_for_test = start_time;
}
void SingleLogFileLogSource::Fetch(SysLogsSourceCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!callback.is_null());
auto response = std::make_unique<SystemLogsResponse>();
auto* response_ptr = response.get();
base::PostTaskWithTraitsAndReply(
FROM_HERE,
base::TaskTraits(base::MayBlock(), base::TaskPriority::BEST_EFFORT),
base::BindOnce(&SingleLogFileLogSource::ReadFile,
weak_ptr_factory_.GetWeakPtr(),
kMaxNumAllowedLogRotationsDuringFileRead, response_ptr),
base::BindOnce(std::move(callback), std::move(response)));
}
base::FilePath SingleLogFileLogSource::GetLogFilePath() const {
return log_file_dir_path_.Append(source_name());
}
void SingleLogFileLogSource::ReadFile(size_t num_rotations_allowed,
SystemLogsResponse* result) {
// Attempt to open the file if it was not previously opened.
if (!file_.IsValid()) {
file_.Initialize(GetLogFilePath(),
base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file_.IsValid())
return;
num_bytes_read_ = 0;
file_inode_ = GetInodeValue(GetLogFilePath());
}
// Check for file size reset.
const size_t length = file_.GetLength();
if (length < num_bytes_read_) {
num_bytes_read_ = 0;
file_.Seek(base::File::FROM_BEGIN, 0);
}
// Read from file until end.
const size_t size_to_read = length - num_bytes_read_;
std::string result_string;
result_string.resize(size_to_read);
size_t size_read = file_.ReadAtCurrentPos(&result_string[0], size_to_read);
result_string.resize(size_read);
const bool file_was_rotated = file_inode_ != GetInodeValue(GetLogFilePath());
const bool should_handle_file_rotation =
file_was_rotated && num_rotations_allowed > 0;
// The reader may only read complete lines. The exception is when there is a
// rotation, in which case all the remaining contents of the old log file
// should be read before moving on to read the new log file.
if ((result_string.empty() || result_string.back() != '\n') &&
!should_handle_file_rotation) {
// If an incomplete line was read, return only the part that includes whole
// lines.
size_t last_newline_pos = result_string.find_last_of('\n');
if (last_newline_pos == std::string::npos) {
file_.Seek(base::File::FROM_CURRENT, -size_read);
AppendToSystemLogsResponse(result, source_name(), "");
return;
}
// The part of the string that will be returned includes the newline itself.
size_t adjusted_size_read = last_newline_pos + 1;
file_.Seek(base::File::FROM_CURRENT, -size_read + adjusted_size_read);
result_string.resize(adjusted_size_read);
// Update |size_read| to reflect that the read was only up to the last
// newline.
size_read = adjusted_size_read;
}
num_bytes_read_ += size_read;
// Pass it back to the callback.
AppendToSystemLogsResponse(result, source_name(), result_string);
// If the file was rotated, close the file handle and call this function
// again, to read from the new file.
if (should_handle_file_rotation) {
file_.Close();
num_bytes_read_ = 0;
file_inode_ = 0;
ReadFile(num_rotations_allowed - 1, result);
}
}
} // namespace system_logs