File: single_log_file_log_source.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (237 lines) | stat: -rw-r--r-- 8,746 bytes parent folder | download | duplicates (6)
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