blob: d03ab3937fb44b1ece2381c0eb33d3835e8c9f9a [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
Niels Möller260770c2018-11-07 15:08:18 +010022#include <dirent.h>
Niels Möller7b3c76b2018-11-07 09:54:28 +010023#include <sys/stat.h>
24#endif // WEBRTC_WIN
25
26#include "absl/strings/match.h"
Mirko Bonadei92ea95e2017-09-15 06:47:31 +020027#include "rtc_base/checks.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);
Niels Möller260770c2018-11-07 15:08:18 +010038
39// |dir| must have a trailing delimiter. |prefix| must not include wild card
40// characters.
41std::vector<std::string> GetFilesWithPrefix(const std::string& directory,
42 const std::string& prefix);
43bool DeleteFile(const std::string& file);
44bool MoveFile(const std::string& old_file, const std::string& new_file);
45bool IsFile(const std::string& file);
Niels Möller7b3c76b2018-11-07 09:54:28 +010046bool IsFolder(const std::string& file);
Niels Möller260770c2018-11-07 15:08:18 +010047absl::optional<size_t> GetFileSize(const std::string& file);
Niels Möller7b3c76b2018-11-07 09:54:28 +010048
49#if defined(WEBRTC_WIN)
50
51std::string AddTrailingPathDelimiterIfNeeded(std::string directory) {
52 if (absl::EndsWith(directory, "\\")) {
53 return directory;
54 }
55 return directory + "\\";
56}
57
Niels Möller260770c2018-11-07 15:08:18 +010058std::vector<std::string> GetFilesWithPrefix(const std::string& directory,
59 const std::string& prefix) {
60 RTC_DCHECK(absl::EndsWith(directory, "\\"));
61 WIN32_FIND_DATA data;
62 HANDLE handle;
63 handle = ::FindFirstFile(ToUtf16(directory + prefix + '*').c_str(), &data);
64 if (handle == INVALID_HANDLE_VALUE)
65 return {};
66
67 std::vector<std::string> file_list;
68 do {
69 file_list.emplace_back(directory + ToUtf8(data.cFileName));
70 } while (::FindNextFile(handle, &data) == TRUE);
71
72 ::FindClose(handle);
73 return file_list;
74}
75
76bool DeleteFile(const std::string& file) {
77 return ::DeleteFile(ToUtf16(file).c_str()) != 0;
78}
79
80bool MoveFile(const std::string& old_file, const std::string& new_file) {
81 return ::MoveFile(ToUtf16(old_file).c_str(), ToUtf16(new_file).c_str()) != 0;
82}
83
84bool IsFile(const std::string& file) {
85 WIN32_FILE_ATTRIBUTE_DATA data = {0};
86 if (0 == ::GetFileAttributesEx(ToUtf16(file).c_str(), GetFileExInfoStandard,
87 &data))
88 return false;
89 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
90}
91
Niels Möller7b3c76b2018-11-07 09:54:28 +010092bool IsFolder(const std::string& file) {
93 WIN32_FILE_ATTRIBUTE_DATA data = {0};
94 if (0 == ::GetFileAttributesEx(ToUtf16(file).c_str(), GetFileExInfoStandard,
95 &data))
96 return false;
97 return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
98 FILE_ATTRIBUTE_DIRECTORY;
99}
100
Niels Möller260770c2018-11-07 15:08:18 +0100101absl::optional<size_t> GetFileSize(const std::string& file) {
102 WIN32_FILE_ATTRIBUTE_DATA data = {0};
103 if (::GetFileAttributesEx(ToUtf16(file).c_str(), GetFileExInfoStandard,
104 &data) == 0)
105 return absl::nullopt;
106 return data.nFileSizeLow;
107}
108
Niels Möller7b3c76b2018-11-07 09:54:28 +0100109#else // defined(WEBRTC_WIN)
110
111std::string AddTrailingPathDelimiterIfNeeded(std::string directory) {
112 if (absl::EndsWith(directory, "/")) {
113 return directory;
114 }
115 return directory + "/";
116}
117
Niels Möller260770c2018-11-07 15:08:18 +0100118std::vector<std::string> GetFilesWithPrefix(const std::string& directory,
119 const std::string& prefix) {
120 RTC_DCHECK(absl::EndsWith(directory, "/"));
121 DIR* dir = ::opendir(directory.c_str());
122 if (dir == nullptr)
123 return {};
124 std::vector<std::string> file_list;
125 for (struct dirent* dirent = ::readdir(dir); dirent;
126 dirent = ::readdir(dir)) {
127 std::string name = dirent->d_name;
128 if (name.compare(0, prefix.size(), prefix) == 0) {
129 file_list.emplace_back(directory + name);
130 }
131 }
132 ::closedir(dir);
133 return file_list;
134}
135
136bool DeleteFile(const std::string& file) {
137 return ::unlink(file.c_str()) == 0;
138}
139
140bool MoveFile(const std::string& old_file, const std::string& new_file) {
141 return ::rename(old_file.c_str(), new_file.c_str()) == 0;
142}
143
144bool IsFile(const std::string& file) {
145 struct stat st;
146 int res = ::stat(file.c_str(), &st);
147 // Treat symlinks, named pipes, etc. all as files.
148 return res == 0 && !S_ISDIR(st.st_mode);
149}
150
Niels Möller7b3c76b2018-11-07 09:54:28 +0100151bool IsFolder(const std::string& file) {
152 struct stat st;
153 int res = ::stat(file.c_str(), &st);
154 return res == 0 && S_ISDIR(st.st_mode);
155}
156
Niels Möller260770c2018-11-07 15:08:18 +0100157absl::optional<size_t> GetFileSize(const std::string& file) {
158 struct stat st;
159 if (::stat(file.c_str(), &st) != 0)
160 return absl::nullopt;
161 return st.st_size;
162}
163
Niels Möller7b3c76b2018-11-07 09:54:28 +0100164#endif
165
166} // namespace
167
tkchin93411912015-07-22 12:12:17 -0700168FileRotatingStream::FileRotatingStream(const std::string& dir_path,
169 const std::string& file_prefix)
Yves Gerey665174f2018-06-19 15:03:05 +0200170 : FileRotatingStream(dir_path, file_prefix, 0, 0, kRead) {}
tkchin93411912015-07-22 12:12:17 -0700171
172FileRotatingStream::FileRotatingStream(const std::string& dir_path,
173 const std::string& file_prefix,
174 size_t max_file_size,
175 size_t num_files)
176 : FileRotatingStream(dir_path,
177 file_prefix,
178 max_file_size,
179 num_files,
180 kWrite) {
kwibergaf476c72016-11-28 15:21:39 -0800181 RTC_DCHECK_GT(max_file_size, 0);
182 RTC_DCHECK_GT(num_files, 1);
tkchin93411912015-07-22 12:12:17 -0700183}
184
185FileRotatingStream::FileRotatingStream(const std::string& dir_path,
186 const std::string& file_prefix,
187 size_t max_file_size,
188 size_t num_files,
189 Mode mode)
Niels Möller7b3c76b2018-11-07 09:54:28 +0100190 : dir_path_(AddTrailingPathDelimiterIfNeeded(dir_path)),
tkchin93411912015-07-22 12:12:17 -0700191 file_prefix_(file_prefix),
192 mode_(mode),
193 file_stream_(nullptr),
194 max_file_size_(max_file_size),
195 current_file_index_(0),
196 rotation_index_(0),
197 current_bytes_written_(0),
198 disable_buffering_(false) {
Niels Möller7b3c76b2018-11-07 09:54:28 +0100199 RTC_DCHECK(IsFolder(dir_path));
tkchin93411912015-07-22 12:12:17 -0700200 switch (mode) {
201 case kWrite: {
202 file_names_.clear();
203 for (size_t i = 0; i < num_files; ++i) {
204 file_names_.push_back(GetFilePath(i, num_files));
205 }
206 rotation_index_ = num_files - 1;
207 break;
208 }
209 case kRead: {
Niels Möller260770c2018-11-07 15:08:18 +0100210 file_names_ = GetFilesWithPrefix(dir_path_, file_prefix_);
tkchin93411912015-07-22 12:12:17 -0700211 std::sort(file_names_.begin(), file_names_.end());
212 if (file_names_.size() > 0) {
213 // |file_names_| is sorted newest first, so read from the end.
214 current_file_index_ = file_names_.size() - 1;
215 }
216 break;
217 }
218 }
219}
220
Yves Gerey665174f2018-06-19 15:03:05 +0200221FileRotatingStream::~FileRotatingStream() {}
tkchin93411912015-07-22 12:12:17 -0700222
223StreamState FileRotatingStream::GetState() const {
224 if (mode_ == kRead && current_file_index_ < file_names_.size()) {
225 return SS_OPEN;
226 }
227 if (!file_stream_) {
228 return SS_CLOSED;
229 }
230 return file_stream_->GetState();
231}
232
233StreamResult FileRotatingStream::Read(void* buffer,
234 size_t buffer_len,
235 size_t* read,
236 int* error) {
henrikg91d6ede2015-09-17 00:24:34 -0700237 RTC_DCHECK(buffer);
tkchin93411912015-07-22 12:12:17 -0700238 if (mode_ != kRead) {
239 return SR_EOS;
240 }
241 if (current_file_index_ >= file_names_.size()) {
242 return SR_EOS;
243 }
244 // We will have no file stream initially, and when we are finished with the
245 // previous file.
246 if (!file_stream_) {
247 if (!OpenCurrentFile()) {
248 return SR_ERROR;
249 }
250 }
251 int local_error = 0;
252 if (!error) {
253 error = &local_error;
254 }
255 StreamResult result = file_stream_->Read(buffer, buffer_len, read, error);
256 if (result == SR_EOS || result == SR_ERROR) {
257 if (result == SR_ERROR) {
Mirko Bonadei675513b2017-11-09 11:09:25 +0100258 RTC_LOG(LS_ERROR) << "Failed to read from: "
259 << file_names_[current_file_index_]
260 << "Error: " << error;
tkchin93411912015-07-22 12:12:17 -0700261 }
262 // Reached the end of the file, read next file. If there is an error return
263 // the error status but allow for a next read by reading next file.
264 CloseCurrentFile();
265 if (current_file_index_ == 0) {
266 // Just finished reading the last file, signal EOS by setting index.
267 current_file_index_ = file_names_.size();
268 } else {
269 --current_file_index_;
270 }
271 if (read) {
272 *read = 0;
273 }
274 return result == SR_EOS ? SR_SUCCESS : result;
275 } else if (result == SR_SUCCESS) {
276 // Succeeded, continue reading from this file.
277 return SR_SUCCESS;
278 } else {
279 RTC_NOTREACHED();
280 }
281 return result;
282}
283
284StreamResult FileRotatingStream::Write(const void* data,
285 size_t data_len,
286 size_t* written,
287 int* error) {
288 if (mode_ != kWrite) {
289 return SR_EOS;
290 }
291 if (!file_stream_) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200292 std::fprintf(stderr, "Open() must be called before Write.\n");
tkchin93411912015-07-22 12:12:17 -0700293 return SR_ERROR;
294 }
295 // Write as much as will fit in to the current file.
henrikg91d6ede2015-09-17 00:24:34 -0700296 RTC_DCHECK_LT(current_bytes_written_, max_file_size_);
tkchin93411912015-07-22 12:12:17 -0700297 size_t remaining_bytes = max_file_size_ - current_bytes_written_;
298 size_t write_length = std::min(data_len, remaining_bytes);
299 size_t local_written = 0;
300 if (!written) {
301 written = &local_written;
302 }
303 StreamResult result = file_stream_->Write(data, write_length, written, error);
304 current_bytes_written_ += *written;
305
306 // If we're done with this file, rotate it out.
307 if (current_bytes_written_ >= max_file_size_) {
henrikg91d6ede2015-09-17 00:24:34 -0700308 RTC_DCHECK_EQ(current_bytes_written_, max_file_size_);
tkchin93411912015-07-22 12:12:17 -0700309 RotateFiles();
310 }
311 return result;
312}
313
314bool FileRotatingStream::Flush() {
315 if (!file_stream_) {
316 return false;
317 }
318 return file_stream_->Flush();
319}
320
tkchin28bae022015-07-23 12:27:02 -0700321bool FileRotatingStream::GetSize(size_t* size) const {
322 if (mode_ != kRead) {
323 // Not possible to get accurate size on disk when writing because of
324 // potential buffering.
325 return false;
326 }
henrikg91d6ede2015-09-17 00:24:34 -0700327 RTC_DCHECK(size);
tkchin28bae022015-07-23 12:27:02 -0700328 *size = 0;
329 size_t total_size = 0;
330 for (auto file_name : file_names_) {
Niels Möller260770c2018-11-07 15:08:18 +0100331 total_size += GetFileSize(file_name).value_or(0);
tkchin28bae022015-07-23 12:27:02 -0700332 }
333 *size = total_size;
334 return true;
335}
336
tkchin93411912015-07-22 12:12:17 -0700337void FileRotatingStream::Close() {
338 CloseCurrentFile();
339}
340
341bool FileRotatingStream::Open() {
342 switch (mode_) {
343 case kRead:
344 // Defer opening to when we first read since we want to return read error
345 // if we fail to open next file.
346 return true;
347 case kWrite: {
348 // Delete existing files when opening for write.
Niels Möller260770c2018-11-07 15:08:18 +0100349 std::vector<std::string> matching_files =
350 GetFilesWithPrefix(dir_path_, file_prefix_);
351 for (const auto& matching_file : matching_files) {
352 if (!DeleteFile(matching_file)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200353 std::fprintf(stderr, "Failed to delete: %s\n", matching_file.c_str());
tkchin93411912015-07-22 12:12:17 -0700354 }
355 }
356 return OpenCurrentFile();
357 }
358 }
359 return false;
360}
361
362bool FileRotatingStream::DisableBuffering() {
363 disable_buffering_ = true;
364 if (!file_stream_) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200365 std::fprintf(stderr, "Open() must be called before DisableBuffering().\n");
tkchin93411912015-07-22 12:12:17 -0700366 return false;
367 }
368 return file_stream_->DisableBuffering();
369}
370
371std::string FileRotatingStream::GetFilePath(size_t index) const {
henrikg91d6ede2015-09-17 00:24:34 -0700372 RTC_DCHECK_LT(index, file_names_.size());
tkchin93411912015-07-22 12:12:17 -0700373 return file_names_[index];
374}
375
376bool FileRotatingStream::OpenCurrentFile() {
377 CloseCurrentFile();
378
379 // Opens the appropriate file in the appropriate mode.
henrikg91d6ede2015-09-17 00:24:34 -0700380 RTC_DCHECK_LT(current_file_index_, file_names_.size());
tkchin93411912015-07-22 12:12:17 -0700381 std::string file_path = file_names_[current_file_index_];
382 file_stream_.reset(new FileStream());
383 const char* mode = nullptr;
384 switch (mode_) {
385 case kWrite:
386 mode = "w+";
387 // We should always we writing to the zero-th file.
kwibergaf476c72016-11-28 15:21:39 -0800388 RTC_DCHECK_EQ(current_file_index_, 0);
tkchin93411912015-07-22 12:12:17 -0700389 break;
390 case kRead:
391 mode = "r";
392 break;
393 }
394 int error = 0;
395 if (!file_stream_->Open(file_path, mode, &error)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200396 std::fprintf(stderr, "Failed to open: %s Error: %i\n", file_path.c_str(),
397 error);
tkchin93411912015-07-22 12:12:17 -0700398 file_stream_.reset();
399 return false;
400 }
401 if (disable_buffering_) {
402 file_stream_->DisableBuffering();
403 }
404 return true;
405}
406
407void FileRotatingStream::CloseCurrentFile() {
408 if (!file_stream_) {
409 return;
410 }
411 current_bytes_written_ = 0;
412 file_stream_.reset();
413}
414
415void FileRotatingStream::RotateFiles() {
henrikg91d6ede2015-09-17 00:24:34 -0700416 RTC_DCHECK_EQ(mode_, kWrite);
tkchin93411912015-07-22 12:12:17 -0700417 CloseCurrentFile();
418 // Rotates the files by deleting the file at |rotation_index_|, which is the
419 // oldest file and then renaming the newer files to have an incremented index.
420 // See header file comments for example.
hayscd02b0fa2015-12-08 13:59:05 -0800421 RTC_DCHECK_LT(rotation_index_, file_names_.size());
tkchin93411912015-07-22 12:12:17 -0700422 std::string file_to_delete = file_names_[rotation_index_];
Niels Möller260770c2018-11-07 15:08:18 +0100423 if (IsFile(file_to_delete)) {
424 if (!DeleteFile(file_to_delete)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200425 std::fprintf(stderr, "Failed to delete: %s\n", file_to_delete.c_str());
tkchin93411912015-07-22 12:12:17 -0700426 }
427 }
428 for (auto i = rotation_index_; i > 0; --i) {
429 std::string rotated_name = file_names_[i];
430 std::string unrotated_name = file_names_[i - 1];
Niels Möller260770c2018-11-07 15:08:18 +0100431 if (IsFile(unrotated_name)) {
432 if (!MoveFile(unrotated_name, rotated_name)) {
Jonas Olsson55378f42018-05-25 10:23:10 +0200433 std::fprintf(stderr, "Failed to move: %s to %s\n",
434 unrotated_name.c_str(), rotated_name.c_str());
tkchin93411912015-07-22 12:12:17 -0700435 }
436 }
437 }
438 // Create a new file for 0th index.
439 OpenCurrentFile();
440 OnRotation();
441}
442
tkchin93411912015-07-22 12:12:17 -0700443std::string FileRotatingStream::GetFilePath(size_t index,
444 size_t num_files) const {
henrikg91d6ede2015-09-17 00:24:34 -0700445 RTC_DCHECK_LT(index, num_files);
tkchin93411912015-07-22 12:12:17 -0700446
Jonas Olsson671cae22018-06-14 09:57:39 +0200447 const size_t buffer_size = 32;
448 char file_postfix[buffer_size];
449 // We want to zero pad the index so that it will sort nicely.
450 const int max_digits = std::snprintf(nullptr, 0, "%zu", num_files - 1);
451 RTC_DCHECK_LT(1 + max_digits, buffer_size);
452 std::snprintf(file_postfix, buffer_size, "_%0*zu", max_digits, index);
tkchin93411912015-07-22 12:12:17 -0700453
Niels Möller7b3c76b2018-11-07 09:54:28 +0100454 return dir_path_ + file_prefix_ + file_postfix;
tkchin93411912015-07-22 12:12:17 -0700455}
456
457CallSessionFileRotatingStream::CallSessionFileRotatingStream(
458 const std::string& dir_path)
459 : FileRotatingStream(dir_path, kLogPrefix),
460 max_total_log_size_(0),
Yves Gerey665174f2018-06-19 15:03:05 +0200461 num_rotations_(0) {}
tkchin93411912015-07-22 12:12:17 -0700462
463CallSessionFileRotatingStream::CallSessionFileRotatingStream(
464 const std::string& dir_path,
465 size_t max_total_log_size)
466 : FileRotatingStream(dir_path,
467 kLogPrefix,
468 max_total_log_size / 2,
469 GetNumRotatingLogFiles(max_total_log_size) + 1),
470 max_total_log_size_(max_total_log_size),
471 num_rotations_(0) {
kwibergaf476c72016-11-28 15:21:39 -0800472 RTC_DCHECK_GE(max_total_log_size, 4);
tkchin93411912015-07-22 12:12:17 -0700473}
474
475const char* CallSessionFileRotatingStream::kLogPrefix = "webrtc_log";
476const size_t CallSessionFileRotatingStream::kRotatingLogFileDefaultSize =
477 1024 * 1024;
478
479void CallSessionFileRotatingStream::OnRotation() {
480 ++num_rotations_;
481 if (num_rotations_ == 1) {
482 // On the first rotation adjust the max file size so subsequent files after
483 // the first are smaller.
484 SetMaxFileSize(GetRotatingLogSize(max_total_log_size_));
485 } else if (num_rotations_ == (GetNumFiles() - 1)) {
486 // On the next rotation the very first file is going to be deleted. Change
487 // the rotation index so this doesn't happen.
488 SetRotationIndex(GetRotationIndex() - 1);
489 }
490}
491
492size_t CallSessionFileRotatingStream::GetRotatingLogSize(
493 size_t max_total_log_size) {
494 size_t num_rotating_log_files = GetNumRotatingLogFiles(max_total_log_size);
495 size_t rotating_log_size = num_rotating_log_files > 2
496 ? kRotatingLogFileDefaultSize
497 : max_total_log_size / 4;
498 return rotating_log_size;
499}
500
501size_t CallSessionFileRotatingStream::GetNumRotatingLogFiles(
502 size_t max_total_log_size) {
503 // At minimum have two rotating files. Otherwise split the available log size
504 // evenly across 1MB files.
505 return std::max((size_t)2,
506 (max_total_log_size / 2) / kRotatingLogFileDefaultSize);
507}
508
509} // namespace rtc