Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No symlink support.

Maybe the source accessor virtual methods should not even be responsible for that behavior. Or at least, symlink resolution should be implemented once, as a mix-in or an adapter.

(Also applies to other methods)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I actually think this is right and its PosixSourceAccessor that needs to be fixed.

Copy link
Member Author

@Ericson2314 Ericson2314 Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#9306 partially fixes PosixSourceAccesor and documents this in the methods.


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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps:

Suggested change
struct File {
struct FileSystemObject {

I don't like the idea that a directory would be a 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);
};

}