aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/common-eval-args.cc26
-rw-r--r--src/libexpr/eval-settings.hh48
-rw-r--r--src/libfetchers/fetch-settings.cc26
-rw-r--r--src/libfetchers/fetch-settings.hh3
-rw-r--r--src/libmain/loggers.cc2
-rw-r--r--src/libstore/globals.cc30
-rw-r--r--src/libstore/globals.hh3
-rw-r--r--src/libstore/local-store.cc2
-rw-r--r--src/libstore/lock.cc12
-rw-r--r--src/libstore/nar-accessor.cc33
-rw-r--r--src/libstore/store-api.cc46
-rw-r--r--src/libutil/archive.cc156
-rw-r--r--src/libutil/archive.hh72
-rw-r--r--src/libutil/file-system.cc14
-rw-r--r--src/libutil/file-system.hh9
15 files changed, 354 insertions, 128 deletions
diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc
index 9a7c30d57..cbb7edbdd 100644
--- a/src/libcmd/common-eval-args.cc
+++ b/src/libcmd/common-eval-args.cc
@@ -9,8 +9,24 @@
#include "store-api.hh"
#include "command.hh"
+#include <regex>
+
namespace nix {
+static std::regex const identifierRegex("^[A-Za-z_][A-Za-z0-9_'-]*$");
+static void warnInvalidNixIdentifier(const std::string & name)
+{
+ std::smatch match;
+ if (!std::regex_match(name, match, identifierRegex)) {
+ warn("This Nix invocation specifies a value for argument '%s' which isn't a valid \
+Nix identifier. The project is considering to drop support for this \
+or to require quotes around args that aren't valid Nix identifiers. \
+If you depend on this behvior, please reach out in \
+https://git.lix.systems/lix-project/lix/issues/496 so we can discuss \
+your use-case.", name);
+ }
+}
+
MixEvalArgs::MixEvalArgs()
{
addFlag({
@@ -18,7 +34,10 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "expr"},
- .handler = {[&](std::string name, std::string expr) { autoArgs[name] = 'E' + expr; }}
+ .handler = {[&](std::string name, std::string expr) {
+ warnInvalidNixIdentifier(name);
+ autoArgs[name] = 'E' + expr;
+ }}
});
addFlag({
@@ -26,7 +45,10 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the string *string* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "string"},
- .handler = {[&](std::string name, std::string s) { autoArgs[name] = 'S' + s; }},
+ .handler = {[&](std::string name, std::string s) {
+ warnInvalidNixIdentifier(name);
+ autoArgs[name] = 'S' + s;
+ }},
});
addFlag({
diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh
index 444e298a0..f7ad2d786 100644
--- a/src/libexpr/eval-settings.hh
+++ b/src/libexpr/eval-settings.hh
@@ -185,6 +185,54 @@ struct EvalSettings : Config
else
{ }
```
+
+ Here's a more elaborate `repl-overlay`, which provides the following
+ variables:
+ - The original, unmodified variables are aliased to `original`.
+ - `legacyPackages.${system}` (if it exists) or `packages.${system}`
+ (otherwise) is aliased to `pkgs`.
+ - All attribute set variables with a `${system}` attribute are
+ abbreviated in the same manner; e.g. `devShells.${system}` is
+ shortened to `devShells`.
+
+ For example, the following attribute set:
+
+ ```nix
+ info: final: attrs: let
+ # Equivalent to nixpkgs `lib.optionalAttrs`.
+ optionalAttrs = predicate: attrs:
+ if predicate
+ then attrs
+ else {};
+
+ # If `attrs.${oldName}.${info.currentSystem}` exists, alias `${newName}` to
+ # it.
+ collapseRenamed = oldName: newName:
+ optionalAttrs (builtins.hasAttr oldName attrs
+ && builtins.hasAttr info.currentSystem attrs.${oldName})
+ {
+ ${newName} = attrs.${oldName}.${info.currentSystem};
+ };
+
+ # Alias `attrs.${oldName}.${info.currentSystem} to `${newName}`.
+ collapse = name: collapseRenamed name name;
+
+ # Alias all `attrs` keys with an `${info.currentSystem}` attribute.
+ collapseAll =
+ builtins.foldl'
+ (prev: name: prev // collapse name)
+ {}
+ (builtins.attrNames attrs);
+ in
+ # Preserve the original bindings as `original`.
+ (optionalAttrs (! attrs ? original)
+ {
+ original = attrs;
+ })
+ // (collapseRenamed "packages" "pkgs")
+ // (collapseRenamed "legacyPackages" "pkgs")
+ // collapseAll
+ ```
)"};
};
diff --git a/src/libfetchers/fetch-settings.cc b/src/libfetchers/fetch-settings.cc
index 007f2725f..b278835ad 100644
--- a/src/libfetchers/fetch-settings.cc
+++ b/src/libfetchers/fetch-settings.cc
@@ -7,6 +7,32 @@
namespace nix {
+void to_json(nlohmann::json & j, const AcceptFlakeConfig & e)
+{
+ if (e == AcceptFlakeConfig::False) {
+ j = false;
+ } else if (e == AcceptFlakeConfig::Ask) {
+ j = "ask";
+ } else if (e == AcceptFlakeConfig::True) {
+ j = true;
+ } else {
+ abort();
+ }
+}
+
+void from_json(const nlohmann::json & j, AcceptFlakeConfig & e)
+{
+ if (j == false) {
+ e = AcceptFlakeConfig::False;
+ } else if (j == "ask") {
+ e = AcceptFlakeConfig::Ask;
+ } else if (j == true) {
+ e = AcceptFlakeConfig::True;
+ } else {
+ throw Error("Invalid accept-flake-config value '%s'", std::string(j));
+ }
+}
+
template<> AcceptFlakeConfig BaseSetting<AcceptFlakeConfig>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
if (str == "true") return AcceptFlakeConfig::True;
diff --git a/src/libfetchers/fetch-settings.hh b/src/libfetchers/fetch-settings.hh
index 93123463c..0bdc707ec 100644
--- a/src/libfetchers/fetch-settings.hh
+++ b/src/libfetchers/fetch-settings.hh
@@ -13,6 +13,9 @@ namespace nix {
enum class AcceptFlakeConfig { False, Ask, True };
+void to_json(nlohmann::json & j, const AcceptFlakeConfig & e);
+void from_json(const nlohmann::json & j, AcceptFlakeConfig & e);
+
struct FetchSettings : public Config
{
FetchSettings();
diff --git a/src/libmain/loggers.cc b/src/libmain/loggers.cc
index 8c3c4e355..46438280a 100644
--- a/src/libmain/loggers.cc
+++ b/src/libmain/loggers.cc
@@ -7,7 +7,7 @@ namespace nix {
LogFormat defaultLogFormat = LogFormat::raw;
LogFormat parseLogFormat(const std::string & logFormatStr) {
- if (logFormatStr == "raw" || getEnv("NIX_GET_COMPLETIONS"))
+ if (logFormatStr == "raw")
return LogFormat::raw;
else if (logFormatStr == "raw-with-logs")
return LogFormat::rawWithLogs;
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index c114e22dc..ffc2543ef 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -269,11 +269,31 @@ Path Settings::getDefaultSSLCertFile()
const std::string nixVersion = PACKAGE_VERSION;
-NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
- {SandboxMode::smEnabled, true},
- {SandboxMode::smRelaxed, "relaxed"},
- {SandboxMode::smDisabled, false},
-});
+void to_json(nlohmann::json & j, const SandboxMode & e)
+{
+ if (e == SandboxMode::smEnabled) {
+ j = true;
+ } else if (e == SandboxMode::smRelaxed) {
+ j = "relaxed";
+ } else if (e == SandboxMode::smDisabled) {
+ j = false;
+ } else {
+ abort();
+ }
+}
+
+void from_json(const nlohmann::json & j, SandboxMode & e)
+{
+ if (j == true) {
+ e = SandboxMode::smEnabled;
+ } else if (j == "relaxed") {
+ e = SandboxMode::smRelaxed;
+ } else if (j == false) {
+ e = SandboxMode::smDisabled;
+ } else {
+ throw Error("Invalid sandbox mode '%s'", std::string(j));
+ }
+}
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str, const ApplyConfigOptions & options) const
{
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 94aa60825..bfba6ab01 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -14,6 +14,9 @@ namespace nix {
typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
+void to_json(nlohmann::json & j, const SandboxMode & e);
+void from_json(const nlohmann::json & j, SandboxMode & e);
+
struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
{
MaxBuildJobsSetting(Config * options,
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index 49991e38a..8f92255f5 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -1216,7 +1216,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
bool narRead = false;
Finally cleanup = [&]() {
if (!narRead) {
- ParseSink sink;
+ NARParseVisitor sink;
try {
parseDump(sink, source);
} catch (...) {
diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc
index 05296757d..377499c0a 100644
--- a/src/libstore/lock.cc
+++ b/src/libstore/lock.cc
@@ -73,8 +73,16 @@ struct SimpleUserLock : UserLock
debug("trying user '%s'", i);
struct passwd * pw = getpwnam(i.c_str());
- if (!pw)
- throw Error("the user '%s' in the group '%s' does not exist", i, settings.buildUsersGroup);
+ if (!pw) {
+#ifdef __APPLE__
+#define APPLE_HINT "\n\nhint: this may be caused by an update to macOS Sequoia breaking existing Lix installations.\n" \
+ "See the macOS Sequoia page on the Lix wiki for detailed repair instructions: https://wiki.lix.systems/link/81"
+#else
+#define APPLE_HINT
+#endif
+ throw Error("the user '%s' in the group '%s' does not exist" APPLE_HINT, i, settings.buildUsersGroup);
+#undef APPLE_HINT
+ }
auto fnUserLock = fmt("%s/userpool/%s", settings.nixStateDir,pw->pw_uid);
diff --git a/src/libstore/nar-accessor.cc b/src/libstore/nar-accessor.cc
index f0dfcb19b..7600de6e7 100644
--- a/src/libstore/nar-accessor.cc
+++ b/src/libstore/nar-accessor.cc
@@ -2,6 +2,7 @@
#include "archive.hh"
#include <map>
+#include <memory>
#include <stack>
#include <algorithm>
@@ -33,7 +34,7 @@ struct NarAccessor : public FSAccessor
NarMember root;
- struct NarIndexer : ParseSink, Source
+ struct NarIndexer : NARParseVisitor, Source
{
NarAccessor & acc;
Source & source;
@@ -44,11 +45,12 @@ struct NarAccessor : public FSAccessor
uint64_t pos = 0;
+ public:
NarIndexer(NarAccessor & 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(), '/');
while (parents.size() > level) parents.pop();
@@ -62,6 +64,8 @@ struct NarAccessor : public FSAccessor
auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member));
parents.push(&result.first->second);
}
+
+ return *parents.top();
}
void createDirectory(const Path & path) override
@@ -69,28 +73,17 @@ struct NarAccessor : public FSAccessor
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});
- }
-
- void closeRegularFile() override
- { }
+ auto & memb = createMember(path, {FSAccessor::Type::tRegular, false, 0, 0});
- void isExecutable() override
- {
- parents.top()->isExecutable = true;
- }
-
- void preallocateContents(uint64_t size) override
- {
assert(size <= std::numeric_limits<uint64_t>::max());
- parents.top()->size = (uint64_t) size;
- parents.top()->start = pos;
- }
+ memb.size = (uint64_t) size;
+ 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
{
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index cb0604233..50d392779 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -379,6 +379,48 @@ void Store::addMultipleToStore(
}
}
+namespace {
+/**
+ * If the NAR archive contains a single file at top-level, then save
+ * the contents of the file to `s`. Otherwise assert.
+ */
+struct RetrieveRegularNARVisitor : NARParseVisitor
+{
+ struct MyFileHandle : public FileHandle
+ {
+ Sink & sink;
+
+ void receiveContents(std::string_view data) override
+ {
+ 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
+ {
+ assert(false && "RetrieveRegularNARVisitor::createDirectory must not be called");
+ }
+
+ void createSymlink(const Path & path, const std::string & target) override
+ {
+ assert(false && "RetrieveRegularNARVisitor::createSymlink must not be called");
+ }
+};
+}
/*
The aim of this function is to compute in one pass the correct ValidPathInfo for
@@ -413,7 +455,7 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
/* Note that fileSink and unusualHashTee must be mutually exclusive, since
they both write to caHashSink. Note that that requisite is currently true
because the former is only used in the flat case. */
- RetrieveRegularNARSink fileSink { caHashSink };
+ RetrieveRegularNARVisitor fileSink { caHashSink };
TeeSink unusualHashTee { narHashSink, caHashSink };
auto & narSink = method == FileIngestionMethod::Recursive && hashAlgo != HashType::SHA256
@@ -429,7 +471,7 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
information to narSink. */
TeeSource tapped { fileSource, narSink };
- ParseSink blank;
+ NARParseVisitor blank;
auto & parseSink = method == FileIngestionMethod::Flat
? fileSink
: blank;
diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc
index d4da18f14..225483804 100644
--- a/src/libutil/archive.cc
+++ b/src/libutil/archive.cc
@@ -334,7 +334,7 @@ Generator<Entry> parse(Source & source)
}
-static WireFormatGenerator restore(ParseSink & sink, Generator<nar::Entry> nar)
+static WireFormatGenerator restore(NARParseVisitor & sink, Generator<nar::Entry> nar)
{
while (auto entry = nar.next()) {
co_yield std::visit(
@@ -347,16 +347,13 @@ static WireFormatGenerator restore(ParseSink & sink, Generator<nar::Entry> nar)
},
[&](nar::File f) {
return [](auto f, auto & sink) -> WireFormatGenerator {
- sink.createRegularFile(f.path);
- sink.preallocateContents(f.size);
- if (f.executable) {
- sink.isExecutable();
- }
+ auto handle = sink.createRegularFile(f.path, f.size, f.executable);
+
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;
}
- sink.closeRegularFile();
+ handle->close();
}(std::move(f), sink);
},
[&](nar::Symlink sl) {
@@ -377,12 +374,12 @@ static WireFormatGenerator restore(ParseSink & sink, Generator<nar::Entry> nar)
}
}
-WireFormatGenerator parseAndCopyDump(ParseSink & sink, Source & source)
+WireFormatGenerator parseAndCopyDump(NARParseVisitor & sink, Source & source)
{
return restore(sink, nar::parse(source));
}
-void parseDump(ParseSink & sink, Source & source)
+void parseDump(NARParseVisitor & sink, Source & source)
{
auto parser = parseAndCopyDump(sink, source);
while (parser.next()) {
@@ -390,11 +387,99 @@ void parseDump(ParseSink & sink, Source & source)
}
}
-struct RestoreSink : ParseSink
+/*
+ * Note [NAR restoration security]:
+ * It's *critical* that NAR restoration will never overwrite anything even if
+ * duplicate filenames are passed in. It is inevitable that not all NARs are
+ * fit to actually successfully restore to the target filesystem; errors may
+ * occur due to collisions, and this *must* cause the NAR to be rejected.
+ *
+ * Although the filenames are blocked from being *the same bytes* by a higher
+ * layer, filesystems have other ideas on every platform:
+ * - The store may be on a case-insensitive filesystem like APFS, ext4 with
+ * casefold directories, zfs with casesensitivity=insensitive
+ * - The store may be on a Unicode normalizing (or normalization-insensitive)
+ * filesystem like APFS (where files are looked up by
+ * hash(normalize(fname))), HFS+ (where file names are always normalized to
+ * approximately NFD), or zfs with normalization=formC, etc.
+ *
+ * It is impossible to know the version of Unicode being used by the underlying
+ * filesystem, thus it is *impossible* to stop these collisions.
+ *
+ * Overwriting files as a result of invalid NARs will cause a security bug like
+ * CppNix's CVE-2024-45593 (GHSA-h4vv-h3jq-v493)
+ */
+
+/**
+ * This code restores NARs from disk.
+ *
+ * See Note [NAR restoration security] for security invariants in this procedure.
+ *
+ */
+struct NARRestoreVisitor : NARParseVisitor
{
Path dstPath;
- AutoCloseFD fd;
+private:
+ class MyFileHandle : public FileHandle
+ {
+ AutoCloseFD fd;
+
+ MyFileHandle(AutoCloseFD && fd, uint64_t size, bool executable) : FileHandle(), fd(std::move(fd))
+ {
+ if (executable) {
+ makeExecutable();
+ }
+
+ maybePreallocateContents(size);
+ }
+
+ void makeExecutable()
+ {
+ struct stat st;
+ if (fstat(fd.get(), &st) == -1)
+ throw SysError("fstat");
+ if (fchmod(fd.get(), st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1)
+ throw SysError("fchmod");
+ }
+
+ void maybePreallocateContents(uint64_t len)
+ {
+ if (!archiveSettings.preallocateContents)
+ return;
+
+#if HAVE_POSIX_FALLOCATE
+ if (len) {
+ errno = posix_fallocate(fd.get(), 0, len);
+ /* Note that EINVAL may indicate that the underlying
+ filesystem doesn't support preallocation (e.g. on
+ OpenSolaris). Since preallocation is just an
+ optimisation, ignore it. */
+ if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS)
+ throw SysError("preallocating file of %1% bytes", len);
+ }
+#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
+ {
+ writeFull(fd.get(), data);
+ }
+
+ friend struct NARRestoreVisitor;
+ };
+
+public:
void createDirectory(const Path & path) override
{
Path p = dstPath + path;
@@ -402,49 +487,13 @@ struct RestoreSink : ParseSink
throw SysError("creating directory '%1%'", p);
};
- void createRegularFile(const Path & path) override
+ std::unique_ptr<FileHandle> createRegularFile(const Path & path, uint64_t size, bool executable) override
{
Path p = dstPath + path;
- fd = AutoCloseFD{open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666)};
+ AutoCloseFD 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
- {
- /* Call close explicitly to make sure the error is checked */
- fd.close();
- }
-
- void isExecutable() override
- {
- struct stat st;
- if (fstat(fd.get(), &st) == -1)
- throw SysError("fstat");
- if (fchmod(fd.get(), st.st_mode | (S_IXUSR | S_IXGRP | S_IXOTH)) == -1)
- throw SysError("fchmod");
- }
-
- void preallocateContents(uint64_t len) override
- {
- if (!archiveSettings.preallocateContents)
- return;
-
-#if HAVE_POSIX_FALLOCATE
- if (len) {
- errno = posix_fallocate(fd.get(), 0, len);
- /* Note that EINVAL may indicate that the underlying
- filesystem doesn't support preallocation (e.g. on
- OpenSolaris). Since preallocation is just an
- optimisation, ignore it. */
- if (errno && errno != EINVAL && errno != EOPNOTSUPP && errno != ENOSYS)
- throw SysError("preallocating file of %1% bytes", len);
- }
-#endif
- }
- void receiveContents(std::string_view data) override
- {
- writeFull(fd.get(), data);
+ return std::unique_ptr<MyFileHandle>(new MyFileHandle(std::move(fd), size, executable));
}
void createSymlink(const Path & path, const std::string & target) override
@@ -457,7 +506,7 @@ struct RestoreSink : ParseSink
void restorePath(const Path & path, Source & source)
{
- RestoreSink sink;
+ NARRestoreVisitor sink;
sink.dstPath = path;
parseDump(sink, source);
}
@@ -468,10 +517,9 @@ WireFormatGenerator copyNAR(Source & source)
// FIXME: if 'source' is the output of dumpPath() followed by EOF,
// we should just forward all data directly without parsing.
- static ParseSink parseSink; /* null sink; just parse the NAR */
+ static NARParseVisitor parseSink; /* null sink; just parse the NAR */
return parseAndCopyDump(parseSink, source);
}
-
}
diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh
index b34d06e3d..c633bee00 100644
--- a/src/libutil/archive.hh
+++ b/src/libutil/archive.hh
@@ -76,45 +76,47 @@ WireFormatGenerator dumpString(std::string_view s);
/**
* \todo Fix this API, it sucks.
+ * A visitor for NAR parsing that performs filesystem (or virtual-filesystem)
+ * actions to restore a NAR.
+ *
+ * Methods of this may arbitrarily fail due to filename collisions.
*/
-struct ParseSink
-{
- virtual void createDirectory(const Path & path) { };
-
- virtual void createRegularFile(const Path & path) { };
- virtual void closeRegularFile() { };
- 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) { };
-};
-
-/**
- * If the NAR archive contains a single file at top-level, then save
- * the contents of the file to `s`. Otherwise barf.
- */
-struct RetrieveRegularNARSink : ParseSink
+struct NARParseVisitor
{
- bool regular = true;
- Sink & sink;
-
- RetrieveRegularNARSink(Sink & sink) : sink(sink) { }
-
- void createDirectory(const Path & path) override
+ /**
+ * A type-erased file handle specific to this particular NARParseVisitor.
+ */
+ struct FileHandle
{
- regular = false;
- }
-
- void receiveContents(std::string_view data) override
+ FileHandle() {}
+ FileHandle(FileHandle const &) = delete;
+ FileHandle & operator=(FileHandle &) = delete;
+
+ /** Puts one block of data into the file */
+ virtual void receiveContents(std::string_view data) { }
+
+ /**
+ * 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)
{
- sink(data);
+ return std::make_unique<FileHandle>();
}
- void createSymlink(const Path & path, const std::string & target) override
- {
- regular = false;
- }
+ virtual void createSymlink(const Path & path, const std::string & target) { }
};
namespace nar {
@@ -160,8 +162,8 @@ Generator<Entry> parse(Source & source);
}
-WireFormatGenerator parseAndCopyDump(ParseSink & sink, Source & source);
-void parseDump(ParseSink & sink, Source & source);
+WireFormatGenerator parseAndCopyDump(NARParseVisitor & sink, Source & source);
+void parseDump(NARParseVisitor & sink, Source & source);
void restorePath(const Path & path, Source & source);
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
index 8c69c9864..1d3eba58f 100644
--- a/src/libutil/file-system.cc
+++ b/src/libutil/file-system.cc
@@ -18,15 +18,19 @@ namespace fs = std::filesystem;
namespace nix {
+Path getCwd() {
+ char buf[PATH_MAX];
+ if (!getcwd(buf, sizeof(buf))) {
+ throw SysError("cannot get cwd");
+ }
+ return Path(buf);
+}
+
Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
{
if (path.empty() || path[0] != '/') {
if (!dir) {
- char buf[PATH_MAX];
- if (!getcwd(buf, sizeof(buf))) {
- throw SysError("cannot get cwd");
- }
- path = concatStrings(buf, "/", path);
+ path = concatStrings(getCwd(), "/", path);
} else {
path = concatStrings(*dir, "/", path);
}
diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh
index 0a54d1a3b..2547c63b5 100644
--- a/src/libutil/file-system.hh
+++ b/src/libutil/file-system.hh
@@ -30,6 +30,13 @@ struct Sink;
struct Source;
/**
+ * Get the current working directory.
+ *
+ * Throw an error if the current directory cannot get got.
+ */
+Path getCwd();
+
+/**
* @return An absolutized path, resolving paths relative to the
* specified directory, or the current directory otherwise. The path
* is also canonicalised.
@@ -203,7 +210,7 @@ inline Paths createDirs(PathView path)
}
/**
- * Create a symlink.
+ * Create a symlink. Throws if the symlink exists.
*/
void createSymlink(const Path & target, const Path & link);