blob: 70ef05d93848a4d3b20f6a872c8807a273d542cb [file] [log] [blame] [edit]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/reporting/util/file.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/types/expected.h"
#include "components/reporting/util/reporting_errors.h"
namespace reporting {
bool DeleteFileWarnIfFailed(const base::FilePath& path) {
const auto delete_result = base::DeleteFile(path);
if (!delete_result) {
LOG(WARNING) << "Failed to delete " << path.MaybeAsASCII();
}
return delete_result;
}
bool DeleteFilesWarnIfFailed(
base::FileEnumerator& dir_enum,
base::RepeatingCallback<bool(const base::FilePath&)> pred) {
std::vector<base::FilePath> files_to_delete;
for (auto full_name = dir_enum.Next(); !full_name.empty();
full_name = dir_enum.Next()) {
if (pred.Run(full_name)) {
files_to_delete.push_back(std::move(full_name));
}
}
// Starting from deeper paths so that directories are always emptied first if
// the files there are to be deleted. This can be done by deleting the file
// with the longest full paths first.
std::sort(files_to_delete.begin(), files_to_delete.end(),
[](const base::FilePath& fp0, const base::FilePath& fp1) {
// Use size of the file path string is sufficient. Semantically it
// is better to use the number of components in a file path
// (GetComponents().size()), but this is more efficient.
return fp0.value().size() > fp1.value().size();
});
bool success = true;
for (const auto& file_to_delete : files_to_delete) {
if (!DeleteFileWarnIfFailed(file_to_delete)) {
success = false;
}
}
return success;
}
bool DeleteFilesWarnIfFailed(
base::FileEnumerator&& dir_enum,
base::RepeatingCallback<bool(const base::FilePath&)> pred) {
return DeleteFilesWarnIfFailed(dir_enum, pred);
}
StatusOr<std::string> MaybeReadFile(const base::FilePath& file_path,
int64_t offset) {
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
DataLossErrorReason::FAILED_TO_OPEN_FILE,
DataLossErrorReason::MAX_VALUE);
return base::unexpected(Status(
error::NOT_FOUND, base::StrCat({"Could not open health data file ",
file_path.MaybeAsASCII()})));
}
base::File::Info file_info;
if (!file.GetInfo(&file_info) || file_info.size - offset < 0) {
base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
DataLossErrorReason::FAILED_TO_READ_FILE_INFO,
DataLossErrorReason::MAX_VALUE);
return base::unexpected(
Status(error::DATA_LOSS, base::StrCat({"Failed to read data file info ",
file_path.MaybeAsASCII()})));
}
std::string result;
result.resize(file_info.size - offset);
const int read_result =
UNSAFE_TODO(file.Read(offset, result.data(), file_info.size - offset));
if (read_result != file_info.size - offset) {
base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
DataLossErrorReason::FAILED_TO_READ_FILE,
DataLossErrorReason::MAX_VALUE);
return base::unexpected(Status(
error::DATA_LOSS,
base::StrCat({"Failed to read data file ", file_path.MaybeAsASCII()})));
}
return result;
}
Status AppendLine(const base::FilePath& file_path,
const std::string_view& data) {
base::File file(file_path,
base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND);
if (!file.IsValid()) {
base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
DataLossErrorReason::FAILED_TO_OPEN_FILE,
DataLossErrorReason::MAX_VALUE);
return Status(error::NOT_FOUND,
base::StrCat({"Could not open health data file ",
file_path.MaybeAsASCII()}));
}
const std::string line = base::StrCat({data, "\n"});
const int write_count = UNSAFE_TODO(file.Write(0, line.data(), line.size()));
if (write_count < 0 || static_cast<size_t>(write_count) < line.size()) {
base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
DataLossErrorReason::FAILED_TO_WRITE_FILE,
DataLossErrorReason::MAX_VALUE);
return Status(error::DATA_LOSS,
base::StrCat({"Failed to write health data file ",
file_path.MaybeAsASCII(), " write count=",
base::NumberToString(write_count)}));
}
return Status::StatusOK();
}
StatusOr<uint32_t> RemoveAndTruncateLine(const base::FilePath& file_path,
uint32_t pos) {
StatusOr<std::string> status_or = MaybeReadFile(file_path, pos);
if (!status_or.has_value()) {
return base::unexpected(std::move(status_or).error());
}
std::string content = status_or.value();
uint32_t offset = 0;
// Search for next new line after pos.
while (offset < content.length()) {
if (content.at(offset++) == '\n') {
break;
}
}
// Check if the last line was removed.
if (offset >= content.length()) {
content = "";
} else {
content = content.substr(offset);
}
Status status = MaybeWriteFile(file_path, content);
if (!status.ok()) {
return base::unexpected(std::move(status));
}
return pos + offset;
}
Status MaybeWriteFile(const base::FilePath& file_path,
const std::string_view& data) {
base::File file(file_path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!file.IsValid()) {
return Status(error::NOT_FOUND, base::StrCat({"Could not open data file ",
file_path.MaybeAsASCII()}));
}
const int write_count = UNSAFE_TODO(file.Write(0, data.data(), data.size()));
if (write_count < 0 || static_cast<size_t>(write_count) < data.size()) {
base::UmaHistogramEnumeration(reporting::kUmaDataLossErrorReason,
DataLossErrorReason::FAILED_TO_WRITE_FILE,
DataLossErrorReason::MAX_VALUE);
return Status(
error::DATA_LOSS,
base::StrCat({"Failed to write data file ", file_path.MaybeAsASCII(),
" write count=", base::NumberToString(write_count)}));
}
return Status::StatusOK();
}
} // namespace reporting