Skip to content

Commit

Permalink
Factor out MemorySourceAccessor, implement missing features
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Ericson2314 and edolstra committed Nov 4, 2023
1 parent 8e222fb commit 9b880e3
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/libfetchers/input-accessor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct SourcePath;
class StorePath;
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
Expand Down
44 changes: 6 additions & 38 deletions src/libfetchers/memory-input-accessor.cc
Original file line number Diff line number Diff line change
@@ -1,48 +1,16 @@
#include "memory-input-accessor.hh"
#include "memory-source-accessor.hh"

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
{
files.emplace(path, std::move(contents));

return {ref(shared_from_this()), std::move(path)};
return {
ref(shared_from_this()),
MemorySourceAccessor::addFile(path, std::move(contents))
};
}
};

Expand Down
124 changes: 124 additions & 0 deletions src/libutil/memory-source-accessor.cc
Original file line number Diff line number Diff line change
@@ -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;
}

}
74 changes: 74 additions & 0 deletions src/libutil/memory-source-accessor.hh
Original file line number Diff line number Diff line change
@@ -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);
};

}

0 comments on commit 9b880e3

Please sign in to comment.