Factor out MemorySourceAccessor, implement missing features

The new `MemorySourceAccessor` rather than being a slightly lossy flat
map is a complete in-memory model of file system objects.

Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
This commit is contained in:
John Ericson 2023-11-02 19:39:09 -04:00
parent 8e222fbb12
commit 9b880e3e29
4 changed files with 205 additions and 39 deletions

View file

@ -14,7 +14,7 @@ struct SourcePath;
class StorePath; class StorePath;
class Store; class Store;
struct InputAccessor : SourceAccessor, std::enable_shared_from_this<InputAccessor> struct InputAccessor : virtual SourceAccessor, std::enable_shared_from_this<InputAccessor>
{ {
/** /**
* Return the maximum last-modified time of the files in this * Return the maximum last-modified time of the files in this

View file

@ -1,48 +1,16 @@
#include "memory-input-accessor.hh" #include "memory-input-accessor.hh"
#include "memory-source-accessor.hh"
namespace nix { namespace nix {
struct MemoryInputAccessorImpl : MemoryInputAccessor struct MemoryInputAccessorImpl : MemoryInputAccessor, MemorySourceAccessor
{ {
std::map<CanonPath, std::string> files;
std::string readFile(const CanonPath & path) override
{
auto i = files.find(path);
if (i == files.end())
throw Error("file '%s' does not exist", path);
return i->second;
}
bool pathExists(const CanonPath & path) override
{
auto i = files.find(path);
return i != files.end();
}
std::optional<Stat> maybeLstat(const CanonPath & path) override
{
auto i = files.find(path);
if (i != files.end())
return Stat { .type = tRegular, .isExecutable = false };
return std::nullopt;
}
DirEntries readDirectory(const CanonPath & path) override
{
return {};
}
std::string readLink(const CanonPath & path) override
{
throw UnimplementedError("MemoryInputAccessor::readLink");
}
SourcePath addFile(CanonPath path, std::string && contents) override SourcePath addFile(CanonPath path, std::string && contents) override
{ {
files.emplace(path, std::move(contents)); return {
ref(shared_from_this()),
return {ref(shared_from_this()), std::move(path)}; MemorySourceAccessor::addFile(path, std::move(contents))
};
} }
}; };

View file

@ -0,0 +1,124 @@
#include "memory-source-accessor.hh"
namespace nix {
MemorySourceAccessor::File *
MemorySourceAccessor::open(const CanonPath & path, std::optional<File> create)
{
File * cur = &root;
bool newF = false;
for (std::string_view name : path)
{
auto * curDirP = std::get_if<File::Directory>(&cur->raw);
if (!curDirP)
return nullptr;
auto & curDir = *curDirP;
auto i = curDir.contents.find(name);
if (i == curDir.contents.end()) {
if (!create)
return nullptr;
else {
newF = true;
i = curDir.contents.insert(i, {
std::string { name },
File::Directory {},
});
}
}
cur = &i->second;
}
if (newF && create) *cur = std::move(*create);
return cur;
}
std::string MemorySourceAccessor::readFile(const CanonPath & path)
{
auto * f = open(path, std::nullopt);
if (!f)
throw Error("file '%s' does not exist", path);
if (auto * r = std::get_if<File::Regular>(&f->raw))
return r->contents;
else
throw Error("file '%s' is not a regular file", path);
}
bool MemorySourceAccessor::pathExists(const CanonPath & path)
{
return open(path, std::nullopt);
}
MemorySourceAccessor::Stat MemorySourceAccessor::File::lstat() const
{
return std::visit(overloaded {
[](const Regular & r) {
return Stat {
.type = tRegular,
.fileSize = r.contents.size(),
.isExecutable = r.executable,
};
},
[](const Directory &) {
return Stat {
.type = tDirectory,
};
},
[](const Symlink &) {
return Stat {
.type = tSymlink,
};
},
}, this->raw);
}
std::optional<MemorySourceAccessor::Stat>
MemorySourceAccessor::maybeLstat(const CanonPath & path)
{
const auto * f = open(path, std::nullopt);
return f ? std::optional { f->lstat() } : std::nullopt;
}
MemorySourceAccessor::DirEntries MemorySourceAccessor::readDirectory(const CanonPath & path)
{
auto * f = open(path, std::nullopt);
if (!f)
throw Error("file '%s' does not exist", path);
if (auto * d = std::get_if<File::Directory>(&f->raw)) {
DirEntries res;
for (auto & [name, file] : d->contents)
res.insert_or_assign(name, file.lstat().type);
return res;
} else
throw Error("file '%s' is not a directory", path);
return {};
}
std::string MemorySourceAccessor::readLink(const CanonPath & path)
{
auto * f = open(path, std::nullopt);
if (!f)
throw Error("file '%s' does not exist", path);
if (auto * s = std::get_if<File::Symlink>(&f->raw))
return s->target;
else
throw Error("file '%s' is not a symbolic link", path);
}
CanonPath MemorySourceAccessor::addFile(CanonPath path, std::string && contents)
{
auto * f = open(path, File { File::Regular {} });
if (!f)
throw Error("file '%s' cannot be made because some parent file is not a directory", path);
if (auto * r = std::get_if<File::Regular>(&f->raw))
r->contents = std::move(contents);
else
throw Error("file '%s' is not a regular file", path);
return path;
}
}

View file

@ -0,0 +1,74 @@
#include "source-accessor.hh"
#include "variant-wrapper.hh"
namespace nix {
/**
* An source accessor for an in-memory file system.
*/
struct MemorySourceAccessor : virtual SourceAccessor
{
/**
* In addition to being part of the implementation of
* `MemorySourceAccessor`, this has a side benefit of nicely
* defining what a "file system object" is in Nix.
*/
struct File {
struct Regular {
bool executable = false;
std::string contents;
GENERATE_CMP(Regular, me->executable, me->contents);
};
struct Directory {
using Name = std::string;
std::map<Name, File, std::less<>> contents;
GENERATE_CMP(Directory, me->contents);
};
struct Symlink {
std::string target;
GENERATE_CMP(Symlink, me->target);
};
using Raw = std::variant<Regular, Directory, Symlink>;
Raw raw;
MAKE_WRAPPER_CONSTRUCTOR(File);
GENERATE_CMP(File, me->raw);
Stat lstat() const;
};
File root { File::Directory {} };
GENERATE_CMP(MemorySourceAccessor, me->root);
std::string readFile(const CanonPath & path) override;
bool pathExists(const CanonPath & path) override;
std::optional<Stat> maybeLstat(const CanonPath & path) override;
DirEntries readDirectory(const CanonPath & path) override;
std::string readLink(const CanonPath & path) override;
/**
* @param create If present, create this file and any parent directories
* that are needed.
*
* Return null if
*
* - `create = false`: File does not exist.
*
* - `create = true`: some parent file was not a dir, so couldn't
* look/create inside.
*/
File * open(const CanonPath & path, std::optional<File> create);
CanonPath addFile(CanonPath path, std::string && contents);
};
}