blob: e9ac394244cf011d101940c5ac04084a6042f4ac [file] [log] [blame]
tkchin93411912015-07-22 12:12:17 -07001/*
2 * Copyright 2015 The WebRTC Project Authors. All rights reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020011#include "rtc_base/filerotatingstream.h"
tkchin93411912015-07-22 12:12:17 -070012
13#include <algorithm>
Jonas Olsson55378f42018-05-25 10:23:10 +020014#include <cstdio>
tkchin93411912015-07-22 12:12:17 -070015#include <string>
Yves Gerey988cc082018-10-23 12:03:01 +020016#include <utility>
tkchin93411912015-07-22 12:12:17 -070017
Niels Möller7b3c76b2018-11-07 09:54:28 +010018#if defined(WEBRTC_WIN)
19#include <windows.h>
20#include "rtc_base/stringutils.h"
21#else
22#include <sys/stat.h>
23#endif // WEBRTC_WIN
24
25#include "absl/strings/match.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020026#include "rtc_base/checks.h"
27#include "rtc_base/fileutils.h"
Yves Gerey2e00abc2018-10-05 15:39:24 +020028#include "rtc_base/logging.h"
tkchin93411912015-07-22 12:12:17 -070029
Jonas Olsson55378f42018-05-25 10:23:10 +020030// Note: We use fprintf for logging in the write paths of this stream to avoid
tkchin93411912015-07-22 12:12:17 -070031// infinite loops when logging.
32
33namespace rtc {
34
Niels Möller7b3c76b2018-11-07 09:54:28 +010035namespace {
36
37std::string AddTrailingPathDelimiterIfNeeded(std::string directory);
38bool IsFolder(const std::string& file);
39
40#if defined(WEBRTC_WIN)
41
42std::string AddTrailingPathDelimiterIfNeeded(std::string directory) {
43 if (absl::EndsWith(directory, "\\")) {
44 return directory;
45 }
46 return directory + "\\";
47}
48
49bool IsFolder(const std::string& file) {
50 WIN32_FILE_ATTRIBUTE_DATA data = {0};
51 if (0 == ::GetFileAttributesEx(ToUtf16(file).c_str(), GetFileExInfoStandard,
52 &data))
53 return false;
54 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
55 FILE_ATTRIBUTE_DIRECTORY;
56}
57
58#else // defined(WEBRTC_WIN)
59
60std::string AddTrailingPathDelimiterIfNeeded(std::string directory) {
61 if (absl::EndsWith(directory, "/")) {
62 return directory;
63 }
64 return directory + "/";
65}
66
67bool IsFolder(const std::string& file) {
68 struct stat st;
69 int res = ::stat(file.c_str(), &st);
70 return res == 0 && S_ISDIR(st.st_mode);
71}
72
73#endif
74
75} // namespace
76
tkchin93411912015-07-22 12:12:17 -070077FileRotatingStream::FileRotatingStream(const std::string& dir_path,
78 const std::string& file_prefix)
Yves Gerey665174f2018-06-19 15:03:05 +020079 : FileRotatingStream(dir_path, file_prefix, 0, 0, kRead) {}
tkchin93411912015-07-22 12:12:17 -070080
81FileRotatingStream::FileRotatingStream(const std::string& dir_path,
82 const std::string& file_prefix,
83 size_t max_file_size,
84 size_t num_files)
85 : FileRotatingStream(dir_path,
86 file_prefix,
87 max_file_size,
88 num_files,
89 kWrite) {
kwibergaf476c72016-11-28 15:21:39 -080090 RTC_DCHECK_GT(max_file_size, 0);
91 RTC_DCHECK_GT(num_files, 1);
tkchin93411912015-07-22 12:12:17 -070092}
93
94FileRotatingStream::FileRotatingStream(const std::string& dir_path,
95 const std::string& file_prefix,
96 size_t max_file_size,
97 size_t num_files,
98 Mode mode)
Niels Möller7b3c76b2018-11-07 09:54:28 +010099 : dir_path_(AddTrailingPathDelimiterIfNeeded(dir_path)),
tkchin93411912015-07-22 12:12:17 -0700100 file_prefix_(file_prefix),
101 mode_(mode),
102 file_stream_(nullptr),
103 max_file_size_(max_file_size),
104 current_file_index_(0),
105 rotation_index_(0),
106 current_bytes_written_(0),
107 disable_buffering_(false) {
Niels Möller7b3c76b2018-11-07 09:54:28 +0100108 RTC_DCHECK(IsFolder(dir_path));
tkchin93411912015-07-22 12:12:17 -0700109 switch (mode) {
110 case kWrite: {
111 file_names_.clear();
112 for (size_t i = 0; i < num_files; ++i) {
113 file_names_.push_back(GetFilePath(i, num_files));
114 }
115 rotation_index_ = num_files - 1;
116 break;
117 }
118 case kRead: {
119 file_names_ = GetFilesWithPrefix();
120 std::sort(file_names_.begin(), file_names_.end());
121 if (file_names_.size() > 0) {
122 // |file_names_| is sorted newest first, so read from the end.
123 current_file_index_ = file_names_.size() - 1;
124 }
125 break;
126 }
127 }
128}
129
Yves Gerey665174f2018-06-19 15:03:05 +0200130FileRotatingStream::~FileRotatingStream() {}
tkchin93411912015-07-22 12:12:17 -0700131
132StreamState FileRotatingStream::GetState() const {
133 if (mode_ == kRead && current_file_index_ < file_names_.size()) {
134 return SS_OPEN;
135 }
136 if (!file_stream_) {
137 return SS_CLOSED;
138 }
139 return file_stream_->GetState();
140}
141
142StreamResult FileRotatingStream::Read(void* buffer,
143 size_t buffer_len,
144 size_t* read,
145 int* error) {
henrikg91d6ede2015-09-17 00:24:34 -0700146 RTC_DCHECK(buffer);
tkchin93411912015-07-22 12:12:17 -0700147 if (mode_ != kRead) {
148 return SR_EOS;
149 }
150 if (current_file_index_ >= file_names_.size()) {
151 return SR_EOS;
152 }
153 // We will have no file stream initially, and when we are finished with the
154 // previous file.
155 if (!file_stream_) {
156 if (!OpenCurrentFile()) {
157 return SR_ERROR;
158 }
159 }
160 int local_error = 0;
161 if (!error) {
162 error = &local_error;
163 }
164 StreamResult result = file_stream_->Read(buffer, buffer_len, read, error);
165 if (result == SR_EOS || result == SR_ERROR) {
166 if (result == SR_ERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100167 RTC_LOG(LS_ERROR) << "Failed to read from: "
168 << file_names_[current_file_index_]
169 << "Error: " << error;
tkchin93411912015-07-22 12:12:17 -0700170 }
171 // Reached the end of the file, read next file. If there is an error return
172 // the error status but allow for a next read by reading next file.
173 CloseCurrentFile();
174 if (current_file_index_ == 0) {
175 // Just finished reading the last file, signal EOS by setting index.
176 current_file_index_ = file_names_.size();
177 } else {
178 --current_file_index_;
179 }
180 if (read) {
181 *read = 0;
182 }
183 return result == SR_EOS ? SR_SUCCESS : result;
184 } else if (result == SR_SUCCESS) {
185 // Succeeded, continue reading from this file.
186 return SR_SUCCESS;
187 } else {
188 RTC_NOTREACHED();
189 }
190 return result;
191}
192
193StreamResult FileRotatingStream::Write(const void* data,
194 size_t data_len,
195 size_t* written,
196 int* error) {
197 if (mode_ != kWrite) {
198 return SR_EOS;
199 }
200 if (!file_stream_) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200201 std::fprintf(stderr, "Open() must be called before Write.\n");
tkchin93411912015-07-22 12:12:17 -0700202 return SR_ERROR;
203 }
204 // Write as much as will fit in to the current file.
henrikg91d6ede2015-09-17 00:24:34 -0700205 RTC_DCHECK_LT(current_bytes_written_, max_file_size_);
tkchin93411912015-07-22 12:12:17 -0700206 size_t remaining_bytes = max_file_size_ - current_bytes_written_;
207 size_t write_length = std::min(data_len, remaining_bytes);
208 size_t local_written = 0;
209 if (!written) {
210 written = &local_written;
211 }
212 StreamResult result = file_stream_->Write(data, write_length, written, error);
213 current_bytes_written_ += *written;
214
215 // If we're done with this file, rotate it out.
216 if (current_bytes_written_ >= max_file_size_) {
henrikg91d6ede2015-09-17 00:24:34 -0700217 RTC_DCHECK_EQ(current_bytes_written_, max_file_size_);
tkchin93411912015-07-22 12:12:17 -0700218 RotateFiles();
219 }
220 return result;
221}
222
223bool FileRotatingStream::Flush() {
224 if (!file_stream_) {
225 return false;
226 }
227 return file_stream_->Flush();
228}
229
tkchin28bae022015-07-23 12:27:02 -0700230bool FileRotatingStream::GetSize(size_t* size) const {
231 if (mode_ != kRead) {
232 // Not possible to get accurate size on disk when writing because of
233 // potential buffering.
234 return false;
235 }
henrikg91d6ede2015-09-17 00:24:34 -0700236 RTC_DCHECK(size);
tkchin28bae022015-07-23 12:27:02 -0700237 *size = 0;
238 size_t total_size = 0;
239 for (auto file_name : file_names_) {
tkchin28bae022015-07-23 12:27:02 -0700240 size_t file_size = 0;
241 if (Filesystem::GetFileSize(file_name, &file_size)) {
242 total_size += file_size;
243 }
244 }
245 *size = total_size;
246 return true;
247}
248
tkchin93411912015-07-22 12:12:17 -0700249void FileRotatingStream::Close() {
250 CloseCurrentFile();
251}
252
253bool FileRotatingStream::Open() {
254 switch (mode_) {
255 case kRead:
256 // Defer opening to when we first read since we want to return read error
257 // if we fail to open next file.
258 return true;
259 case kWrite: {
260 // Delete existing files when opening for write.
261 std::vector<std::string> matching_files = GetFilesWithPrefix();
262 for (auto matching_file : matching_files) {
263 if (!Filesystem::DeleteFile(matching_file)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200264 std::fprintf(stderr, "Failed to delete: %s\n", matching_file.c_str());
tkchin93411912015-07-22 12:12:17 -0700265 }
266 }
267 return OpenCurrentFile();
268 }
269 }
270 return false;
271}
272
273bool FileRotatingStream::DisableBuffering() {
274 disable_buffering_ = true;
275 if (!file_stream_) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200276 std::fprintf(stderr, "Open() must be called before DisableBuffering().\n");
tkchin93411912015-07-22 12:12:17 -0700277 return false;
278 }
279 return file_stream_->DisableBuffering();
280}
281
282std::string FileRotatingStream::GetFilePath(size_t index) const {
henrikg91d6ede2015-09-17 00:24:34 -0700283 RTC_DCHECK_LT(index, file_names_.size());
tkchin93411912015-07-22 12:12:17 -0700284 return file_names_[index];
285}
286
287bool FileRotatingStream::OpenCurrentFile() {
288 CloseCurrentFile();
289
290 // Opens the appropriate file in the appropriate mode.
henrikg91d6ede2015-09-17 00:24:34 -0700291 RTC_DCHECK_LT(current_file_index_, file_names_.size());
tkchin93411912015-07-22 12:12:17 -0700292 std::string file_path = file_names_[current_file_index_];
293 file_stream_.reset(new FileStream());
294 const char* mode = nullptr;
295 switch (mode_) {
296 case kWrite:
297 mode = "w+";
298 // We should always we writing to the zero-th file.
kwibergaf476c72016-11-28 15:21:39 -0800299 RTC_DCHECK_EQ(current_file_index_, 0);
tkchin93411912015-07-22 12:12:17 -0700300 break;
301 case kRead:
302 mode = "r";
303 break;
304 }
305 int error = 0;
306 if (!file_stream_->Open(file_path, mode, &error)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200307 std::fprintf(stderr, "Failed to open: %s Error: %i\n", file_path.c_str(),
308 error);
tkchin93411912015-07-22 12:12:17 -0700309 file_stream_.reset();
310 return false;
311 }
312 if (disable_buffering_) {
313 file_stream_->DisableBuffering();
314 }
315 return true;
316}
317
318void FileRotatingStream::CloseCurrentFile() {
319 if (!file_stream_) {
320 return;
321 }
322 current_bytes_written_ = 0;
323 file_stream_.reset();
324}
325
326void FileRotatingStream::RotateFiles() {
henrikg91d6ede2015-09-17 00:24:34 -0700327 RTC_DCHECK_EQ(mode_, kWrite);
tkchin93411912015-07-22 12:12:17 -0700328 CloseCurrentFile();
329 // Rotates the files by deleting the file at |rotation_index_|, which is the
330 // oldest file and then renaming the newer files to have an incremented index.
331 // See header file comments for example.
hayscd02b0fa2015-12-08 13:59:05 -0800332 RTC_DCHECK_LT(rotation_index_, file_names_.size());
tkchin93411912015-07-22 12:12:17 -0700333 std::string file_to_delete = file_names_[rotation_index_];
334 if (Filesystem::IsFile(file_to_delete)) {
335 if (!Filesystem::DeleteFile(file_to_delete)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200336 std::fprintf(stderr, "Failed to delete: %s\n", file_to_delete.c_str());
tkchin93411912015-07-22 12:12:17 -0700337 }
338 }
339 for (auto i = rotation_index_; i > 0; --i) {
340 std::string rotated_name = file_names_[i];
341 std::string unrotated_name = file_names_[i - 1];
342 if (Filesystem::IsFile(unrotated_name)) {
343 if (!Filesystem::MoveFile(unrotated_name, rotated_name)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200344 std::fprintf(stderr, "Failed to move: %s to %s\n",
345 unrotated_name.c_str(), rotated_name.c_str());
tkchin93411912015-07-22 12:12:17 -0700346 }
347 }
348 }
349 // Create a new file for 0th index.
350 OpenCurrentFile();
351 OnRotation();
352}
353
354std::vector<std::string> FileRotatingStream::GetFilesWithPrefix() const {
355 std::vector<std::string> files;
356 // Iterate over the files in the directory.
357 DirectoryIterator it;
Niels Möller7b3c76b2018-11-07 09:54:28 +0100358 if (!it.Iterate(dir_path_)) {
tkchin93411912015-07-22 12:12:17 -0700359 return files;
360 }
361 do {
362 std::string current_name = it.Name();
363 if (current_name.size() && !it.IsDirectory() &&
364 current_name.compare(0, file_prefix_.size(), file_prefix_) == 0) {
Niels Möller7b3c76b2018-11-07 09:54:28 +0100365 files.push_back(it.PathName());
tkchin93411912015-07-22 12:12:17 -0700366 }
367 } while (it.Next());
368 return files;
369}
370
371std::string FileRotatingStream::GetFilePath(size_t index,
372 size_t num_files) const {
henrikg91d6ede2015-09-17 00:24:34 -0700373 RTC_DCHECK_LT(index, num_files);
tkchin93411912015-07-22 12:12:17 -0700374
Jonas Olsson671cae22018-06-14 09:57:39 +0200375 const size_t buffer_size = 32;
376 char file_postfix[buffer_size];
377 // We want to zero pad the index so that it will sort nicely.
378 const int max_digits = std::snprintf(nullptr, 0, "%zu", num_files - 1);
379 RTC_DCHECK_LT(1 + max_digits, buffer_size);
380 std::snprintf(file_postfix, buffer_size, "_%0*zu", max_digits, index);
tkchin93411912015-07-22 12:12:17 -0700381
Niels Möller7b3c76b2018-11-07 09:54:28 +0100382 return dir_path_ + file_prefix_ + file_postfix;
tkchin93411912015-07-22 12:12:17 -0700383}
384
385CallSessionFileRotatingStream::CallSessionFileRotatingStream(
386 const std::string& dir_path)
387 : FileRotatingStream(dir_path, kLogPrefix),
388 max_total_log_size_(0),
Yves Gerey665174f2018-06-19 15:03:05 +0200389 num_rotations_(0) {}
tkchin93411912015-07-22 12:12:17 -0700390
391CallSessionFileRotatingStream::CallSessionFileRotatingStream(
392 const std::string& dir_path,
393 size_t max_total_log_size)
394 : FileRotatingStream(dir_path,
395 kLogPrefix,
396 max_total_log_size / 2,
397 GetNumRotatingLogFiles(max_total_log_size) + 1),
398 max_total_log_size_(max_total_log_size),
399 num_rotations_(0) {
kwibergaf476c72016-11-28 15:21:39 -0800400 RTC_DCHECK_GE(max_total_log_size, 4);
tkchin93411912015-07-22 12:12:17 -0700401}
402
403const char* CallSessionFileRotatingStream::kLogPrefix = "webrtc_log";
404const size_t CallSessionFileRotatingStream::kRotatingLogFileDefaultSize =
405 1024 * 1024;
406
407void CallSessionFileRotatingStream::OnRotation() {
408 ++num_rotations_;
409 if (num_rotations_ == 1) {
410 // On the first rotation adjust the max file size so subsequent files after
411 // the first are smaller.
412 SetMaxFileSize(GetRotatingLogSize(max_total_log_size_));
413 } else if (num_rotations_ == (GetNumFiles() - 1)) {
414 // On the next rotation the very first file is going to be deleted. Change
415 // the rotation index so this doesn't happen.
416 SetRotationIndex(GetRotationIndex() - 1);
417 }
418}
419
420size_t CallSessionFileRotatingStream::GetRotatingLogSize(
421 size_t max_total_log_size) {
422 size_t num_rotating_log_files = GetNumRotatingLogFiles(max_total_log_size);
423 size_t rotating_log_size = num_rotating_log_files > 2
424 ? kRotatingLogFileDefaultSize
425 : max_total_log_size / 4;
426 return rotating_log_size;
427}
428
429size_t CallSessionFileRotatingStream::GetNumRotatingLogFiles(
430 size_t max_total_log_size) {
431 // At minimum have two rotating files. Otherwise split the available log size
432 // evenly across 1MB files.
433 return std::max((size_t)2,
434 (max_total_log_size / 2) / kRotatingLogFileDefaultSize);
435}
436
437} // namespace rtc