| // Copyright 2021 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "croslog/log_rotator/log_rotator.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file_enumerator.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/string_util.h> |
| |
| #include "croslog/constants.h" |
| |
| namespace log_rotator { |
| |
| constexpr int DAYS_TO_PRESERVE_LOGS = 7; |
| |
| LogRotator::LogRotator(base::FilePath base_log_path) |
| : base_log_path_(base_log_path) {} |
| |
| // Creating a new log file for Forwarder is handled outside this code. |
| |
| base::FilePath LogRotator::GetFilePathWithIndex(int index) { |
| CHECK_GE(index, 0); |
| if (index == 0) |
| return base_log_path_; |
| |
| return base_log_path_.InsertBeforeExtensionASCII("." + |
| base::NumberToString(index)); |
| } |
| |
| int LogRotator::GetIndexFromFilePath(const base::FilePath& log_path) { |
| if (log_path == base_log_path_) |
| return 0; |
| |
| if (log_path.value().length() <= base_log_path_.value().length()) { |
| // Invalid input, since the given path must be longer than the base path. |
| return -1; |
| } |
| |
| // Verify the base part (before the index number) of the path. |
| const base::FilePath base_path_body = base_log_path_.RemoveFinalExtension(); |
| size_t base_path_body_len = base_path_body.value().length(); |
| const std::string& log_path_body = |
| log_path.value().substr(0, base_path_body_len); |
| if (log_path_body != base_path_body.value()) { |
| // Invalid input, since the path str doesn't start with the path string |
| return -1; |
| } |
| if (log_path.value()[base_path_body_len] != '.') { |
| // Invalid input, since in the path, the dot doesn't follow with the base |
| // path. |
| return -1; |
| } |
| |
| // Verify the final extension of the path. |
| const std::string& final_extension = base_log_path_.FinalExtension(); |
| size_t final_extension_len = final_extension.length(); |
| const std::string& log_path_final_extension = log_path.value().substr( |
| log_path.value().length() - final_extension_len, final_extension_len); |
| if (log_path_final_extension != final_extension) { |
| // Invalid input, since the path str doesn't start with the path string |
| return -1; |
| } |
| |
| // Extract the index number. |
| const std::string& log_path_index_str = log_path.value().substr( |
| base_path_body_len + 1, |
| log_path.value().length() - base_path_body_len - final_extension_len - 1); |
| int index = 0; |
| if (!base::StringToInt(log_path_index_str, &index)) |
| return -1; |
| |
| if (index == 0) { |
| // The ".0" extension is invalid. The 0-th file should have no extension. |
| return -1; |
| } |
| |
| return index; |
| } |
| |
| void LogRotator::CleanUpFiles(int max_index) { |
| base::FilePath dir = base_log_path_.DirName(); |
| base::FilePath pattern = base_log_path_.BaseName().AddExtension("*"); |
| |
| base::FileEnumerator file_enumerator(dir, false, base::FileEnumerator::FILES, |
| pattern.value()); |
| |
| while (!file_enumerator.Next().empty()) { |
| base::FilePath file_path = dir.Append(file_enumerator.GetInfo().GetName()); |
| int index = GetIndexFromFilePath(file_path); |
| if (index > max_index || index < 0) |
| base::DeleteFile(file_path); |
| } |
| } |
| |
| void LogRotator::RotateLogFile(int max_index) { |
| std::vector<base::FileEnumerator::FileInfo> info; |
| |
| base::FilePath max_index_path = GetFilePathWithIndex(max_index); |
| if (base::PathExists(max_index_path)) |
| base::DeleteFile(max_index_path); |
| |
| for (int i = (max_index - 1); i >= 0; --i) { |
| base::FilePath old_path = GetFilePathWithIndex(i); |
| base::FilePath new_path = GetFilePathWithIndex(i + 1); |
| |
| if (!base::PathExists(old_path)) |
| continue; |
| |
| if (!base::Move(old_path, new_path)) { |
| LOG(ERROR) << "File error while moving " << old_path << " to " << new_path |
| << ": " |
| << base::File::ErrorToString(base::File::GetLastFileError()); |
| } |
| } |
| |
| CleanUpFiles(max_index); |
| CreateNewBaseFile(max_index); |
| } |
| |
| void LogRotator::CreateNewBaseFile(int max_index) { |
| const base::FilePath base_file_path = GetFilePathWithIndex(0); |
| if (base::PathExists(base_file_path)) |
| return; |
| |
| int mode = -1; |
| uid_t uid = 0; |
| gid_t gid = 0; |
| bool failed_on_stat = false; |
| // Traverse the log files from newer one, and retrieve the file info. |
| for (int i = 1; i < max_index; ++i) { |
| const base::FilePath previous_file_path = GetFilePathWithIndex(i); |
| if (!base::PathExists(previous_file_path)) |
| continue; |
| |
| base::stat_wrapper_t file_info; |
| if (base::File::Stat(previous_file_path.value().c_str(), &file_info) == 0) { |
| constexpr int kFilePermissionMask = S_IRWXU | S_IRWXG | S_IRWXO; |
| mode = file_info.st_mode & kFilePermissionMask; |
| uid = file_info.st_uid; |
| gid = file_info.st_gid; |
| break; |
| } else { |
| PLOG(ERROR) << "Error on retrieving the file info: " |
| << previous_file_path; |
| failed_on_stat = true; |
| } |
| } |
| |
| if (mode == -1) { |
| if (failed_on_stat) { |
| // Show an error only when the stat was called but failed. |
| LOG(ERROR) << "A new log file won't be created, because retrieving the " |
| "mode from the all old files was failed."; |
| } |
| return; |
| } |
| |
| // Create the log file. |
| base::File base_file(base_file_path, |
| base::File::FLAG_CREATE | base::File::FLAG_WRITE); |
| if (!base_file.IsValid()) { |
| LOG(ERROR) << "Failed to create or open a new log file."; |
| return; |
| } |
| |
| // Set the same permission. |
| if (HANDLE_EINTR(fchmod(base_file.GetPlatformFile(), mode)) != 0) { |
| LOG(ERROR) << "Failed to set the mode to the new log file."; |
| } |
| |
| // Set the same owner. |
| if (HANDLE_EINTR(fchown(base_file.GetPlatformFile(), uid, gid)) != 0) { |
| LOG(ERROR) << "Failed to set the owner and group to the new log file."; |
| } |
| } |
| |
| void RotateStandardLogFiles() { |
| for (const auto& base_log_path_str : croslog::kLogsToRotate) { |
| const base::FilePath& base_log_path = |
| base::FilePath{base_log_path_str.data()}; |
| LogRotator rotator(base_log_path); |
| rotator.RotateLogFile(DAYS_TO_PRESERVE_LOGS); |
| } |
| } |
| |
| } // namespace log_rotator |