xref: /aosp_15_r20/external/webrtc/rtc_base/file_rotating_stream.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
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 
11 #include "rtc_base/file_rotating_stream.h"
12 
13 #include <cstdio>
14 #include <string>
15 #include <utility>
16 
17 #include "absl/strings/string_view.h"
18 
19 #if defined(WEBRTC_WIN)
20 #include <windows.h>
21 
22 #include "rtc_base/string_utils.h"
23 #else
24 #include <dirent.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #endif  // WEBRTC_WIN
28 
29 #include "absl/algorithm/container.h"
30 #include "absl/strings/match.h"
31 #include "absl/types/optional.h"
32 #include "rtc_base/checks.h"
33 #include "rtc_base/logging.h"
34 #include "rtc_base/strings/string_builder.h"
35 
36 // Note: We use fprintf for logging in the write paths of this stream to avoid
37 // infinite loops when logging.
38 
39 namespace rtc {
40 
41 namespace {
42 
43 const char kCallSessionLogPrefix[] = "webrtc_log";
44 
45 std::string AddTrailingPathDelimiterIfNeeded(absl::string_view directory);
46 
47 // `dir` must have a trailing delimiter. `prefix` must not include wild card
48 // characters.
49 std::vector<std::string> GetFilesWithPrefix(absl::string_view directory,
50                                             absl::string_view prefix);
51 bool DeleteFile(absl::string_view file);
52 bool MoveFile(absl::string_view old_file, absl::string_view new_file);
53 bool IsFile(absl::string_view file);
54 bool IsFolder(absl::string_view file);
55 absl::optional<size_t> GetFileSize(absl::string_view file);
56 
57 #if defined(WEBRTC_WIN)
58 
AddTrailingPathDelimiterIfNeeded(absl::string_view directory)59 std::string AddTrailingPathDelimiterIfNeeded(absl::string_view directory) {
60   if (absl::EndsWith(directory, "\\")) {
61     return std::string(directory);
62   }
63   return std::string(directory) + "\\";
64 }
65 
GetFilesWithPrefix(absl::string_view directory,absl::string_view prefix)66 std::vector<std::string> GetFilesWithPrefix(absl::string_view directory,
67                                             absl::string_view prefix) {
68   RTC_DCHECK(absl::EndsWith(directory, "\\"));
69   WIN32_FIND_DATAW data;
70   HANDLE handle;
71   StringBuilder pattern_builder{directory};
72   pattern_builder << prefix << "*";
73   handle = ::FindFirstFileW(ToUtf16(pattern_builder.str()).c_str(), &data);
74   if (handle == INVALID_HANDLE_VALUE)
75     return {};
76 
77   std::vector<std::string> file_list;
78   do {
79     StringBuilder file_builder{directory};
80     file_builder << ToUtf8(data.cFileName);
81     file_list.emplace_back(file_builder.Release());
82   } while (::FindNextFileW(handle, &data) == TRUE);
83 
84   ::FindClose(handle);
85   return file_list;
86 }
87 
DeleteFile(absl::string_view file)88 bool DeleteFile(absl::string_view file) {
89   return ::DeleteFileW(ToUtf16(file).c_str()) != 0;
90 }
91 
MoveFile(absl::string_view old_file,absl::string_view new_file)92 bool MoveFile(absl::string_view old_file, absl::string_view new_file) {
93   return ::MoveFileW(ToUtf16(old_file).c_str(), ToUtf16(new_file).c_str()) != 0;
94 }
95 
IsFile(absl::string_view file)96 bool IsFile(absl::string_view file) {
97   WIN32_FILE_ATTRIBUTE_DATA data = {0};
98   if (0 == ::GetFileAttributesExW(ToUtf16(file).c_str(), GetFileExInfoStandard,
99                                   &data))
100     return false;
101   return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
102 }
103 
IsFolder(absl::string_view file)104 bool IsFolder(absl::string_view file) {
105   WIN32_FILE_ATTRIBUTE_DATA data = {0};
106   if (0 == ::GetFileAttributesExW(ToUtf16(file).c_str(), GetFileExInfoStandard,
107                                   &data))
108     return false;
109   return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
110          FILE_ATTRIBUTE_DIRECTORY;
111 }
112 
GetFileSize(absl::string_view file)113 absl::optional<size_t> GetFileSize(absl::string_view file) {
114   WIN32_FILE_ATTRIBUTE_DATA data = {0};
115   if (::GetFileAttributesExW(ToUtf16(file).c_str(), GetFileExInfoStandard,
116                              &data) == 0)
117     return absl::nullopt;
118   return data.nFileSizeLow;
119 }
120 
121 #else  // defined(WEBRTC_WIN)
122 
AddTrailingPathDelimiterIfNeeded(absl::string_view directory)123 std::string AddTrailingPathDelimiterIfNeeded(absl::string_view directory) {
124   if (absl::EndsWith(directory, "/")) {
125     return std::string(directory);
126   }
127   return std::string(directory) + "/";
128 }
129 
GetFilesWithPrefix(absl::string_view directory,absl::string_view prefix)130 std::vector<std::string> GetFilesWithPrefix(absl::string_view directory,
131                                             absl::string_view prefix) {
132   RTC_DCHECK(absl::EndsWith(directory, "/"));
133   std::string directory_str(directory);
134   DIR* dir = ::opendir(directory_str.c_str());
135   if (dir == nullptr)
136     return {};
137   std::vector<std::string> file_list;
138   for (struct dirent* dirent = ::readdir(dir); dirent;
139        dirent = ::readdir(dir)) {
140     std::string name = dirent->d_name;
141     if (name.compare(0, prefix.size(), prefix.data(), prefix.size()) == 0) {
142       file_list.emplace_back(directory_str + name);
143     }
144   }
145   ::closedir(dir);
146   return file_list;
147 }
148 
DeleteFile(absl::string_view file)149 bool DeleteFile(absl::string_view file) {
150   return ::unlink(std::string(file).c_str()) == 0;
151 }
152 
MoveFile(absl::string_view old_file,absl::string_view new_file)153 bool MoveFile(absl::string_view old_file, absl::string_view new_file) {
154   return ::rename(std::string(old_file).c_str(),
155                   std::string(new_file).c_str()) == 0;
156 }
157 
IsFile(absl::string_view file)158 bool IsFile(absl::string_view file) {
159   struct stat st;
160   int res = ::stat(std::string(file).c_str(), &st);
161   // Treat symlinks, named pipes, etc. all as files.
162   return res == 0 && !S_ISDIR(st.st_mode);
163 }
164 
IsFolder(absl::string_view file)165 bool IsFolder(absl::string_view file) {
166   struct stat st;
167   int res = ::stat(std::string(file).c_str(), &st);
168   return res == 0 && S_ISDIR(st.st_mode);
169 }
170 
GetFileSize(absl::string_view file)171 absl::optional<size_t> GetFileSize(absl::string_view file) {
172   struct stat st;
173   if (::stat(std::string(file).c_str(), &st) != 0)
174     return absl::nullopt;
175   return st.st_size;
176 }
177 
178 #endif
179 
180 }  // namespace
181 
FileRotatingStream(absl::string_view dir_path,absl::string_view file_prefix,size_t max_file_size,size_t num_files)182 FileRotatingStream::FileRotatingStream(absl::string_view dir_path,
183                                        absl::string_view file_prefix,
184                                        size_t max_file_size,
185                                        size_t num_files)
186     : dir_path_(AddTrailingPathDelimiterIfNeeded(dir_path)),
187       file_prefix_(file_prefix),
188       max_file_size_(max_file_size),
189       current_file_index_(0),
190       rotation_index_(0),
191       current_bytes_written_(0),
192       disable_buffering_(false) {
193   RTC_DCHECK_GT(max_file_size, 0);
194   RTC_DCHECK_GT(num_files, 1);
195   RTC_DCHECK(IsFolder(dir_path));
196   file_names_.clear();
197   for (size_t i = 0; i < num_files; ++i) {
198     file_names_.push_back(GetFilePath(i, num_files));
199   }
200   rotation_index_ = num_files - 1;
201 }
202 
~FileRotatingStream()203 FileRotatingStream::~FileRotatingStream() {}
204 
IsOpen() const205 bool FileRotatingStream::IsOpen() const {
206   return file_.is_open();
207 }
208 
Write(const void * data,size_t data_len)209 bool FileRotatingStream::Write(const void* data, size_t data_len) {
210   if (!file_.is_open()) {
211     std::fprintf(stderr, "Open() must be called before Write.\n");
212     return false;
213   }
214   while (data_len > 0) {
215     // Write as much as will fit in to the current file.
216     RTC_DCHECK_LT(current_bytes_written_, max_file_size_);
217     size_t remaining_bytes = max_file_size_ - current_bytes_written_;
218     size_t write_length = std::min(data_len, remaining_bytes);
219 
220     if (!file_.Write(data, write_length)) {
221       return false;
222     }
223     if (disable_buffering_ && !file_.Flush()) {
224       return false;
225     }
226 
227     current_bytes_written_ += write_length;
228 
229     // If we're done with this file, rotate it out.
230     if (current_bytes_written_ >= max_file_size_) {
231       RTC_DCHECK_EQ(current_bytes_written_, max_file_size_);
232       RotateFiles();
233     }
234     data_len -= write_length;
235     data =
236         static_cast<const void*>(static_cast<const char*>(data) + write_length);
237   }
238   return true;
239 }
240 
Flush()241 bool FileRotatingStream::Flush() {
242   if (!file_.is_open()) {
243     return false;
244   }
245   return file_.Flush();
246 }
247 
Close()248 void FileRotatingStream::Close() {
249   CloseCurrentFile();
250 }
251 
Open()252 bool FileRotatingStream::Open() {
253   // Delete existing files when opening for write.
254   std::vector<std::string> matching_files =
255       GetFilesWithPrefix(dir_path_, file_prefix_);
256   for (const auto& matching_file : matching_files) {
257     if (!DeleteFile(matching_file)) {
258       std::fprintf(stderr, "Failed to delete: %s\n", matching_file.c_str());
259     }
260   }
261   return OpenCurrentFile();
262 }
263 
DisableBuffering()264 bool FileRotatingStream::DisableBuffering() {
265   disable_buffering_ = true;
266   return true;
267 }
268 
GetFilePath(size_t index) const269 std::string FileRotatingStream::GetFilePath(size_t index) const {
270   RTC_DCHECK_LT(index, file_names_.size());
271   return file_names_[index];
272 }
273 
OpenCurrentFile()274 bool FileRotatingStream::OpenCurrentFile() {
275   CloseCurrentFile();
276 
277   // Opens the appropriate file in the appropriate mode.
278   RTC_DCHECK_LT(current_file_index_, file_names_.size());
279   std::string file_path = file_names_[current_file_index_];
280 
281   // We should always be writing to the zero-th file.
282   RTC_DCHECK_EQ(current_file_index_, 0);
283   int error;
284   file_ = webrtc::FileWrapper::OpenWriteOnly(file_path, &error);
285   if (!file_.is_open()) {
286     std::fprintf(stderr, "Failed to open: %s Error: %d\n", file_path.c_str(),
287                  error);
288     return false;
289   }
290   return true;
291 }
292 
CloseCurrentFile()293 void FileRotatingStream::CloseCurrentFile() {
294   if (!file_.is_open()) {
295     return;
296   }
297   current_bytes_written_ = 0;
298   file_.Close();
299 }
300 
RotateFiles()301 void FileRotatingStream::RotateFiles() {
302   CloseCurrentFile();
303   // Rotates the files by deleting the file at `rotation_index_`, which is the
304   // oldest file and then renaming the newer files to have an incremented index.
305   // See header file comments for example.
306   RTC_DCHECK_LT(rotation_index_, file_names_.size());
307   std::string file_to_delete = file_names_[rotation_index_];
308   if (IsFile(file_to_delete)) {
309     if (!DeleteFile(file_to_delete)) {
310       std::fprintf(stderr, "Failed to delete: %s\n", file_to_delete.c_str());
311     }
312   }
313   for (auto i = rotation_index_; i > 0; --i) {
314     std::string rotated_name = file_names_[i];
315     std::string unrotated_name = file_names_[i - 1];
316     if (IsFile(unrotated_name)) {
317       if (!MoveFile(unrotated_name, rotated_name)) {
318         std::fprintf(stderr, "Failed to move: %s to %s\n",
319                      unrotated_name.c_str(), rotated_name.c_str());
320       }
321     }
322   }
323   // Create a new file for 0th index.
324   OpenCurrentFile();
325   OnRotation();
326 }
327 
GetFilePath(size_t index,size_t num_files) const328 std::string FileRotatingStream::GetFilePath(size_t index,
329                                             size_t num_files) const {
330   RTC_DCHECK_LT(index, num_files);
331 
332   const size_t buffer_size = 32;
333   char file_postfix[buffer_size];
334   // We want to zero pad the index so that it will sort nicely.
335   const int max_digits = std::snprintf(nullptr, 0, "%zu", num_files - 1);
336   RTC_DCHECK_LT(1 + max_digits, buffer_size);
337   std::snprintf(file_postfix, buffer_size, "_%0*zu", max_digits, index);
338 
339   return dir_path_ + file_prefix_ + file_postfix;
340 }
341 
CallSessionFileRotatingStream(absl::string_view dir_path,size_t max_total_log_size)342 CallSessionFileRotatingStream::CallSessionFileRotatingStream(
343     absl::string_view dir_path,
344     size_t max_total_log_size)
345     : FileRotatingStream(dir_path,
346                          kCallSessionLogPrefix,
347                          max_total_log_size / 2,
348                          GetNumRotatingLogFiles(max_total_log_size) + 1),
349       max_total_log_size_(max_total_log_size),
350       num_rotations_(0) {
351   RTC_DCHECK_GE(max_total_log_size, 4);
352 }
353 
354 const size_t CallSessionFileRotatingStream::kRotatingLogFileDefaultSize =
355     1024 * 1024;
356 
OnRotation()357 void CallSessionFileRotatingStream::OnRotation() {
358   ++num_rotations_;
359   if (num_rotations_ == 1) {
360     // On the first rotation adjust the max file size so subsequent files after
361     // the first are smaller.
362     SetMaxFileSize(GetRotatingLogSize(max_total_log_size_));
363   } else if (num_rotations_ == (GetNumFiles() - 1)) {
364     // On the next rotation the very first file is going to be deleted. Change
365     // the rotation index so this doesn't happen.
366     SetRotationIndex(GetRotationIndex() - 1);
367   }
368 }
369 
GetRotatingLogSize(size_t max_total_log_size)370 size_t CallSessionFileRotatingStream::GetRotatingLogSize(
371     size_t max_total_log_size) {
372   size_t num_rotating_log_files = GetNumRotatingLogFiles(max_total_log_size);
373   size_t rotating_log_size = num_rotating_log_files > 2
374                                  ? kRotatingLogFileDefaultSize
375                                  : max_total_log_size / 4;
376   return rotating_log_size;
377 }
378 
GetNumRotatingLogFiles(size_t max_total_log_size)379 size_t CallSessionFileRotatingStream::GetNumRotatingLogFiles(
380     size_t max_total_log_size) {
381   // At minimum have two rotating files. Otherwise split the available log size
382   // evenly across 1MB files.
383   return std::max((size_t)2,
384                   (max_total_log_size / 2) / kRotatingLogFileDefaultSize);
385 }
386 
FileRotatingStreamReader(absl::string_view dir_path,absl::string_view file_prefix)387 FileRotatingStreamReader::FileRotatingStreamReader(
388     absl::string_view dir_path,
389     absl::string_view file_prefix) {
390   file_names_ = GetFilesWithPrefix(AddTrailingPathDelimiterIfNeeded(dir_path),
391                                    file_prefix);
392 
393   // Plain sort of the file names would sort by age, i.e., oldest last. Using
394   // std::greater gives us the desired chronological older, oldest first.
395   absl::c_sort(file_names_, std::greater<std::string>());
396 }
397 
398 FileRotatingStreamReader::~FileRotatingStreamReader() = default;
399 
GetSize() const400 size_t FileRotatingStreamReader::GetSize() const {
401   size_t total_size = 0;
402   for (const auto& file_name : file_names_) {
403     total_size += GetFileSize(file_name).value_or(0);
404   }
405   return total_size;
406 }
407 
ReadAll(void * buffer,size_t size) const408 size_t FileRotatingStreamReader::ReadAll(void* buffer, size_t size) const {
409   size_t done = 0;
410   for (const auto& file_name : file_names_) {
411     if (done < size) {
412       webrtc::FileWrapper f = webrtc::FileWrapper::OpenReadOnly(file_name);
413       if (!f.is_open()) {
414         break;
415       }
416       done += f.Read(static_cast<char*>(buffer) + done, size - done);
417     } else {
418       break;
419     }
420   }
421   return done;
422 }
423 
CallSessionFileRotatingStreamReader(absl::string_view dir_path)424 CallSessionFileRotatingStreamReader::CallSessionFileRotatingStreamReader(
425     absl::string_view dir_path)
426     : FileRotatingStreamReader(dir_path, kCallSessionLogPrefix) {}
427 
428 }  // namespace rtc
429