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 "webrtc/base/filerotatingstream.h" 12 13 #include <algorithm> 14 #include <iostream> 15 #include <string> 16 17 #include "webrtc/base/checks.h" 18 #include "webrtc/base/fileutils.h" 19 #include "webrtc/base/pathutils.h" 20 21 // Note: We use std::cerr for logging in the write paths of this stream to avoid 22 // infinite loops when logging. 23 24 namespace rtc { 25 26 FileRotatingStream::FileRotatingStream(const std::string& dir_path, 27 const std::string& file_prefix) 28 : FileRotatingStream(dir_path, file_prefix, 0, 0, kRead) { 29 } 30 31 FileRotatingStream::FileRotatingStream(const std::string& dir_path, 32 const std::string& file_prefix, 33 size_t max_file_size, 34 size_t num_files) 35 : FileRotatingStream(dir_path, 36 file_prefix, 37 max_file_size, 38 num_files, 39 kWrite) { 40 RTC_DCHECK_GT(max_file_size, 0u); 41 RTC_DCHECK_GT(num_files, 1u); 42 } 43 44 FileRotatingStream::FileRotatingStream(const std::string& dir_path, 45 const std::string& file_prefix, 46 size_t max_file_size, 47 size_t num_files, 48 Mode mode) 49 : dir_path_(dir_path), 50 file_prefix_(file_prefix), 51 mode_(mode), 52 file_stream_(nullptr), 53 max_file_size_(max_file_size), 54 current_file_index_(0), 55 rotation_index_(0), 56 current_bytes_written_(0), 57 disable_buffering_(false) { 58 RTC_DCHECK(Filesystem::IsFolder(dir_path)); 59 switch (mode) { 60 case kWrite: { 61 file_names_.clear(); 62 for (size_t i = 0; i < num_files; ++i) { 63 file_names_.push_back(GetFilePath(i, num_files)); 64 } 65 rotation_index_ = num_files - 1; 66 break; 67 } 68 case kRead: { 69 file_names_ = GetFilesWithPrefix(); 70 std::sort(file_names_.begin(), file_names_.end()); 71 if (file_names_.size() > 0) { 72 // |file_names_| is sorted newest first, so read from the end. 73 current_file_index_ = file_names_.size() - 1; 74 } 75 break; 76 } 77 } 78 } 79 80 FileRotatingStream::~FileRotatingStream() { 81 } 82 83 StreamState FileRotatingStream::GetState() const { 84 if (mode_ == kRead && current_file_index_ < file_names_.size()) { 85 return SS_OPEN; 86 } 87 if (!file_stream_) { 88 return SS_CLOSED; 89 } 90 return file_stream_->GetState(); 91 } 92 93 StreamResult FileRotatingStream::Read(void* buffer, 94 size_t buffer_len, 95 size_t* read, 96 int* error) { 97 RTC_DCHECK(buffer); 98 if (mode_ != kRead) { 99 return SR_EOS; 100 } 101 if (current_file_index_ >= file_names_.size()) { 102 return SR_EOS; 103 } 104 // We will have no file stream initially, and when we are finished with the 105 // previous file. 106 if (!file_stream_) { 107 if (!OpenCurrentFile()) { 108 return SR_ERROR; 109 } 110 } 111 int local_error = 0; 112 if (!error) { 113 error = &local_error; 114 } 115 StreamResult result = file_stream_->Read(buffer, buffer_len, read, error); 116 if (result == SR_EOS || result == SR_ERROR) { 117 if (result == SR_ERROR) { 118 LOG(LS_ERROR) << "Failed to read from: " 119 << file_names_[current_file_index_] << "Error: " << error; 120 } 121 // Reached the end of the file, read next file. If there is an error return 122 // the error status but allow for a next read by reading next file. 123 CloseCurrentFile(); 124 if (current_file_index_ == 0) { 125 // Just finished reading the last file, signal EOS by setting index. 126 current_file_index_ = file_names_.size(); 127 } else { 128 --current_file_index_; 129 } 130 if (read) { 131 *read = 0; 132 } 133 return result == SR_EOS ? SR_SUCCESS : result; 134 } else if (result == SR_SUCCESS) { 135 // Succeeded, continue reading from this file. 136 return SR_SUCCESS; 137 } else { 138 RTC_NOTREACHED(); 139 } 140 return result; 141 } 142 143 StreamResult FileRotatingStream::Write(const void* data, 144 size_t data_len, 145 size_t* written, 146 int* error) { 147 if (mode_ != kWrite) { 148 return SR_EOS; 149 } 150 if (!file_stream_) { 151 std::cerr << "Open() must be called before Write." << std::endl; 152 return SR_ERROR; 153 } 154 // Write as much as will fit in to the current file. 155 RTC_DCHECK_LT(current_bytes_written_, max_file_size_); 156 size_t remaining_bytes = max_file_size_ - current_bytes_written_; 157 size_t write_length = std::min(data_len, remaining_bytes); 158 size_t local_written = 0; 159 if (!written) { 160 written = &local_written; 161 } 162 StreamResult result = file_stream_->Write(data, write_length, written, error); 163 current_bytes_written_ += *written; 164 165 // If we're done with this file, rotate it out. 166 if (current_bytes_written_ >= max_file_size_) { 167 RTC_DCHECK_EQ(current_bytes_written_, max_file_size_); 168 RotateFiles(); 169 } 170 return result; 171 } 172 173 bool FileRotatingStream::Flush() { 174 if (!file_stream_) { 175 return false; 176 } 177 return file_stream_->Flush(); 178 } 179 180 bool FileRotatingStream::GetSize(size_t* size) const { 181 if (mode_ != kRead) { 182 // Not possible to get accurate size on disk when writing because of 183 // potential buffering. 184 return false; 185 } 186 RTC_DCHECK(size); 187 *size = 0; 188 size_t total_size = 0; 189 for (auto file_name : file_names_) { 190 Pathname pathname(file_name); 191 size_t file_size = 0; 192 if (Filesystem::GetFileSize(file_name, &file_size)) { 193 total_size += file_size; 194 } 195 } 196 *size = total_size; 197 return true; 198 } 199 200 void FileRotatingStream::Close() { 201 CloseCurrentFile(); 202 } 203 204 bool FileRotatingStream::Open() { 205 switch (mode_) { 206 case kRead: 207 // Defer opening to when we first read since we want to return read error 208 // if we fail to open next file. 209 return true; 210 case kWrite: { 211 // Delete existing files when opening for write. 212 std::vector<std::string> matching_files = GetFilesWithPrefix(); 213 for (auto matching_file : matching_files) { 214 if (!Filesystem::DeleteFile(matching_file)) { 215 std::cerr << "Failed to delete: " << matching_file << std::endl; 216 } 217 } 218 return OpenCurrentFile(); 219 } 220 } 221 return false; 222 } 223 224 bool FileRotatingStream::DisableBuffering() { 225 disable_buffering_ = true; 226 if (!file_stream_) { 227 std::cerr << "Open() must be called before DisableBuffering()." 228 << std::endl; 229 return false; 230 } 231 return file_stream_->DisableBuffering(); 232 } 233 234 std::string FileRotatingStream::GetFilePath(size_t index) const { 235 RTC_DCHECK_LT(index, file_names_.size()); 236 return file_names_[index]; 237 } 238 239 bool FileRotatingStream::OpenCurrentFile() { 240 CloseCurrentFile(); 241 242 // Opens the appropriate file in the appropriate mode. 243 RTC_DCHECK_LT(current_file_index_, file_names_.size()); 244 std::string file_path = file_names_[current_file_index_]; 245 file_stream_.reset(new FileStream()); 246 const char* mode = nullptr; 247 switch (mode_) { 248 case kWrite: 249 mode = "w+"; 250 // We should always we writing to the zero-th file. 251 RTC_DCHECK_EQ(current_file_index_, 0u); 252 break; 253 case kRead: 254 mode = "r"; 255 break; 256 } 257 int error = 0; 258 if (!file_stream_->Open(file_path, mode, &error)) { 259 std::cerr << "Failed to open: " << file_path << "Error: " << error 260 << std::endl; 261 file_stream_.reset(); 262 return false; 263 } 264 if (disable_buffering_) { 265 file_stream_->DisableBuffering(); 266 } 267 return true; 268 } 269 270 void FileRotatingStream::CloseCurrentFile() { 271 if (!file_stream_) { 272 return; 273 } 274 current_bytes_written_ = 0; 275 file_stream_.reset(); 276 } 277 278 void FileRotatingStream::RotateFiles() { 279 RTC_DCHECK_EQ(mode_, kWrite); 280 CloseCurrentFile(); 281 // Rotates the files by deleting the file at |rotation_index_|, which is the 282 // oldest file and then renaming the newer files to have an incremented index. 283 // See header file comments for example. 284 RTC_DCHECK_LT(rotation_index_, file_names_.size()); 285 std::string file_to_delete = file_names_[rotation_index_]; 286 if (Filesystem::IsFile(file_to_delete)) { 287 if (!Filesystem::DeleteFile(file_to_delete)) { 288 std::cerr << "Failed to delete: " << file_to_delete << std::endl; 289 } 290 } 291 for (auto i = rotation_index_; i > 0; --i) { 292 std::string rotated_name = file_names_[i]; 293 std::string unrotated_name = file_names_[i - 1]; 294 if (Filesystem::IsFile(unrotated_name)) { 295 if (!Filesystem::MoveFile(unrotated_name, rotated_name)) { 296 std::cerr << "Failed to move: " << unrotated_name << " to " 297 << rotated_name << std::endl; 298 } 299 } 300 } 301 // Create a new file for 0th index. 302 OpenCurrentFile(); 303 OnRotation(); 304 } 305 306 std::vector<std::string> FileRotatingStream::GetFilesWithPrefix() const { 307 std::vector<std::string> files; 308 // Iterate over the files in the directory. 309 DirectoryIterator it; 310 Pathname dir_path; 311 dir_path.SetFolder(dir_path_); 312 if (!it.Iterate(dir_path)) { 313 return files; 314 } 315 do { 316 std::string current_name = it.Name(); 317 if (current_name.size() && !it.IsDirectory() && 318 current_name.compare(0, file_prefix_.size(), file_prefix_) == 0) { 319 Pathname path(dir_path_, current_name); 320 files.push_back(path.pathname()); 321 } 322 } while (it.Next()); 323 return files; 324 } 325 326 std::string FileRotatingStream::GetFilePath(size_t index, 327 size_t num_files) const { 328 RTC_DCHECK_LT(index, num_files); 329 std::ostringstream file_name; 330 // The format will be "_%<num_digits>zu". We want to zero pad the index so 331 // that it will sort nicely. 332 size_t max_digits = ((num_files - 1) / 10) + 1; 333 size_t num_digits = (index / 10) + 1; 334 RTC_DCHECK_LE(num_digits, max_digits); 335 size_t padding = max_digits - num_digits; 336 337 file_name << file_prefix_ << "_"; 338 for (size_t i = 0; i < padding; ++i) { 339 file_name << "0"; 340 } 341 file_name << index; 342 343 Pathname file_path(dir_path_, file_name.str()); 344 return file_path.pathname(); 345 } 346 347 CallSessionFileRotatingStream::CallSessionFileRotatingStream( 348 const std::string& dir_path) 349 : FileRotatingStream(dir_path, kLogPrefix), 350 max_total_log_size_(0), 351 num_rotations_(0) { 352 } 353 354 CallSessionFileRotatingStream::CallSessionFileRotatingStream( 355 const std::string& dir_path, 356 size_t max_total_log_size) 357 : FileRotatingStream(dir_path, 358 kLogPrefix, 359 max_total_log_size / 2, 360 GetNumRotatingLogFiles(max_total_log_size) + 1), 361 max_total_log_size_(max_total_log_size), 362 num_rotations_(0) { 363 RTC_DCHECK_GE(max_total_log_size, 4u); 364 } 365 366 const char* CallSessionFileRotatingStream::kLogPrefix = "webrtc_log"; 367 const size_t CallSessionFileRotatingStream::kRotatingLogFileDefaultSize = 368 1024 * 1024; 369 370 void CallSessionFileRotatingStream::OnRotation() { 371 ++num_rotations_; 372 if (num_rotations_ == 1) { 373 // On the first rotation adjust the max file size so subsequent files after 374 // the first are smaller. 375 SetMaxFileSize(GetRotatingLogSize(max_total_log_size_)); 376 } else if (num_rotations_ == (GetNumFiles() - 1)) { 377 // On the next rotation the very first file is going to be deleted. Change 378 // the rotation index so this doesn't happen. 379 SetRotationIndex(GetRotationIndex() - 1); 380 } 381 } 382 383 size_t CallSessionFileRotatingStream::GetRotatingLogSize( 384 size_t max_total_log_size) { 385 size_t num_rotating_log_files = GetNumRotatingLogFiles(max_total_log_size); 386 size_t rotating_log_size = num_rotating_log_files > 2 387 ? kRotatingLogFileDefaultSize 388 : max_total_log_size / 4; 389 return rotating_log_size; 390 } 391 392 size_t CallSessionFileRotatingStream::GetNumRotatingLogFiles( 393 size_t max_total_log_size) { 394 // At minimum have two rotating files. Otherwise split the available log size 395 // evenly across 1MB files. 396 return std::max((size_t)2, 397 (max_total_log_size / 2) / kRotatingLogFileDefaultSize); 398 } 399 400 } // namespace rtc 401