archive: refactor bad mutable-state API in the NAR parse listener

Remove the mutable state stuff that assumes that one file is being
written a time. It's true that we don't write multiple files
interleaved, but that mutable state is evil.

Change-Id: Ia1481da48255d901e4b09a9b783e7af44fae8cff
This commit is contained in:
jade 2024-09-11 00:27:39 -07:00
parent 81c2e0ac8e
commit ca1dc3f70b
4 changed files with 127 additions and 79 deletions

View file

@ -45,11 +45,12 @@ struct NarAccessor : public FSAccessor
uint64_t pos = 0; uint64_t pos = 0;
public:
NarIndexer(NarAccessor & acc, Source & source) NarIndexer(NarAccessor & acc, Source & source)
: acc(acc), source(source) : acc(acc), source(source)
{ } { }
void createMember(const Path & path, NarMember member) NarMember & createMember(const Path & path, NarMember member)
{ {
size_t level = std::count(path.begin(), path.end(), '/'); size_t level = std::count(path.begin(), path.end(), '/');
while (parents.size() > level) parents.pop(); while (parents.size() > level) parents.pop();
@ -63,6 +64,8 @@ struct NarAccessor : public FSAccessor
auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member)); auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member));
parents.push(&result.first->second); parents.push(&result.first->second);
} }
return *parents.top();
} }
void createDirectory(const Path & path) override void createDirectory(const Path & path) override
@ -70,28 +73,17 @@ struct NarAccessor : public FSAccessor
createMember(path, {FSAccessor::Type::tDirectory, false, 0, 0}); createMember(path, {FSAccessor::Type::tDirectory, false, 0, 0});
} }
void createRegularFile(const Path & path) override std::unique_ptr<FileHandle> createRegularFile(const Path & path, uint64_t size, bool executable) override
{ {
createMember(path, {FSAccessor::Type::tRegular, false, 0, 0}); auto & memb = createMember(path, {FSAccessor::Type::tRegular, false, 0, 0});
}
void closeRegularFile() override
{ }
void isExecutable() override
{
parents.top()->isExecutable = true;
}
void preallocateContents(uint64_t size) override
{
assert(size <= std::numeric_limits<uint64_t>::max()); assert(size <= std::numeric_limits<uint64_t>::max());
parents.top()->size = (uint64_t) size; memb.size = (uint64_t) size;
parents.top()->start = pos; memb.start = pos;
} memb.isExecutable = executable;
void receiveContents(std::string_view data) override return std::make_unique<FileHandle>();
{ } }
void createSymlink(const Path & path, const std::string & target) override void createSymlink(const Path & path, const std::string & target) override
{ {

View file

@ -386,19 +386,30 @@ namespace {
*/ */
struct RetrieveRegularNARVisitor : NARParseVisitor struct RetrieveRegularNARVisitor : NARParseVisitor
{ {
Sink & sink; struct MyFileHandle : public FileHandle
RetrieveRegularNARVisitor(Sink & sink) : sink(sink) { }
void createRegularFile(const Path & path) override
{ {
} Sink & sink;
void receiveContents(std::string_view data) override void receiveContents(std::string_view data) override
{ {
sink(data); sink(data);
} }
private:
MyFileHandle(Sink & sink) : sink(sink) {}
friend struct RetrieveRegularNARVisitor;
};
Sink & sink;
RetrieveRegularNARVisitor(Sink & sink) : sink(sink) { }
std::unique_ptr<FileHandle> createRegularFile(const Path & path, uint64_t size, bool executable) override
{
return std::unique_ptr<MyFileHandle>(new MyFileHandle{sink});
}
void createDirectory(const Path & path) override void createDirectory(const Path & path) override
{ {
assert(false && "RetrieveRegularNARVisitor::createDirectory must not be called"); assert(false && "RetrieveRegularNARVisitor::createDirectory must not be called");

View file

@ -347,16 +347,13 @@ static WireFormatGenerator restore(NARParseVisitor & sink, Generator<nar::Entry>
}, },
[&](nar::File f) { [&](nar::File f) {
return [](auto f, auto & sink) -> WireFormatGenerator { return [](auto f, auto & sink) -> WireFormatGenerator {
sink.createRegularFile(f.path); auto handle = sink.createRegularFile(f.path, f.size, f.executable);
sink.preallocateContents(f.size);
if (f.executable) {
sink.isExecutable();
}
while (auto block = f.contents.next()) { while (auto block = f.contents.next()) {
sink.receiveContents(std::string_view{block->data(), block->size()}); handle->receiveContents(std::string_view{block->data(), block->size()});
co_yield *block; co_yield *block;
} }
sink.closeRegularFile(); handle->close();
}(std::move(f), sink); }(std::move(f), sink);
}, },
[&](nar::Symlink sl) { [&](nar::Symlink sl) {
@ -422,29 +419,22 @@ void parseDump(NARParseVisitor & sink, Source & source)
struct NARRestoreVisitor : NARParseVisitor struct NARRestoreVisitor : NARParseVisitor
{ {
Path dstPath; Path dstPath;
private:
class MyFileHandle : public FileHandle
{
AutoCloseFD fd; AutoCloseFD fd;
void createDirectory(const Path & path) override MyFileHandle(AutoCloseFD && fd, uint64_t size, bool executable) : FileHandle(), fd(std::move(fd))
{ {
Path p = dstPath + path; if (executable) {
if (mkdir(p.c_str(), 0777) == -1) makeExecutable();
throw SysError("creating directory '%1%'", p);
};
void createRegularFile(const Path & path) override
{
Path p = dstPath + path;
fd = AutoCloseFD{open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)};
if (!fd) throw SysError("creating file '%1%'", p);
} }
void closeRegularFile() override maybePreallocateContents(size);
{
/* Call close explicitly to make sure the error is checked */
fd.close();
} }
void isExecutable() override void makeExecutable()
{ {
struct stat st; struct stat st;
if (fstat(fd.get(), &st) == -1) if (fstat(fd.get(), &st) == -1)
@ -453,7 +443,7 @@ struct NARRestoreVisitor : NARParseVisitor
throw SysError("fchmod"); throw SysError("fchmod");
} }
void preallocateContents(uint64_t len) override void maybePreallocateContents(uint64_t len)
{ {
if (!archiveSettings.preallocateContents) if (!archiveSettings.preallocateContents)
return; return;
@ -471,11 +461,41 @@ struct NARRestoreVisitor : NARParseVisitor
#endif #endif
} }
public:
~MyFileHandle() = default;
virtual void close() override
{
/* Call close explicitly to make sure the error is checked */
fd.close();
}
void receiveContents(std::string_view data) override void receiveContents(std::string_view data) override
{ {
writeFull(fd.get(), data); writeFull(fd.get(), data);
} }
friend struct NARRestoreVisitor;
};
public:
void createDirectory(const Path & path) override
{
Path p = dstPath + path;
if (mkdir(p.c_str(), 0777) == -1)
throw SysError("creating directory '%1%'", p);
};
std::unique_ptr<FileHandle> createRegularFile(const Path & path, uint64_t size, bool executable) override
{
Path p = dstPath + path;
AutoCloseFD fd = AutoCloseFD{open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)};
if (!fd) throw SysError("creating file '%1%'", p);
return std::unique_ptr<MyFileHandle>(new MyFileHandle(std::move(fd), size, executable));
}
void createSymlink(const Path & path, const std::string & target) override void createSymlink(const Path & path, const std::string & target) override
{ {
Path p = dstPath + path; Path p = dstPath + path;

View file

@ -83,15 +83,40 @@ WireFormatGenerator dumpString(std::string_view s);
*/ */
struct NARParseVisitor struct NARParseVisitor
{ {
virtual void createDirectory(const Path & path) { }; /**
* A type-erased file handle specific to this particular NARParseVisitor.
*/
struct FileHandle
{
FileHandle() {}
FileHandle(FileHandle const &) = delete;
FileHandle & operator=(FileHandle &) = delete;
virtual void createRegularFile(const Path & path) { }; /** Puts one block of data into the file */
virtual void closeRegularFile() { }; virtual void receiveContents(std::string_view data) { }
virtual void isExecutable() { };
virtual void preallocateContents(uint64_t size) { };
virtual void receiveContents(std::string_view data) { };
virtual void createSymlink(const Path & path, const std::string & target) { }; /**
* Explicitly closes the file. Further operations may throw an assert.
* This exists so that closing can fail and throw an exception without doing so in a destructor.
*/
virtual void close() { }
virtual ~FileHandle() = default;
};
virtual void createDirectory(const Path & path) { }
/**
* Creates a regular file in the extraction output with the given size and executable flag.
* The size is guaranteed to be the true size of the file.
*/
[[nodiscard]]
virtual std::unique_ptr<FileHandle> createRegularFile(const Path & path, uint64_t size, bool executable)
{
return std::make_unique<FileHandle>();
}
virtual void createSymlink(const Path & path, const std::string & target) { }
}; };
namespace nar { namespace nar {