| // Copyright 2022 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "missive/util/file.h" | |
| #include <algorithm> | |
| #include <string> | |
| #include <string_view> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/files/file_enumerator.h> | |
| #include <base/files/file_path.h> | |
| #include <base/files/file_util.h> | |
| #include <base/functional/callback.h> | |
| #include <base/logging.h> | |
| #include <base/strings/strcat.h> | |
| #include <base/strings/string_number_conversions.h> | |
| #include <base/types/expected.h> | |
| #include <brillo/files/file_util.h> | |
| #include "missive/analytics/metrics.h" | |
| #include "missive/util/reporting_errors.h" | |
| namespace reporting { | |
| bool DeleteFileWarnIfFailed(const base::FilePath& path) { | |
| const auto delete_result = brillo::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()) { | |
| analytics::Metrics::SendEnumToUMA(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) { | |
| analytics::Metrics::SendEnumToUMA( | |
| 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 = | |
| file.Read(offset, result.data(), file_info.size - offset); | |
| if (read_result != file_info.size - offset) { | |
| 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()) { | |
| 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 = file.Write(0, line.data(), line.size()); | |
| if (write_count < 0 || static_cast<size_t>(write_count) < line.size()) { | |
| analytics::Metrics::SendEnumToUMA(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 = file.Write(0, data.data(), data.size()); | |
| if (write_count < 0 || static_cast<size_t>(write_count) < data.size()) { | |
| 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 |