1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
|
// 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
|