blob: eb208ea7972f1ce982eba29339f0db0b7f2febfb [file] [log] [blame]
// 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