| OLD | NEW |
| (Empty) |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/chromeos/system_logs/single_log_source.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/files/file_path.h" | |
| 9 #include "base/files/file_util.h" | |
| 10 #include "base/process/process_info.h" | |
| 11 #include "base/strings/string_split.h" | |
| 12 #include "base/task_scheduler/post_task.h" | |
| 13 #include "base/time/time.h" | |
| 14 #include "content/public/browser/browser_thread.h" | |
| 15 | |
| 16 namespace system_logs { | |
| 17 | |
| 18 namespace { | |
| 19 | |
| 20 constexpr char kDefaultSystemLogDirPath[] = "/var/log"; | |
| 21 constexpr int kMaxNumAllowedLogRotationsDuringFileRead = 3; | |
| 22 | |
| 23 // For log files that contain old logging, start reading from the first | |
| 24 // timestamp that is less than this amount of time before the current session of | |
| 25 // Chrome started. | |
| 26 constexpr base::TimeDelta kLogCutoffTimeBeforeChromeStart = | |
| 27 base::TimeDelta::FromMinutes(10); | |
| 28 | |
| 29 // A custom timestamp for when the current Chrome session started. Used during | |
| 30 // testing to override the actual time. | |
| 31 const base::Time* g_chrome_start_time_for_test = nullptr; | |
| 32 | |
| 33 // Converts a logs source type to the corresponding file path, relative to the | |
| 34 // base system log directory path. In the future, if non-file source types are | |
| 35 // added, this function should return an empty file path. | |
| 36 base::FilePath GetLogFileSourceRelativeFilePath( | |
| 37 SingleLogSource::SupportedSource source) { | |
| 38 switch (source) { | |
| 39 case SingleLogSource::SupportedSource::kMessages: | |
| 40 return base::FilePath("messages"); | |
| 41 case SingleLogSource::SupportedSource::kUiLatest: | |
| 42 return base::FilePath("ui/ui.LATEST"); | |
| 43 case SingleLogSource::SupportedSource::kAtrusLog: | |
| 44 return base::FilePath("atrus.log"); | |
| 45 } | |
| 46 NOTREACHED(); | |
| 47 return base::FilePath(); | |
| 48 } | |
| 49 | |
| 50 // Returns the inode value of file at |path|, or 0 if it doesn't exist or is | |
| 51 // otherwise unable to be accessed for file system info. | |
| 52 ino_t GetInodeValue(const base::FilePath& path) { | |
| 53 struct stat file_stats; | |
| 54 if (stat(path.value().c_str(), &file_stats) != 0) | |
| 55 return 0; | |
| 56 return file_stats.st_ino; | |
| 57 } | |
| 58 | |
| 59 // Attempts to store a string |value| in |*response| under |key|. If there is | |
| 60 // already a string in |*response| under |key|, appends |value| to the existing | |
| 61 // string value. | |
| 62 void AppendToSystemLogsResponse(SystemLogsResponse* response, | |
| 63 const std::string& key, | |
| 64 const std::string& value) { | |
| 65 auto iter = response->find(key); | |
| 66 if (iter == response->end()) | |
| 67 response->emplace(key, value); | |
| 68 else | |
| 69 iter->second += value; | |
| 70 } | |
| 71 | |
| 72 // Returns the time that the current Chrome process started. Will instead return | |
| 73 // |*g_chrome_start_time_for_test| if it is set. | |
| 74 base::Time GetChromeStartTime() { | |
| 75 if (g_chrome_start_time_for_test) | |
| 76 return *g_chrome_start_time_for_test; | |
| 77 return base::CurrentProcessInfo::CreationTime(); | |
| 78 } | |
| 79 | |
| 80 // Returns the file offset into |path| of the first line that starts with a | |
| 81 // timestamp no earlier than |time|. Returns 0 if no such offset could be | |
| 82 // determined (e.g. can't open file, no timestamps present). | |
| 83 size_t GetFirstFileOffsetWithTime(const base::FilePath& path, | |
| 84 const base::Time& time) { | |
| 85 base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ); | |
| 86 if (!file.IsValid()) | |
| 87 return 0; | |
| 88 | |
| 89 const size_t file_size = file.GetLength(); | |
| 90 if (file_size == 0) | |
| 91 return 0; | |
| 92 | |
| 93 std::string file_contents; | |
| 94 file_contents.resize(file_size); | |
| 95 size_t size_read = file.ReadAtCurrentPos(&file_contents[0], file_size); | |
| 96 | |
| 97 if (size_read < file_size) | |
| 98 return 0; | |
| 99 | |
| 100 std::vector<base::StringPiece> lines = base::SplitStringPiece( | |
| 101 file_contents, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); | |
| 102 | |
| 103 bool any_timestamp_found = false; | |
| 104 | |
| 105 // Find the first line with timestamp >= |time|. If a line has no timestamp, | |
| 106 // just advance to the next line. | |
| 107 size_t offset = 0; | |
| 108 base::Time timestamp; | |
| 109 for (const auto& line : lines) { | |
| 110 if (base::Time::FromString(line.as_string().c_str(), ×tamp)) { | |
| 111 any_timestamp_found = true; | |
| 112 | |
| 113 if (timestamp >= time) | |
| 114 break; | |
| 115 } | |
| 116 | |
| 117 // Include the newline in the offset. | |
| 118 offset += line.length() + 1; | |
| 119 } | |
| 120 | |
| 121 // If the file does not have any timestamps at all, don't skip any contents. | |
| 122 if (!any_timestamp_found) | |
| 123 return 0; | |
| 124 | |
| 125 if (offset > 0 && offset >= file_size && lines.back().as_string().empty()) { | |
| 126 // The last line may or may not have ended with a newline. If it ended with | |
| 127 // a newline, |lines| would end with an extra empty line after the newline. | |
| 128 // This would have resulted in an extra nonexistent newline being counted | |
| 129 // during the computation of |offset|. | |
| 130 --offset; | |
| 131 } | |
| 132 return offset; | |
| 133 } | |
| 134 | |
| 135 } // namespace | |
| 136 | |
| 137 SingleLogSource::SingleLogSource(SupportedSource source_type) | |
| 138 : SystemLogsSource(GetLogFileSourceRelativeFilePath(source_type).value()), | |
| 139 source_type_(source_type), | |
| 140 log_file_dir_path_(kDefaultSystemLogDirPath), | |
| 141 num_bytes_read_(0), | |
| 142 file_inode_(0), | |
| 143 weak_ptr_factory_(this) {} | |
| 144 | |
| 145 SingleLogSource::~SingleLogSource() {} | |
| 146 | |
| 147 // static | |
| 148 void SingleLogSource::SetChromeStartTimeForTesting( | |
| 149 const base::Time* start_time) { | |
| 150 g_chrome_start_time_for_test = start_time; | |
| 151 } | |
| 152 | |
| 153 void SingleLogSource::Fetch(const SysLogsSourceCallback& callback) { | |
| 154 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 155 DCHECK(!callback.is_null()); | |
| 156 | |
| 157 SystemLogsResponse* response = new SystemLogsResponse; | |
| 158 base::PostTaskWithTraitsAndReply( | |
| 159 FROM_HERE, | |
| 160 base::TaskTraits(base::MayBlock(), base::TaskPriority::BACKGROUND), | |
| 161 base::Bind(&SingleLogSource::ReadFile, weak_ptr_factory_.GetWeakPtr(), | |
| 162 kMaxNumAllowedLogRotationsDuringFileRead, response), | |
| 163 base::Bind(callback, base::Owned(response))); | |
| 164 } | |
| 165 | |
| 166 base::FilePath SingleLogSource::GetLogFilePath() const { | |
| 167 return base::FilePath(log_file_dir_path_).Append(source_name()); | |
| 168 } | |
| 169 | |
| 170 void SingleLogSource::ReadFile(size_t num_rotations_allowed, | |
| 171 SystemLogsResponse* result) { | |
| 172 // Attempt to open the file if it was not previously opened. | |
| 173 if (!file_.IsValid()) { | |
| 174 file_.Initialize(GetLogFilePath(), | |
| 175 base::File::FLAG_OPEN | base::File::FLAG_READ); | |
| 176 if (!file_.IsValid()) | |
| 177 return; | |
| 178 | |
| 179 // Determine actual offset from which to start reading. | |
| 180 if (source_type_ == SupportedSource::kMessages) { | |
| 181 const base::Time earliest_log_time = | |
| 182 GetChromeStartTime() - kLogCutoffTimeBeforeChromeStart; | |
| 183 | |
| 184 num_bytes_read_ = | |
| 185 GetFirstFileOffsetWithTime(GetLogFilePath(), earliest_log_time); | |
| 186 } else { | |
| 187 num_bytes_read_ = 0; | |
| 188 } | |
| 189 file_.Seek(base::File::FROM_BEGIN, num_bytes_read_); | |
| 190 | |
| 191 file_inode_ = GetInodeValue(GetLogFilePath()); | |
| 192 } | |
| 193 | |
| 194 // Check for file size reset. | |
| 195 const size_t length = file_.GetLength(); | |
| 196 if (length < num_bytes_read_) { | |
| 197 num_bytes_read_ = 0; | |
| 198 file_.Seek(base::File::FROM_BEGIN, 0); | |
| 199 } | |
| 200 | |
| 201 // Read from file until end. | |
| 202 const size_t size_to_read = length - num_bytes_read_; | |
| 203 std::string result_string; | |
| 204 result_string.resize(size_to_read); | |
| 205 size_t size_read = file_.ReadAtCurrentPos(&result_string[0], size_to_read); | |
| 206 result_string.resize(size_read); | |
| 207 | |
| 208 const bool file_was_rotated = file_inode_ != GetInodeValue(GetLogFilePath()); | |
| 209 const bool should_handle_file_rotation = | |
| 210 file_was_rotated && num_rotations_allowed > 0; | |
| 211 | |
| 212 // The reader may only read complete lines. The exception is when there is a | |
| 213 // rotation, in which case all the remaining contents of the old log file | |
| 214 // should be read before moving on to read the new log file. | |
| 215 if ((result_string.empty() || result_string.back() != '\n') && | |
| 216 !should_handle_file_rotation) { | |
| 217 // If an incomplete line was read, return only the part that includes whole | |
| 218 // lines. | |
| 219 size_t last_newline_pos = result_string.find_last_of('\n'); | |
| 220 if (last_newline_pos == std::string::npos) { | |
| 221 file_.Seek(base::File::FROM_CURRENT, -size_read); | |
| 222 AppendToSystemLogsResponse(result, source_name(), ""); | |
| 223 return; | |
| 224 } | |
| 225 // The part of the string that will be returned includes the newline itself. | |
| 226 size_t adjusted_size_read = last_newline_pos + 1; | |
| 227 file_.Seek(base::File::FROM_CURRENT, -size_read + adjusted_size_read); | |
| 228 result_string.resize(adjusted_size_read); | |
| 229 | |
| 230 // Update |size_read| to reflect that the read was only up to the last | |
| 231 // newline. | |
| 232 size_read = adjusted_size_read; | |
| 233 } | |
| 234 | |
| 235 num_bytes_read_ += size_read; | |
| 236 | |
| 237 // Pass it back to the callback. | |
| 238 AppendToSystemLogsResponse(result, source_name(), | |
| 239 anonymizer_.Anonymize(result_string)); | |
| 240 | |
| 241 // If the file was rotated, close the file handle and call this function | |
| 242 // again, to read from the new file. | |
| 243 if (should_handle_file_rotation) { | |
| 244 file_.Close(); | |
| 245 num_bytes_read_ = 0; | |
| 246 file_inode_ = 0; | |
| 247 ReadFile(num_rotations_allowed - 1, result); | |
| 248 } | |
| 249 } | |
| 250 | |
| 251 } // namespace system_logs | |
| OLD | NEW |