mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
459 lines
14 KiB
C++
459 lines
14 KiB
C++
/// \file S3StorageClient.cpp
|
|
/// \brief Implementation of StorageClient operations in S3
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <fstream>
|
|
|
|
#include "aws/core/Aws.h"
|
|
#include "aws/core/auth/AWSCredentials.h"
|
|
#include "aws/core/auth/AWSCredentialsProvider.h"
|
|
#include "aws/core/utils/logging/FormattedLogSystem.h"
|
|
#include "aws/s3/S3Client.h"
|
|
#include "aws/s3/model/CopyObjectRequest.h"
|
|
#include "aws/s3/model/DeleteObjectRequest.h"
|
|
#include "aws/s3/model/GetObjectRequest.h"
|
|
#include "aws/s3/model/HeadObjectRequest.h"
|
|
#include "aws/s3/model/PutObjectRequest.h"
|
|
|
|
#include "llvm/Support/Process.h"
|
|
#include "llvm/Support/YAMLTraits.h"
|
|
|
|
#include "revng/Storage/Path.h"
|
|
#include "revng/Support/Assert.h"
|
|
#include "revng/Support/OnQuit.h"
|
|
#include "revng/Support/PathList.h"
|
|
#include "revng/Support/TemporaryFile.h"
|
|
|
|
#include "S3StorageClient.h"
|
|
#include "Utils.h"
|
|
|
|
using FilenameMapType = llvm::StringMap<std::string>;
|
|
|
|
template<>
|
|
struct llvm::yaml::CustomMappingTraits<FilenameMapType> {
|
|
static void inputOne(IO &IO, llvm::StringRef Key, FilenameMapType &Value) {
|
|
IO.mapRequired(Key.str().c_str(), Value[Key]);
|
|
}
|
|
|
|
static void output(IO &IO, FilenameMapType &Value) {
|
|
for (auto &Pair : Value) {
|
|
IO.mapRequired(Pair.first().str().c_str(), Pair.second);
|
|
}
|
|
}
|
|
};
|
|
|
|
namespace {
|
|
|
|
using Aws::Utils::Logging::FormattedLogSystem;
|
|
using Aws::Utils::Logging::LogLevel;
|
|
|
|
Logger<> Logger("s3-storage");
|
|
|
|
class LoggerSystem : public FormattedLogSystem {
|
|
private:
|
|
std::mutex Mutex;
|
|
|
|
public:
|
|
LoggerSystem(LogLevel LogLevel) : FormattedLogSystem(LogLevel) {}
|
|
~LoggerSystem() override = default;
|
|
void Flush() override { Logger.flush(); }
|
|
|
|
void ProcessFormattedStatement(Aws::String &&Statement) override {
|
|
// This function needs to be thread-safe, hence the lock_guard
|
|
// TODO: check if the mangled text is the result of aggressive flushed
|
|
// and/or if buffering might alleviate this problem
|
|
std::lock_guard Guard(Mutex);
|
|
revng_log(Logger, Statement);
|
|
}
|
|
};
|
|
|
|
bool SDKIsInitialized = false;
|
|
|
|
void initializeSDK() {
|
|
revng_assert(!SDKIsInitialized);
|
|
|
|
LogLevel Level(LogLevel::Info);
|
|
Aws::SDKOptions Options;
|
|
|
|
Options.loggingOptions.logLevel = Level;
|
|
Options.loggingOptions.logger_create_fn = [Level]() {
|
|
return std::make_shared<LoggerSystem>(Level);
|
|
};
|
|
Aws::InitAPI(Options);
|
|
OnQuit->add([Options = std::move(Options)] { Aws::ShutdownAPI(Options); });
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace revng {
|
|
|
|
static llvm::StringRef consumeSplit(llvm::StringRef &Input, char SplitChar) {
|
|
size_t Pos = Input.find(SplitChar);
|
|
revng_assert(Pos != llvm::StringRef::npos);
|
|
llvm::StringRef Result = Input.slice(0, Pos);
|
|
Input = Input.drop_front(Pos + 1);
|
|
return Result;
|
|
}
|
|
|
|
template<typename T>
|
|
static llvm::Error
|
|
toError(const Aws::Utils::Outcome<T, Aws::S3::S3Error> &Request) {
|
|
revng_check(!Request.IsSuccess());
|
|
return revng::createError(Request.GetError().GetMessage());
|
|
}
|
|
|
|
std::string S3StorageClient::resolvePath(llvm::StringRef Path) {
|
|
if (Path.empty()) {
|
|
return SubPath;
|
|
} else {
|
|
revng_assert(checkPath(Path, getStyle()));
|
|
return joinPath(getStyle(), SubPath, Path);
|
|
}
|
|
}
|
|
|
|
// Given the path `dir1/dir2/filename.ext` this function will return the path
|
|
// `dir1/dir2/<random UUID>-filename.ext`
|
|
static std::string generateNewFilename(llvm::StringRef Path) {
|
|
revng_assert(not Path.ends_with("/"));
|
|
|
|
using uuid_t = uint8_t[16];
|
|
uuid_t UUID;
|
|
for (int I = 0; I < 16; I++)
|
|
UUID[I] = (llvm::sys::Process::GetRandomNumber() % UINT8_MAX);
|
|
|
|
std::string Output;
|
|
llvm::raw_string_ostream OS(Output);
|
|
OS.write_uuid(UUID);
|
|
OS.flush();
|
|
|
|
if (Path.contains("/")) {
|
|
auto Parts = Path.rsplit("/");
|
|
return Parts.first.str() + "/" + Output + "-" + Parts.second.str();
|
|
} else {
|
|
return Output + "-" + Path.str();
|
|
}
|
|
}
|
|
|
|
static Aws::Auth::AWSCredentials readCredentials(llvm::StringRef Credentials) {
|
|
llvm::StringRef Username = consumeSplit(Credentials, ':');
|
|
llvm::StringRef Password = Credentials;
|
|
return Aws::Auth::AWSCredentials{ Username.str(), Password.str() };
|
|
}
|
|
|
|
class S3ReadableFile : public ReadableFile {
|
|
private:
|
|
TemporaryFile TempFile;
|
|
std::unique_ptr<llvm::MemoryBuffer> Buffer;
|
|
|
|
public:
|
|
S3ReadableFile(TemporaryFile &&TempFile,
|
|
std::unique_ptr<llvm::MemoryBuffer> &&Buffer) :
|
|
TempFile(std::move(TempFile)), Buffer(std::move(Buffer)) {}
|
|
~S3ReadableFile() override = default;
|
|
llvm::MemoryBuffer &buffer() override { return *Buffer; };
|
|
};
|
|
|
|
class S3WritableFile : public WritableFile {
|
|
private:
|
|
TemporaryFile TempFile;
|
|
std::unique_ptr<llvm::raw_fd_ostream> OS;
|
|
std::string Path;
|
|
ContentEncoding Encoding;
|
|
S3StorageClient &Client;
|
|
|
|
public:
|
|
S3WritableFile(TemporaryFile &&TempFile,
|
|
std::unique_ptr<llvm::raw_fd_ostream> &&OS,
|
|
llvm::StringRef Path,
|
|
ContentEncoding Encoding,
|
|
S3StorageClient &Client) :
|
|
TempFile(std::move(TempFile)),
|
|
OS(std::move(OS)),
|
|
Path(Path.str()),
|
|
Encoding(Encoding),
|
|
Client(Client) {}
|
|
|
|
llvm::raw_pwrite_stream &os() override { return *OS; }
|
|
llvm::Error commit() override {
|
|
OS->flush();
|
|
|
|
Aws::S3::Model::PutObjectRequest Request;
|
|
Request.SetBucket(Client.Bucket);
|
|
|
|
if (Encoding == ContentEncoding::Gzip)
|
|
Request.SetContentEncoding("gzip");
|
|
|
|
std::string NewFilename = generateNewFilename(Path);
|
|
Request.SetKey(Client.resolvePath(NewFilename));
|
|
|
|
auto File = std::make_shared<Aws::FStream>(TempFile.path().str(),
|
|
std::ios_base::in
|
|
| std::ios_base::binary);
|
|
if (File->fail()) {
|
|
return revng::createError("Could not open temporary file");
|
|
}
|
|
|
|
Request.SetBody(File);
|
|
Aws::S3::Model::PutObjectOutcome Result = Client.Client.PutObject(Request);
|
|
if (not Result.IsSuccess())
|
|
return toError(Result);
|
|
|
|
Client.FilenameMap[Path] = NewFilename;
|
|
return llvm::Error::success();
|
|
}
|
|
};
|
|
|
|
class S3CredentialsProvider : public Aws::Auth::AWSCredentialsProvider {
|
|
private:
|
|
Aws::Auth::AWSCredentials &Credentials;
|
|
|
|
public:
|
|
explicit S3CredentialsProvider(Aws::Auth::AWSCredentials &Credentials) :
|
|
Credentials(Credentials) {}
|
|
~S3CredentialsProvider() override = default;
|
|
Aws::Auth::AWSCredentials GetAWSCredentials() override { return Credentials; }
|
|
};
|
|
|
|
S3StorageClient::S3StorageClient(llvm::StringRef RawURL) {
|
|
// Url format is:
|
|
// s3://<username>:<password>@<region>+<host:port>/<bucket name>/<path>
|
|
revng_assert(isS3URL(RawURL));
|
|
|
|
Aws::Client::ClientConfiguration Config(false, "standard", true);
|
|
Config.enableEndpointDiscovery = false;
|
|
|
|
llvm::StringRef URL(RawURL);
|
|
|
|
if (URL.starts_with("s3://")) {
|
|
Config.scheme = Aws::Http::Scheme::HTTP;
|
|
RedactedURL += "s3://";
|
|
revng_assert(URL.consume_front("s3://"));
|
|
} else {
|
|
Config.scheme = Aws::Http::Scheme::HTTPS;
|
|
RedactedURL += "s3s://";
|
|
revng_assert(URL.consume_front("s3s://"));
|
|
}
|
|
|
|
llvm::StringRef CredentialsString = consumeSplit(URL, '@');
|
|
Credentials = readCredentials(CredentialsString);
|
|
RedactedURL += "<user>:<pwd>@";
|
|
|
|
Config.region = consumeSplit(URL, '+').str();
|
|
Config.endpointOverride = consumeSplit(URL, '/').str();
|
|
RedactedURL += Config.region + '+' + Config.endpointOverride + '/';
|
|
|
|
Client = { std::make_shared<S3CredentialsProvider>(Credentials),
|
|
Config,
|
|
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Always,
|
|
false };
|
|
|
|
if (URL.contains('/')) {
|
|
Bucket = consumeSplit(URL, '/').str();
|
|
SubPath = URL.str();
|
|
} else {
|
|
Bucket = URL.str();
|
|
SubPath = "";
|
|
}
|
|
|
|
RedactedURL += Bucket + '/' + SubPath;
|
|
}
|
|
|
|
llvm::Expected<std::unique_ptr<S3StorageClient>>
|
|
S3StorageClient::fromURL(llvm::StringRef URL) {
|
|
if (not SDKIsInitialized)
|
|
initializeSDK();
|
|
|
|
auto Instance = std::make_unique<S3StorageClient>(URL);
|
|
std::string IndexPath = Instance->resolvePath(IndexName);
|
|
|
|
Aws::S3::Model::GetObjectRequest Request;
|
|
Request.SetBucket(Instance->Bucket);
|
|
Request.SetKey(IndexPath);
|
|
|
|
Aws::S3::Model::GetObjectOutcome Result = Instance->Client.GetObject(Request);
|
|
if (not Result.IsSuccess()) {
|
|
using Aws::Http::HttpResponseCode::NOT_FOUND;
|
|
if (Result.GetError().GetResponseCode() == NOT_FOUND)
|
|
return Instance;
|
|
else
|
|
return toError(Result);
|
|
}
|
|
|
|
std::string SerializedIndex;
|
|
{
|
|
constexpr size_t BufSize = 4096;
|
|
char Buffer[BufSize];
|
|
auto &Body = Result.GetResult().GetBody();
|
|
|
|
while (!Body.eof()) {
|
|
Body.read(Buffer, BufSize);
|
|
SerializedIndex.append(Buffer, Body.gcount());
|
|
}
|
|
}
|
|
|
|
llvm::yaml::Input YAMLInput(SerializedIndex);
|
|
YAMLInput >> Instance->FilenameMap;
|
|
|
|
return Instance;
|
|
}
|
|
|
|
std::string S3StorageClient::dumpString() const {
|
|
return RedactedURL;
|
|
}
|
|
|
|
llvm::Expected<PathType> S3StorageClient::type(llvm::StringRef Path) {
|
|
if (FilenameMap.count(Path) > 0) {
|
|
Aws::S3::Model::HeadObjectRequest Request;
|
|
Request.SetBucket(Bucket);
|
|
Request.SetKey(resolvePath(FilenameMap[Path]));
|
|
|
|
Aws::S3::Model::HeadObjectOutcome Result = Client.HeadObject(Request);
|
|
if (not Result.IsSuccess()) {
|
|
using Aws::Http::HttpResponseCode::NOT_FOUND;
|
|
if (Result.GetError().GetResponseCode() == NOT_FOUND)
|
|
return PathType::Missing;
|
|
else
|
|
return toError(Result);
|
|
}
|
|
|
|
return PathType::File;
|
|
} else {
|
|
std::string Prefix = Path.endswith("/") ? Path.str() : (Path.str() + "/");
|
|
for (auto &[MapPath, _] : FilenameMap) {
|
|
if (MapPath.startswith(Prefix))
|
|
return PathType::Directory;
|
|
}
|
|
return PathType::Missing;
|
|
}
|
|
}
|
|
|
|
llvm::Error S3StorageClient::createDirectory(llvm::StringRef Path) {
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error S3StorageClient::remove(llvm::StringRef Path) {
|
|
revng_assert(FilenameMap.count(Path) != 0);
|
|
FilenameMap.erase(Path);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::sys::path::Style S3StorageClient::getStyle() const {
|
|
return llvm::sys::path::Style::posix;
|
|
}
|
|
|
|
llvm::Error S3StorageClient::copy(llvm::StringRef Source,
|
|
llvm::StringRef Destination) {
|
|
if (FilenameMap.count(Source) == 0) {
|
|
return revng::createError("Source file %s does not exist",
|
|
Source.str().c_str());
|
|
}
|
|
|
|
FilenameMap[Destination] = FilenameMap[Source];
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Expected<std::unique_ptr<ReadableFile>>
|
|
S3StorageClient::getReadableFile(llvm::StringRef Path) {
|
|
using llvm::MemoryBuffer;
|
|
if (FilenameMap.count(Path) == 0) {
|
|
return revng::createError("File %s does not exist", Path.str().c_str());
|
|
}
|
|
|
|
Aws::S3::Model::GetObjectRequest Request;
|
|
Request.SetBucket(Bucket);
|
|
Request.SetKey(resolvePath(FilenameMap[Path]));
|
|
|
|
Aws::S3::Model::GetObjectOutcome Result = Client.GetObject(Request);
|
|
if (not Result.IsSuccess())
|
|
return toError(Result);
|
|
|
|
auto MaybeTemporary = TemporaryFile::make("revng-s3-storage");
|
|
if (!MaybeTemporary) {
|
|
return llvm::createStringError(MaybeTemporary.getError(),
|
|
"Could not create temporary file");
|
|
}
|
|
|
|
std::ofstream OS(MaybeTemporary->path().str(), std::ios::binary);
|
|
if (OS.fail()) {
|
|
return revng::createError("Could not open temporary file");
|
|
}
|
|
|
|
constexpr size_t BufSize = 4096;
|
|
llvm::SmallVector<char> Buffer;
|
|
Buffer.resize_for_overwrite(BufSize);
|
|
auto &Body = Result.GetResult().GetBody();
|
|
|
|
while (!Body.eof()) {
|
|
Body.read(Buffer.data(), BufSize);
|
|
OS.write(Buffer.data(), Body.gcount());
|
|
}
|
|
|
|
OS.flush();
|
|
OS.close();
|
|
|
|
auto MaybeReadableStream = MemoryBuffer::getFile(MaybeTemporary->path());
|
|
if (not MaybeReadableStream) {
|
|
return llvm::createStringError(MaybeReadableStream.getError(),
|
|
"Failed to open the file for reading");
|
|
}
|
|
|
|
return std::make_unique<S3ReadableFile>(std::move(MaybeTemporary.get()),
|
|
std::move(MaybeReadableStream.get()));
|
|
}
|
|
|
|
llvm::Expected<std::unique_ptr<WritableFile>>
|
|
S3StorageClient::getWritableFile(llvm::StringRef Path,
|
|
ContentEncoding Encoding) {
|
|
auto MaybeTemporary = TemporaryFile::make("revng-s3-storage");
|
|
if (!MaybeTemporary) {
|
|
return llvm::createStringError(MaybeTemporary.getError(),
|
|
"Could not create temporary file");
|
|
}
|
|
|
|
std::error_code EC;
|
|
auto OS = std::make_unique<llvm::raw_fd_ostream>(MaybeTemporary->path(), EC);
|
|
if (EC)
|
|
return llvm::createStringError(EC, "Could not open temporary file");
|
|
|
|
return std::make_unique<S3WritableFile>(std::move(MaybeTemporary.get()),
|
|
std::move(OS),
|
|
Path,
|
|
Encoding,
|
|
*this);
|
|
}
|
|
|
|
llvm::Error S3StorageClient::commit() {
|
|
std::string SerializedIndex;
|
|
|
|
{
|
|
llvm::raw_string_ostream OS(SerializedIndex);
|
|
llvm::yaml::Output YAMLOutput(OS);
|
|
YAMLOutput << FilenameMap;
|
|
}
|
|
|
|
Aws::S3::Model::PutObjectRequest Request;
|
|
Request.SetBucket(Bucket);
|
|
Request.SetKey(resolvePath(IndexName));
|
|
|
|
auto Stream = std::make_shared<std::stringstream>(SerializedIndex,
|
|
std::ios_base::in
|
|
| std::ios_base::binary);
|
|
|
|
Request.SetBody(Stream);
|
|
Aws::S3::Model::PutObjectOutcome Result = Client.PutObject(Request);
|
|
if (not Result.IsSuccess())
|
|
return toError(Result);
|
|
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
llvm::Error S3StorageClient::setCredentials(llvm::StringRef Credentials) {
|
|
this->Credentials = readCredentials(Credentials);
|
|
return llvm::Error::success();
|
|
}
|
|
|
|
} // namespace revng
|