1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
|
// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/ash/system_logs/single_log_file_log_source.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/process/process_info.h"
#include "base/strings/string_split.h"
#include "base/task/thread_pool.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 char kLogTruncated[] = "<earlier logs truncated>\n<partial line>";
constexpr int kMaxNumAllowedLogRotationsDuringFileRead = 3;
// We set a per-read limit of 5 MiB to avoid running out of memory. Clients are
// responsible for further bundling and truncating.
// * This cap is applied to the read buffer before dropping trailing incomplete
// lines.
constexpr size_t kMaxReadSize = 5 * 1024 * 1024;
// 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();
}
// 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),
max_read_size_(kMaxReadSize),
file_cursor_position_(0),
file_inode_(0) {}
SingleLogFileLogSource::~SingleLogFileLogSource() = default;
// 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::ThreadPool::PostTaskAndReply(
FROM_HERE, {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)));
}
void SingleLogFileLogSource::SetMaxReadSizeForTesting(
const size_t max_read_size) {
max_read_size_ = max_read_size;
}
base::FilePath SingleLogFileLogSource::GetLogFilePath() const {
return log_file_dir_path_.Append(source_name());
}
void SingleLogFileLogSource::ReadFile(size_t num_rotations_allowed,
SystemLogsResponse* response) {
auto result_string = std::make_unique<std::string>();
// No bytes have been skipped yet, because the read hasn't started.
constexpr bool bytes_skipped = false;
ContinueReadFile(std::move(result_string), bytes_skipped,
num_rotations_allowed, response);
}
void SingleLogFileLogSource::ContinueReadFile(
std::unique_ptr<std::string> result_string,
bool bytes_skipped,
size_t num_rotations_allowed,
SystemLogsResponse* response) {
// 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;
file_cursor_position_ = 0;
file_inode_ = GetInodeValue(GetLogFilePath());
}
// Check for file size reset.
const size_t length = file_.GetLength();
if (length < file_cursor_position_) {
file_cursor_position_ = 0;
file_.Seek(base::File::FROM_BEGIN, 0);
}
// Check for large read and skip forward to avoid out-of-memory conditions.
if (length - file_cursor_position_ > max_read_size_) {
bytes_skipped = true;
file_.Seek(base::File::FROM_END, -max_read_size_);
// Update |file_cursor_position_| to support the file size reset check.
file_cursor_position_ = length - max_read_size_;
}
// The calculated amount of data to read, after adjusting for
// |max_read_size_|.
const size_t size_to_read = length - file_cursor_position_;
// Trim down the previously read data before starting a new read.
const size_t available_previous_read_size = max_read_size_ - size_to_read;
if (available_previous_read_size < result_string->size()) {
result_string->erase(0,
result_string->size() - available_previous_read_size);
}
// Read from file until end.
std::string new_result_string;
new_result_string.resize(size_to_read);
size_t size_read =
file_.ReadAtCurrentPos(&new_result_string[0], size_to_read);
new_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 ((new_result_string.empty() || new_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 = new_result_string.find_last_of('\n');
// The part of the string that will be returned includes the newline
// itself.
size_t adjusted_size_read =
last_newline_pos == std::string::npos ? 0 : last_newline_pos + 1;
file_.Seek(base::File::FROM_CURRENT, -size_read + adjusted_size_read);
new_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;
}
file_cursor_position_ += size_read;
result_string->append(new_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();
file_cursor_position_ = 0;
file_inode_ = 0;
ContinueReadFile(std::move(result_string), bytes_skipped,
num_rotations_allowed - 1, response);
} else {
// Only write the log truncated sentinel value once we have something to
// go after it, and any accumulation and rollover has been handled.
if (bytes_skipped && result_string->size() > 0) {
AppendToSystemLogsResponse(response, source_name(), kLogTruncated);
}
// Pass it back to the callback.
AppendToSystemLogsResponse(response, source_name(), *result_string.get());
}
}
} // namespace system_logs
|