Files
revng-revng/include/revng/Pipes/FileContainer.h
Giacomo Vercesi 761e84fc9f Add revng::cantFail
Add the `revng::cantFail` function which allows to wrap functions with
`std::error_code` and check their result. This is analogous to
`llvm::cantFail` for `llvm::Error`.
2025-02-05 10:46:00 +01:00

250 lines
7.9 KiB
C++

#pragma once
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <optional>
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"
#include "revng/Pipeline/ContainerSet.h"
#include "revng/Pipeline/LLVMContainerFactory.h"
#include "revng/Pipeline/Target.h"
#include "revng/Pipes/Kinds.h"
#include "revng/Support/Assert.h"
namespace revng::pipes {
/// A temporary file is a container that wraps a single temporary file on disk.
/// The enumeration of this container will yield the empty set if there is
/// currently no file associated to a instance of Temporary file, and will
/// return The target ("root", K) otherwise, where K is the kind provided at
/// construction time.
template<pipeline::SingleElementKind *K,
const char *TypeName,
const char *MIME,
const char *Suffix>
class FileContainer
: public pipeline::Container<FileContainer<K, TypeName, MIME, Suffix>> {
private:
llvm::SmallString<32> Path;
public:
inline static char ID = '0';
inline static const llvm::StringRef MIMEType = MIME;
inline static const char *Name = TypeName;
FileContainer(llvm::StringRef Name) :
pipeline::Container<FileContainer>(Name), Path() {}
FileContainer(FileContainer &&);
~FileContainer() override { remove(); }
FileContainer(const FileContainer &);
FileContainer &operator=(const FileContainer &Other) noexcept {
if (this == &Other)
return *this;
if (Path.empty()) {
using llvm::sys::fs::createTemporaryFile;
cantFail(createTemporaryFile(llvm::Twine("revng-") + this->name(),
Other.Suffix,
Path));
llvm::sys::RemoveFileOnSignal(Path);
}
cantFail(llvm::sys::fs::copy_file(Other.Path, Path));
return *this;
}
FileContainer &operator=(FileContainer &&Other) noexcept {
if (this == &Other)
return *this;
remove();
Path = std::move(Other.Path);
return *this;
}
std::unique_ptr<pipeline::ContainerBase>
cloneFiltered(const pipeline::TargetsList &Container) const final {
auto Result = std::make_unique<FileContainer>(this->name());
// Return an empty FileContainer if we are empty or our target has not been
// requested
if (Path.empty() or not Container.contains(getOnlyPossibleTarget()))
return Result;
Result->getOrCreatePath();
cantFail(llvm::sys::fs::copy_file(Path, Result->Path));
return Result;
}
pipeline::TargetsList enumerate() const final {
if (Path.empty())
return {};
return pipeline::TargetsList({ getOnlyPossibleTarget() });
}
bool remove(const pipeline::TargetsList &Target) final {
auto NotFound = llvm::find(Target, getOnlyPossibleTarget()) == Target.end();
if (NotFound)
return false;
clear();
return true;
}
llvm::Error store(const revng::FilePath &Path) const override {
// We must ensure that if we got invalidated then no file on disk is
// present, so that the next time we load we don't mistakenly think that we
// have some content.
//
// Other containers do not need this because they determine their content by
// looking inside the stored file, instead of only checking if the file
// exists.
if (this->Path.empty()) {
auto MaybeExists = Path.exists();
if (not MaybeExists)
return MaybeExists.takeError();
if (MaybeExists.get())
return Path.remove();
return llvm::Error::success();
}
return revng::FilePath::fromLocalStorage(this->Path).copyTo(Path);
}
llvm::Error load(const revng::FilePath &Path) override {
auto MaybeExists = Path.exists();
if (not MaybeExists)
return MaybeExists.takeError();
if (not MaybeExists.get()) {
clear();
return llvm::Error::success();
}
getOrCreatePath();
return Path.copyTo(revng::FilePath::fromLocalStorage(this->Path));
}
void clear() override { *this = FileContainer(this->name()); }
llvm::Error serialize(llvm::raw_ostream &OS) const override {
if (Path.empty())
return llvm::Error::success();
if (auto MaybeBuffer = llvm::MemoryBuffer::getFile(Path); !MaybeBuffer)
return llvm::createStringError(MaybeBuffer.getError(),
"could not read file");
else
OS << (*MaybeBuffer)->getBuffer();
return llvm::Error::success();
}
llvm::Error deserialize(const llvm::MemoryBuffer &Buffer) override {
std::error_code EC;
llvm::raw_fd_ostream OS(getOrCreatePath(), EC, llvm::sys::fs::OF_None);
if (EC)
return llvm::createStringError(EC,
"could not write file at %s",
Path.str().str().c_str());
OS << Buffer.getBuffer();
return llvm::Error::success();
}
llvm::Error extractOne(llvm::raw_ostream &OS,
const pipeline::Target &Target) const override {
revng_check(Target == getOnlyPossibleTarget());
return serialize(OS);
}
static std::vector<pipeline::Kind *> possibleKinds() { return { K }; }
public:
std::optional<llvm::StringRef> path() const {
if (Path.empty())
return std::nullopt;
return llvm::StringRef(Path);
}
llvm::StringRef getOrCreatePath() {
if (Path.empty()) {
using llvm::sys::fs::createTemporaryFile;
cantFail(createTemporaryFile(llvm::Twine("revng-") + this->name(),
Suffix,
Path));
llvm::sys::RemoveFileOnSignal(Path);
}
return llvm::StringRef(Path);
}
bool exists() const { return not Path.empty(); }
void dump() const debug_function { dbg << Path.data() << "\n"; }
private:
void mergeBackImpl(FileContainer &&Container) override {
if (not Container.exists())
return;
cantFail(llvm::sys::fs::rename(*Container.path(), getOrCreatePath()));
Container.Path = "";
}
void remove() {
if (not Path.empty()) {
llvm::sys::DontRemoveFileOnSignal(Path);
cantFail(llvm::sys::fs::remove(Path));
}
}
pipeline::Target getOnlyPossibleTarget() const {
return pipeline::Target({}, *K);
}
};
inline constexpr char BinaryFileMIMEType[] = "application/"
"x-executable";
inline constexpr char BinaryFileSuffix[] = "";
inline constexpr char BinaryFileName[] = "binary";
using BinaryFileContainer = FileContainer<&kinds::Binary,
BinaryFileName,
BinaryFileMIMEType,
BinaryFileSuffix>;
inline constexpr char ObjectFileMIMEType[] = "application/x-object";
inline constexpr char ObjectFileSuffix[] = ".o";
inline constexpr char ObjectFileName[] = "object";
using ObjectFileContainer = FileContainer<&kinds::Object,
ObjectFileName,
ObjectFileMIMEType,
ObjectFileSuffix>;
inline constexpr char TranslatedFileMIMEType[] = "application/x-executable";
inline constexpr char TranslatedFileSuffix[] = "";
inline constexpr char TranslatedFileName[] = "translated";
using TranslatedFileContainer = FileContainer<&kinds::Translated,
TranslatedFileName,
TranslatedFileMIMEType,
TranslatedFileSuffix>;
inline constexpr char HexDumpFileMIMEType[] = "text/x.hexdump+ptml";
inline constexpr char HexDumpFileSuffix[] = ".hex";
inline constexpr char HexDumpFileName[] = "hex-dump";
using HexDumpFileContainer = FileContainer<&kinds::HexDump,
HexDumpFileName,
HexDumpFileMIMEType,
HexDumpFileSuffix>;
} // namespace revng::pipes