blob: 9477b2ad0b6a2815ce05ddfbc51543e7982bd3b5 [file] [log] [blame]
// Copyright 2023 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/storage/storage_util.h"
#include <string>
#include <tuple>
#include <base/strings/strcat.h>
#include <base/uuid.h>
#include "missive/util/file.h"
#include "missive/util/status.h"
namespace reporting {
// Find all subdirectories in the storage directory that represent storage
// queues based on the subdirectory name. Returns a set of tuples of
// <Priority,GenerationGuid> where each tuple represents a valid storage
// queue directory. Queue directories are named in the format:
// <Priority>.<GenerationGuid> (e.g. foo/bar/Security.Xhd34k,
// foo/bar/FastBatch.bKd3La1). If the directory name follows this format,
// then it is a valid queue directory so we parse the priority and
// generation guid and return them. The exception to this naming convention
// are legacy queue directories which may be named by priority with no
// generation guid (e.g. foo/bar/Security, foo/bar/FastBatch). In this case
// we set the generation guid in the queue parameter to be an empty string.
StorageDirectory::Set StorageDirectory::FindQueueDirectories(
const StorageOptions& options) {
Set queue_params;
base::FileEnumerator dir_enum(options.directory(),
/*recursive=*/false,
base::FileEnumerator::DIRECTORIES);
for (auto full_name = dir_enum.Next(); !full_name.empty();
full_name = dir_enum.Next()) {
if (const auto priority_result =
ParsePriorityFromQueueDirectory(full_name, options);
priority_result.ok() && full_name.Extension().empty()) {
// This is a legacy queue directory named just by priority with no
// generation guid as an extension: foo/bar/Security,
// foo/bar/FastBatch, etc.
queue_params.emplace(
std::make_tuple(priority_result.ValueOrDie(), GenerationGuid()));
LOG(INFO) << "Found legacy queue directory: " << full_name;
} else if (auto queue_param =
GetPriorityAndGenerationGuid(full_name, options);
queue_param.ok()) {
queue_params.emplace(queue_param.ValueOrDie());
} else {
LOG(INFO) << "Could not parse queue parameters from filename "
<< full_name.MaybeAsASCII()
<< " error = " << queue_param.status();
}
}
return queue_params;
}
StatusOr<std::tuple<Priority, GenerationGuid>>
StorageDirectory::GetPriorityAndGenerationGuid(const base::FilePath& full_name,
const StorageOptions& options) {
// Try to parse generation guid from file path
const auto generation_guid = ParseGenerationGuidFromFileName(full_name);
if (!generation_guid.ok()) {
return generation_guid.status();
}
// Try to parse a priority from file path
const auto priority = ParsePriorityFromQueueDirectory(full_name, options);
if (!priority.ok()) {
return priority.status();
}
return std::make_tuple(priority.ValueOrDie(), generation_guid.ValueOrDie());
}
StatusOr<GenerationGuid> StorageDirectory::ParseGenerationGuidFromFileName(
const base::FilePath& full_name) {
// The string returned by `Extension()` includes the leading period, i.e
// ".txt" instead of "txt", so remove the period just get the text part of
// the extension.
if (full_name.Extension().empty()) {
return Status(
error::DATA_LOSS,
base::StrCat({"Could not parse generation GUID from queue directory ",
full_name.MaybeAsASCII()}));
}
std::string extension_without_leading_period =
full_name.Extension().substr(1);
const auto generation_guid =
base::Uuid::ParseCaseInsensitive(extension_without_leading_period);
if (!generation_guid.is_valid()) {
return Status(
error::DATA_LOSS,
base::StrCat({"Could not parse generation GUID from queue directory ",
full_name.MaybeAsASCII()}));
}
return generation_guid.AsLowercaseString();
}
StatusOr<Priority> StorageDirectory::ParsePriorityFromQueueDirectory(
const base::FilePath full_path, const StorageOptions& options) {
for (const auto& priority_queue_options_pair :
options.ProduceQueuesOptionsList()) {
if (priority_queue_options_pair.second.directory() ==
full_path.RemoveExtension()) {
return priority_queue_options_pair.first;
}
}
return Status(error::NOT_FOUND,
base::StrCat({"Found no priority for queue directory ",
full_path.MaybeAsASCII()}));
}
bool StorageDirectory::DeleteEmptySubdirectories(
const base::FilePath directory) {
base::FileEnumerator dir_enum(directory,
/*recursive=*/false,
base::FileEnumerator::DIRECTORIES);
return DeleteFilesWarnIfFailed(
dir_enum, base::BindRepeating([](const base::FilePath& directory) {
LOG(ERROR) << "Checking " << directory.MaybeAsASCII();
if (base::IsDirectoryEmpty(directory)) {
LOG(INFO) << "Deleting empty queue directory "
<< directory.MaybeAsASCII();
return true;
}
return false;
}));
}
} // namespace reporting