aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libcmd/repl.cc24
-rw-r--r--src/libexpr/eval.cc38
-rw-r--r--src/libexpr/flake/lockfile.cc5
-rw-r--r--src/libexpr/flake/lockfile.hh3
-rw-r--r--src/libexpr/nixexpr.cc42
-rw-r--r--src/libexpr/print.cc78
-rw-r--r--src/libexpr/print.hh48
-rw-r--r--src/libstore/derivations.cc9
-rw-r--r--src/libstore/derived-path.cc38
-rw-r--r--src/libstore/derived-path.hh26
-rw-r--r--src/libstore/dummy-store.cc3
-rw-r--r--src/libstore/globals.hh20
-rw-r--r--src/libstore/legacy-ssh-store.cc3
-rw-r--r--src/libstore/remote-store.cc4
-rw-r--r--src/libstore/sqlite.cc9
-rw-r--r--src/libstore/sqlite.hh6
-rw-r--r--src/libstore/store-api.hh3
-rw-r--r--src/libstore/tests/derived-path.cc8
-rw-r--r--src/libutil/compression.cc6
-rw-r--r--src/libutil/compression.hh2
-rw-r--r--src/libutil/config.cc20
-rw-r--r--src/libutil/config.hh19
-rw-r--r--src/libutil/experimental-features.cc231
-rw-r--r--src/libutil/experimental-features.hh37
-rw-r--r--src/libutil/hash.cc2
-rw-r--r--src/libutil/hash.hh2
-rw-r--r--src/libutil/serialise.cc45
-rw-r--r--src/libutil/serialise.hh14
-rw-r--r--src/libutil/tests/config.cc48
-rw-r--r--src/libutil/util.hh10
-rw-r--r--src/nix/daemon.cc215
-rw-r--r--src/nix/main.cc5
-rw-r--r--src/nix/nix.md14
33 files changed, 809 insertions, 228 deletions
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 57848a5d3..80c08bf1c 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -40,6 +40,7 @@ extern "C" {
#include "markdown.hh"
#include "local-fs-store.hh"
#include "progress-bar.hh"
+#include "print.hh"
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
@@ -425,6 +426,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
}
+// FIXME: DRY and match or use the parser
static bool isVarName(std::string_view s)
{
if (s.size() == 0) return false;
@@ -894,17 +896,6 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
}
-std::ostream & printStringValue(std::ostream & str, const char * string) {
- str << "\"";
- for (const char * i = string; *i; i++)
- if (*i == '\"' || *i == '\\') str << "\\" << *i;
- else if (*i == '\n') str << "\\n";
- else if (*i == '\r') str << "\\r";
- else if (*i == '\t') str << "\\t";
- else str << *i;
- str << "\"";
- return str;
-}
// FIXME: lot of cut&paste from Nix's eval.cc.
@@ -922,12 +913,14 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
break;
case nBool:
- str << ANSI_CYAN << (v.boolean ? "true" : "false") << ANSI_NORMAL;
+ str << ANSI_CYAN;
+ printLiteralBool(str, v.boolean);
+ str << ANSI_NORMAL;
break;
case nString:
str << ANSI_WARNING;
- printStringValue(str, v.string.s);
+ printLiteralString(str, v.string.s);
str << ANSI_NORMAL;
break;
@@ -964,10 +957,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
sorted.emplace(state->symbols[i.name], i.value);
for (auto & i : sorted) {
- if (isVarName(i.first))
- str << i.first;
- else
- printStringValue(str, i.first.c_str());
+ printAttributeName(str, i.first);
str << " = ";
if (seen.count(i.second))
str << "«repeated»";
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 7f2065656..6668add8c 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -9,6 +9,7 @@
#include "filetransfer.hh"
#include "function-trace.hh"
#include "profiles.hh"
+#include "print.hh"
#include <algorithm>
#include <chrono>
@@ -104,18 +105,10 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
str << integer;
break;
case tBool:
- str << (boolean ? "true" : "false");
+ printLiteralBool(str, boolean);
break;
case tString:
- str << "\"";
- for (const char * i = string.s; *i; i++)
- if (*i == '\"' || *i == '\\') str << "\\" << *i;
- else if (*i == '\n') str << "\\n";
- else if (*i == '\r') str << "\\r";
- else if (*i == '\t') str << "\\t";
- else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
- else str << *i;
- str << "\"";
+ printLiteralString(str, string.s);
break;
case tPath:
str << path; // !!! escaping?
@@ -344,6 +337,22 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
}
}
+#if HAVE_BOEHMGC
+/* Disable GC while this object lives. Used by CoroutineContext.
+ *
+ * Boehm keeps a count of GC_disable() and GC_enable() calls,
+ * and only enables GC when the count matches.
+ */
+class BoehmDisableGC {
+public:
+ BoehmDisableGC() {
+ GC_disable();
+ };
+ ~BoehmDisableGC() {
+ GC_enable();
+ };
+};
+#endif
static bool gcInitialised = false;
@@ -368,6 +377,15 @@ void initGC()
StackAllocator::defaultAllocator = &boehmGCStackAllocator;
+
+#if NIX_BOEHM_PATCH_VERSION != 1
+ printTalkative("Unpatched BoehmGC, disabling GC inside coroutines");
+ /* Used to disable GC when entering coroutines on macOS */
+ create_coro_gc_hook = []() -> std::shared_ptr<void> {
+ return std::make_shared<BoehmDisableGC>();
+ };
+#endif
+
/* Set the initial heap size to something fairly big (25% of
physical RAM, up to a maximum of 384 MiB) so that in most cases
we don't need to garbage collect at all. (Collection has a
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index a74e68c9c..ba2fd46f0 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -234,6 +234,11 @@ bool LockFile::operator ==(const LockFile & other) const
return toJSON() == other.toJSON();
}
+bool LockFile::operator !=(const LockFile & other) const
+{
+ return !(*this == other);
+}
+
InputPath parseInputPath(std::string_view s)
{
InputPath path;
diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh
index 0ac731b5d..ba4c0c848 100644
--- a/src/libexpr/flake/lockfile.hh
+++ b/src/libexpr/flake/lockfile.hh
@@ -73,6 +73,9 @@ struct LockFile
std::optional<FlakeRef> isUnlocked() const;
bool operator ==(const LockFile & other) const;
+ // Needed for old gcc versions that don't synthesize it (like gcc 8.2.2
+ // that is still the default on aarch64-linux)
+ bool operator !=(const LockFile & other) const;
std::shared_ptr<Node> findInput(const InputPath & path);
diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc
index eb6f062b4..1557cbbeb 100644
--- a/src/libexpr/nixexpr.cc
+++ b/src/libexpr/nixexpr.cc
@@ -3,6 +3,7 @@
#include "eval.hh"
#include "symbol-table.hh"
#include "util.hh"
+#include "print.hh"
#include <cstdlib>
@@ -60,45 +61,12 @@ Pos::operator std::shared_ptr<AbstractPos>() const
return pos;
}
-/* Displaying abstract syntax trees. */
-
-static void showString(std::ostream & str, std::string_view s)
-{
- str << '"';
- for (auto c : s)
- if (c == '"' || c == '\\' || c == '$') str << "\\" << c;
- else if (c == '\n') str << "\\n";
- else if (c == '\r') str << "\\r";
- else if (c == '\t') str << "\\t";
- else str << c;
- str << '"';
-}
-
+// FIXME: remove, because *symbols* are abstract and do not have a single
+// textual representation; see printIdentifier()
std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
{
std::string_view s = symbol;
-
- if (s.empty())
- str << "\"\"";
- else if (s == "if") // FIXME: handle other keywords
- str << '"' << s << '"';
- else {
- char c = s[0];
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
- showString(str, s);
- return str;
- }
- for (auto c : s)
- if (!((c >= 'a' && c <= 'z') ||
- (c >= 'A' && c <= 'Z') ||
- (c >= '0' && c <= '9') ||
- c == '_' || c == '\'' || c == '-')) {
- showString(str, s);
- return str;
- }
- str << s;
- }
- return str;
+ return printIdentifier(str, s);
}
void Expr::show(const SymbolTable & symbols, std::ostream & str) const
@@ -118,7 +86,7 @@ void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
{
- showString(str, s);
+ printLiteralString(str, s);
}
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
diff --git a/src/libexpr/print.cc b/src/libexpr/print.cc
new file mode 100644
index 000000000..d08672cfc
--- /dev/null
+++ b/src/libexpr/print.cc
@@ -0,0 +1,78 @@
+#include "print.hh"
+
+namespace nix {
+
+std::ostream &
+printLiteralString(std::ostream & str, const std::string_view string)
+{
+ str << "\"";
+ for (auto i = string.begin(); i != string.end(); ++i) {
+ if (*i == '\"' || *i == '\\') str << "\\" << *i;
+ else if (*i == '\n') str << "\\n";
+ else if (*i == '\r') str << "\\r";
+ else if (*i == '\t') str << "\\t";
+ else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
+ else str << *i;
+ }
+ str << "\"";
+ return str;
+}
+
+std::ostream &
+printLiteralBool(std::ostream & str, bool boolean)
+{
+ str << (boolean ? "true" : "false");
+ return str;
+}
+
+std::ostream &
+printIdentifier(std::ostream & str, std::string_view s) {
+ if (s.empty())
+ str << "\"\"";
+ else if (s == "if") // FIXME: handle other keywords
+ str << '"' << s << '"';
+ else {
+ char c = s[0];
+ if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
+ printLiteralString(str, s);
+ return str;
+ }
+ for (auto c : s)
+ if (!((c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ c == '_' || c == '\'' || c == '-')) {
+ printLiteralString(str, s);
+ return str;
+ }
+ str << s;
+ }
+ return str;
+}
+
+// FIXME: keywords
+static bool isVarName(std::string_view s)
+{
+ if (s.size() == 0) return false;
+ char c = s[0];
+ if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false;
+ for (auto & i : s)
+ if (!((i >= 'a' && i <= 'z') ||
+ (i >= 'A' && i <= 'Z') ||
+ (i >= '0' && i <= '9') ||
+ i == '_' || i == '-' || i == '\''))
+ return false;
+ return true;
+}
+
+std::ostream &
+printAttributeName(std::ostream & str, std::string_view name) {
+ if (isVarName(name))
+ str << name;
+ else
+ printLiteralString(str, name);
+ return str;
+}
+
+
+}
diff --git a/src/libexpr/print.hh b/src/libexpr/print.hh
new file mode 100644
index 000000000..f9cfc3964
--- /dev/null
+++ b/src/libexpr/print.hh
@@ -0,0 +1,48 @@
+#pragma once
+/**
+ * @file
+ * @brief Common printing functions for the Nix language
+ *
+ * While most types come with their own methods for printing, they share some
+ * functions that are placed here.
+ */
+
+#include <iostream>
+
+namespace nix {
+ /**
+ * Print a string as a Nix string literal.
+ *
+ * Quotes and fairly minimal escaping are added.
+ *
+ * @param s The logical string
+ */
+ std::ostream & printLiteralString(std::ostream & o, std::string_view s);
+ inline std::ostream & printLiteralString(std::ostream & o, const char * s) {
+ return printLiteralString(o, std::string_view(s));
+ }
+ inline std::ostream & printLiteralString(std::ostream & o, const std::string & s) {
+ return printLiteralString(o, std::string_view(s));
+ }
+
+ /** Print `true` or `false`. */
+ std::ostream & printLiteralBool(std::ostream & o, bool b);
+
+ /**
+ * Print a string as an attribute name in the Nix expression language syntax.
+ *
+ * Prints a quoted string if necessary.
+ */
+ std::ostream & printAttributeName(std::ostream & o, std::string_view s);
+
+ /**
+ * Print a string as an identifier in the Nix expression language syntax.
+ *
+ * FIXME: "identifier" is ambiguous. Identifiers do not have a single
+ * textual representation. They can be used in variable references,
+ * let bindings, left-hand sides or attribute names in a select
+ * expression, or something else entirely, like JSON. Use one of the
+ * `print*` functions instead.
+ */
+ std::ostream & printIdentifier(std::ostream & o, std::string_view s);
+}
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index abdfb1978..7eb5cd275 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -313,6 +313,15 @@ Derivation parseDerivation(const Store & store, std::string && s, std::string_vi
}
+/**
+ * Print a derivation string literal to an `std::string`.
+ *
+ * This syntax does not generalize to the expression language, which needs to
+ * escape `$`.
+ *
+ * @param res Where to print to
+ * @param s Which logical string to print
+ */
static void printString(std::string & res, std::string_view s)
{
boost::container::small_vector<char, 64 * 1024> buffer;
diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc
index e5f0f1b33..9a2ffda39 100644
--- a/src/libstore/derived-path.cc
+++ b/src/libstore/derived-path.cc
@@ -62,15 +62,31 @@ std::string DerivedPath::Opaque::to_string(const Store & store) const
std::string DerivedPath::Built::to_string(const Store & store) const
{
return store.printStorePath(drvPath)
- + "!"
+ + '^'
+ + outputs.to_string();
+}
+
+std::string DerivedPath::Built::to_string_legacy(const Store & store) const
+{
+ return store.printStorePath(drvPath)
+ + '!'
+ outputs.to_string();
}
std::string DerivedPath::to_string(const Store & store) const
{
- return std::visit(
- [&](const auto & req) { return req.to_string(store); },
- this->raw());
+ return std::visit(overloaded {
+ [&](const DerivedPath::Built & req) { return req.to_string(store); },
+ [&](const DerivedPath::Opaque & req) { return req.to_string(store); },
+ }, this->raw());
+}
+
+std::string DerivedPath::to_string_legacy(const Store & store) const
+{
+ return std::visit(overloaded {
+ [&](const DerivedPath::Built & req) { return req.to_string_legacy(store); },
+ [&](const DerivedPath::Opaque & req) { return req.to_string(store); },
+ }, this->raw());
}
@@ -87,14 +103,24 @@ DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_vi
};
}
-DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
+static inline DerivedPath parseWith(const Store & store, std::string_view s, std::string_view separator)
{
- size_t n = s.find("!");
+ size_t n = s.find(separator);
return n == s.npos
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
: (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1));
}
+DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
+{
+ return parseWith(store, s, "^");
+}
+
+DerivedPath DerivedPath::parseLegacy(const Store & store, std::string_view s)
+{
+ return parseWith(store, s, "!");
+}
+
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
{
RealisedPath::Set res;
diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh
index 2155776b1..5f7acbebc 100644
--- a/src/libstore/derived-path.hh
+++ b/src/libstore/derived-path.hh
@@ -48,8 +48,18 @@ struct DerivedPathBuilt {
StorePath drvPath;
OutputsSpec outputs;
+ /**
+ * Uses `^` as the separator
+ */
std::string to_string(const Store & store) const;
- static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view);
+ /**
+ * Uses `!` as the separator
+ */
+ std::string to_string_legacy(const Store & store) const;
+ /**
+ * The caller splits on the separator, so it works for both variants.
+ */
+ static DerivedPathBuilt parse(const Store & store, std::string_view drvPath, std::string_view outputs);
nlohmann::json toJSON(ref<Store> store) const;
GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs);
@@ -81,8 +91,22 @@ struct DerivedPath : _DerivedPathRaw {
return static_cast<const Raw &>(*this);
}
+ /**
+ * Uses `^` as the separator
+ */
std::string to_string(const Store & store) const;
+ /**
+ * Uses `!` as the separator
+ */
+ std::string to_string_legacy(const Store & store) const;
+ /**
+ * Uses `^` as the separator
+ */
static DerivedPath parse(const Store & store, std::string_view);
+ /**
+ * Uses `!` as the separator
+ */
+ static DerivedPath parseLegacy(const Store & store, std::string_view);
};
/**
diff --git a/src/libstore/dummy-store.cc b/src/libstore/dummy-store.cc
index ae2777d0c..74d6ed3b5 100644
--- a/src/libstore/dummy-store.cc
+++ b/src/libstore/dummy-store.cc
@@ -71,6 +71,9 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
void queryRealisationUncached(const DrvOutput &,
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
{ callback(nullptr); }
+
+ virtual ref<FSAccessor> getFSAccessor() override
+ { unsupported("getFSAccessor"); }
};
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index c29ad5f89..d6c5d437a 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -328,16 +328,6 @@ public:
users in `build-users-group`.
UIDs are allocated starting at 872415232 (0x34000000) on Linux and 56930 on macOS.
-
- > **Warning**
- > This is an experimental feature.
-
- To enable it, add the following to [`nix.conf`](#):
-
- ```
- extra-experimental-features = auto-allocate-uids
- auto-allocate-uids = true
- ```
)"};
Setting<uint32_t> startId{this,
@@ -367,16 +357,6 @@ public:
Cgroups are required and enabled automatically for derivations
that require the `uid-range` system feature.
-
- > **Warning**
- > This is an experimental feature.
-
- To enable it, add the following to [`nix.conf`](#):
-
- ```
- extra-experimental-features = cgroups
- use-cgroups = true
- ```
)"};
#endif
diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc
index eb471d8fc..d2ddbbe5f 100644
--- a/src/libstore/legacy-ssh-store.cc
+++ b/src/libstore/legacy-ssh-store.cc
@@ -342,6 +342,9 @@ public:
void ensurePath(const StorePath & path) override
{ unsupported("ensurePath"); }
+ virtual ref<FSAccessor> getFSAccessor() override
+ { unsupported("getFSAccessor"); }
+
void computeFSClosure(const StorePathSet & paths,
StorePathSet & out, bool flipDirection = false,
bool includeOutputs = false, bool includeDerivers = false) override
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index e128c3a29..b862902d1 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -90,12 +90,12 @@ void write(const Store & store, Sink & out, const ContentAddress & ca)
DerivedPath read(const Store & store, Source & from, Phantom<DerivedPath> _)
{
auto s = readString(from);
- return DerivedPath::parse(store, s);
+ return DerivedPath::parseLegacy(store, s);
}
void write(const Store & store, Sink & out, const DerivedPath & req)
{
- out << req.to_string(store);
+ out << req.to_string_legacy(store);
}
diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc
index 871f2f3be..df334c23c 100644
--- a/src/libstore/sqlite.cc
+++ b/src/libstore/sqlite.cc
@@ -239,14 +239,11 @@ SQLiteTxn::~SQLiteTxn()
}
}
-void handleSQLiteBusy(const SQLiteBusy & e)
+void handleSQLiteBusy(const SQLiteBusy & e, time_t & nextWarning)
{
- static std::atomic<time_t> lastWarned{0};
-
time_t now = time(0);
-
- if (now > lastWarned + 10) {
- lastWarned = now;
+ if (now > nextWarning) {
+ nextWarning = now + 10;
logWarning({
.msg = hintfmt(e.what())
});
diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh
index b735838ec..6e14852cb 100644
--- a/src/libstore/sqlite.hh
+++ b/src/libstore/sqlite.hh
@@ -139,7 +139,7 @@ protected:
MakeError(SQLiteBusy, SQLiteError);
-void handleSQLiteBusy(const SQLiteBusy & e);
+void handleSQLiteBusy(const SQLiteBusy & e, time_t & nextWarning);
/**
* Convenience function for retrying a SQLite transaction when the
@@ -148,11 +148,13 @@ void handleSQLiteBusy(const SQLiteBusy & e);
template<typename T, typename F>
T retrySQLite(F && fun)
{
+ time_t nextWarning = time(0) + 1;
+
while (true) {
try {
return fun();
} catch (SQLiteBusy & e) {
- handleSQLiteBusy(e);
+ handleSQLiteBusy(e, nextWarning);
}
}
}
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 3cb48bff5..5bee272bf 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -678,8 +678,7 @@ public:
/**
* @return An object to access files in the Nix store.
*/
- virtual ref<FSAccessor> getFSAccessor()
- { unsupported("getFSAccessor"); }
+ virtual ref<FSAccessor> getFSAccessor() = 0;
/**
* Repair the contents of the given path by redownloading it using
diff --git a/src/libstore/tests/derived-path.cc b/src/libstore/tests/derived-path.cc
index d1ac2c5e7..e6d32dbd0 100644
--- a/src/libstore/tests/derived-path.cc
+++ b/src/libstore/tests/derived-path.cc
@@ -53,6 +53,14 @@ TEST_F(DerivedPathTest, force_init)
RC_GTEST_FIXTURE_PROP(
DerivedPathTest,
+ prop_legacy_round_rip,
+ (const DerivedPath & o))
+{
+ RC_ASSERT(o == DerivedPath::parseLegacy(*store, o.to_string_legacy(*store)));
+}
+
+RC_GTEST_FIXTURE_PROP(
+ DerivedPathTest,
prop_round_rip,
(const DerivedPath & o))
{
diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc
index 89180e7a7..ba0847cde 100644
--- a/src/libutil/compression.cc
+++ b/src/libutil/compression.cc
@@ -23,7 +23,7 @@ struct ChunkedCompressionSink : CompressionSink
{
uint8_t outbuf[32 * 1024];
- void write(std::string_view data) override
+ void writeUnbuffered(std::string_view data) override
{
const size_t CHUNK_SIZE = sizeof(outbuf) << 2;
while (!data.empty()) {
@@ -103,7 +103,7 @@ struct ArchiveCompressionSink : CompressionSink
throw Error(reason, archive_error_string(this->archive));
}
- void write(std::string_view data) override
+ void writeUnbuffered(std::string_view data) override
{
ssize_t result = archive_write_data(archive, data.data(), data.length());
if (result <= 0) check(result);
@@ -136,7 +136,7 @@ struct NoneSink : CompressionSink
warn("requested compression level '%d' not supported by compression method 'none'", level);
}
void finish() override { flush(); }
- void write(std::string_view data) override { nextSink(data); }
+ void writeUnbuffered(std::string_view data) override { nextSink(data); }
};
struct BrotliDecompressionSink : ChunkedCompressionSink
diff --git a/src/libutil/compression.hh b/src/libutil/compression.hh
index 3892831c2..4e53a7b3c 100644
--- a/src/libutil/compression.hh
+++ b/src/libutil/compression.hh
@@ -12,7 +12,7 @@ namespace nix {
struct CompressionSink : BufferedSink, FinishSink
{
using BufferedSink::operator ();
- using BufferedSink::write;
+ using BufferedSink::writeUnbuffered;
using FinishSink::finish;
};
diff --git a/src/libutil/config.cc b/src/libutil/config.cc
index 8d63536d6..a42f3a849 100644
--- a/src/libutil/config.cc
+++ b/src/libutil/config.cc
@@ -70,17 +70,10 @@ void AbstractConfig::reapplyUnknownSettings()
set(s.first, s.second);
}
-// Whether we should process the option. Excludes aliases, which are handled elsewhere, and disabled features.
-static bool applicable(const Config::SettingData & sd)
-{
- return !sd.isAlias
- && experimentalFeatureSettings.isEnabled(sd.setting->experimentalFeature);
-}
-
void Config::getSettings(std::map<std::string, SettingInfo> & res, bool overriddenOnly)
{
for (auto & opt : _settings)
- if (applicable(opt.second) && (!overriddenOnly || opt.second.setting->overridden))
+ if (!opt.second.isAlias && (!overriddenOnly || opt.second.setting->overridden))
res.emplace(opt.first, SettingInfo{opt.second.setting->to_string(), opt.second.setting->description});
}
@@ -154,7 +147,7 @@ nlohmann::json Config::toJSON()
{
auto res = nlohmann::json::object();
for (auto & s : _settings)
- if (applicable(s.second))
+ if (!s.second.isAlias)
res.emplace(s.first, s.second.setting->toJSON());
return res;
}
@@ -163,7 +156,7 @@ std::string Config::toKeyValue()
{
auto res = std::string();
for (auto & s : _settings)
- if (applicable(s.second))
+ if (s.second.isAlias)
res += fmt("%s = %s\n", s.first, s.second.setting->to_string());
return res;
}
@@ -171,9 +164,6 @@ std::string Config::toKeyValue()
void Config::convertToArgs(Args & args, const std::string & category)
{
for (auto & s : _settings) {
- /* We do include args for settings gated on disabled
- experimental-features. The args themselves however will also be
- gated on any experimental feature the underlying setting is. */
if (!s.second.isAlias)
s.second.setting->convertToArg(args, category);
}
@@ -201,6 +191,10 @@ std::map<std::string, nlohmann::json> AbstractSetting::toJSONObject()
std::map<std::string, nlohmann::json> obj;
obj.emplace("description", description);
obj.emplace("aliases", aliases);
+ if (experimentalFeature)
+ obj.emplace("experimentalFeature", *experimentalFeature);
+ else
+ obj.emplace("experimentalFeature", nullptr);
return obj;
}
diff --git a/src/libutil/config.hh b/src/libutil/config.hh
index 3c1d70294..162626791 100644
--- a/src/libutil/config.hh
+++ b/src/libutil/config.hh
@@ -371,8 +371,23 @@ extern GlobalConfig globalConfig;
struct ExperimentalFeatureSettings : Config {
- Setting<std::set<ExperimentalFeature>> experimentalFeatures{this, {}, "experimental-features",
- "Experimental Nix features to enable."};
+ Setting<std::set<ExperimentalFeature>> experimentalFeatures{
+ this, {}, "experimental-features",
+ R"(
+ Experimental features that are enabled.
+
+ Example:
+
+ ```
+ experimental-features = nix-command flakes
+ ```
+
+ The following experimental features are available:
+
+ {{#include experimental-features-shortlist.md}}
+
+ Experimental features are [further documented in the manual](@docroot@/contributing/experimental-features.md).
+ )"};
/**
* Check whether the given experimental feature is enabled.
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
index 58d762ebb..5b4418714 100644
--- a/src/libutil/experimental-features.cc
+++ b/src/libutil/experimental-features.cc
@@ -5,29 +5,209 @@
namespace nix {
-std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
- { Xp::CaDerivations, "ca-derivations" },
- { Xp::ImpureDerivations, "impure-derivations" },
- { Xp::Flakes, "flakes" },
- { Xp::NixCommand, "nix-command" },
- { Xp::RecursiveNix, "recursive-nix" },
- { Xp::NoUrlLiterals, "no-url-literals" },
- { Xp::FetchClosure, "fetch-closure" },
- { Xp::ReplFlake, "repl-flake" },
- { Xp::AutoAllocateUids, "auto-allocate-uids" },
- { Xp::Cgroups, "cgroups" },
- { Xp::DiscardReferences, "discard-references" },
+struct ExperimentalFeatureDetails
+{
+ ExperimentalFeature tag;
+ std::string_view name;
+ std::string_view description;
};
+constexpr std::array<ExperimentalFeatureDetails, 11> xpFeatureDetails = {{
+ {
+ .tag = Xp::CaDerivations,
+ .name = "ca-derivations",
+ .description = R"(
+ Allow derivations to be content-addressed in order to prevent
+ rebuilds when changes to the derivation do not result in changes to
+ the derivation's output. See
+ [__contentAddressed](@docroot@/language/advanced-attributes.md#adv-attr-__contentAddressed)
+ for details.
+ )",
+ },
+ {
+ .tag = Xp::ImpureDerivations,
+ .name = "impure-derivations",
+ .description = R"(
+ Allow derivations to produce non-fixed outputs by setting the
+ `__impure` derivation attribute to `true`. An impure derivation can
+ have differing outputs each time it is built.
+
+ Example:
+
+ ```
+ derivation {
+ name = "impure";
+ builder = /bin/sh;
+ __impure = true; # mark this derivation as impure
+ args = [ "-c" "read -n 10 random < /dev/random; echo $random > $out" ];
+ system = builtins.currentSystem;
+ }
+ ```
+
+ Each time this derivation is built, it can produce a different
+ output (as the builder outputs random bytes to `$out`). Impure
+ derivations also have access to the network, and only fixed-output
+ or other impure derivations can rely on impure derivations. Finally,
+ an impure derivation cannot also be
+ [content-addressed](#xp-feature-ca-derivations).
+ )",
+ },
+ {
+ .tag = Xp::Flakes,
+ .name = "flakes",
+ .description = R"(
+ Enable flakes. See the manual entry for [`nix
+ flake`](@docroot@/command-ref/new-cli/nix3-flake.md) for details.
+ )",
+ },
+ {
+ .tag = Xp::NixCommand,
+ .name = "nix-command",
+ .description = R"(
+ Enable the new `nix` subcommands. See the manual on
+ [`nix`](@docroot@/command-ref/new-cli/nix.md) for details.
+ )",
+ },
+ {
+ .tag = Xp::RecursiveNix,
+ .name = "recursive-nix",
+ .description = R"(
+ Allow derivation builders to call Nix, and thus build derivations
+ recursively.
+
+ Example:
+
+ ```
+ with import <nixpkgs> {};
+
+ runCommand "foo"
+ {
+ buildInputs = [ nix jq ];
+ NIX_PATH = "nixpkgs=${<nixpkgs>}";
+ }
+ ''
+ hello=$(nix-build -E '(import <nixpkgs> {}).hello.overrideDerivation (args: { name = "recursive-hello"; })')
+
+ mkdir -p $out/bin
+ ln -s $hello/bin/hello $out/bin/hello
+ ''
+ ```
+
+ An important restriction on recursive builders is disallowing
+ arbitrary substitutions. For example, running
+
+ ```
+ nix-store -r /nix/store/kmwd1hq55akdb9sc7l3finr175dajlby-hello-2.10
+ ```
+
+ in the above `runCommand` script would be disallowed, as this could
+ lead to derivations with hidden dependencies or breaking
+ reproducibility by relying on the current state of the Nix store. An
+ exception would be if
+ `/nix/store/kmwd1hq55akdb9sc7l3finr175dajlby-hello-2.10` were
+ already in the build inputs or built by a previous recursive Nix
+ call.
+ )",
+ },
+ {
+ .tag = Xp::NoUrlLiterals,
+ .name = "no-url-literals",
+ .description = R"(
+ Disallow unquoted URLs as part of the Nix language syntax. The Nix
+ language allows for URL literals, like so:
+
+ ```
+ $ nix repl
+ Welcome to Nix 2.15.0. Type :? for help.
+
+ nix-repl> http://foo
+ "http://foo"
+ ```
+
+ But enabling this experimental feature will cause the Nix parser to
+ throw an error when encountering a URL literal:
+
+ ```
+ $ nix repl --extra-experimental-features 'no-url-literals'
+ Welcome to Nix 2.15.0. Type :? for help.
+
+ nix-repl> http://foo
+ error: URL literals are disabled
+
+ at «string»:1:1:
+
+ 1| http://foo
+ | ^
+
+ ```
+
+ While this is currently an experimental feature, unquoted URLs are
+ being deprecated and their usage is discouraged.
+
+ The reason is that, as opposed to path literals, URLs have no
+ special properties that distinguish them from regular strings, URLs
+ containing parameters have to be quoted anyway, and unquoted URLs
+ may confuse external tooling.
+ )",
+ },
+ {
+ .tag = Xp::FetchClosure,
+ .name = "fetch-closure",
+ .description = R"(
+ Enable the use of the [`fetchClosure`](@docroot@/language/builtins.md#builtins-fetchClosure) built-in function in the Nix language.
+ )",
+ },
+ {
+ .tag = Xp::ReplFlake,
+ .name = "repl-flake",
+ .description = R"(
+ Allow passing [installables](@docroot@/command-ref/new-cli/nix.md#installables) to `nix repl`, making its interface consistent with the other experimental commands.
+ )",
+ },
+ {
+ .tag = Xp::AutoAllocateUids,
+ .name = "auto-allocate-uids",
+ .description = R"(
+ Allows Nix to automatically pick UIDs for builds, rather than creating
+ `nixbld*` user accounts. See the [`auto-allocate-uids`](#conf-auto-allocate-uids) setting for details.
+ )",
+ },
+ {
+ .tag = Xp::Cgroups,
+ .name = "cgroups",
+ .description = R"(
+ Allows Nix to execute builds inside cgroups. See
+ the [`use-cgroups`](#conf-use-cgroups) setting for details.
+ )",
+ },
+ {
+ .tag = Xp::DiscardReferences,
+ .name = "discard-references",
+ .description = R"(
+ Allow the use of the [`unsafeDiscardReferences`](@docroot@/language/advanced-attributes.html#adv-attr-unsafeDiscardReferences) attribute in derivations
+ that use [structured attributes](@docroot@/language/advanced-attributes.html#adv-attr-structuredAttrs). This disables scanning of outputs for
+ runtime dependencies.
+ )",
+ },
+}};
+
+static_assert(
+ []() constexpr {
+ for (auto [index, feature] : enumerate(xpFeatureDetails))
+ if (index != (size_t)feature.tag)
+ return false;
+ return true;
+ }(),
+ "array order does not match enum tag order");
+
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
{
using ReverseXpMap = std::map<std::string_view, ExperimentalFeature>;
- static auto reverseXpMap = []()
- {
+ static std::unique_ptr<ReverseXpMap> reverseXpMap = []() {
auto reverseXpMap = std::make_unique<ReverseXpMap>();
- for (auto & [feature, name] : stringifiedXpFeatures)
- (*reverseXpMap)[name] = feature;
+ for (auto & xpFeature : xpFeatureDetails)
+ (*reverseXpMap)[xpFeature.name] = xpFeature.tag;
return reverseXpMap;
}();
@@ -37,20 +217,27 @@ const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::str
return std::nullopt;
}
-std::string_view showExperimentalFeature(const ExperimentalFeature feature)
+std::string_view showExperimentalFeature(const ExperimentalFeature tag)
+{
+ assert((size_t)tag < xpFeatureDetails.size());
+ return xpFeatureDetails[(size_t)tag].name;
+}
+
+nlohmann::json documentExperimentalFeatures()
{
- const auto ret = get(stringifiedXpFeatures, feature);
- assert(ret);
- return *ret;
+ StringMap res;
+ for (auto & xpFeature : xpFeatureDetails)
+ res[std::string { xpFeature.name }] =
+ trim(stripIndentation(xpFeature.description));
+ return (nlohmann::json) res;
}
std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> & rawFeatures)
{
std::set<ExperimentalFeature> res;
- for (auto & rawFeature : rawFeatures) {
+ for (auto & rawFeature : rawFeatures)
if (auto feature = parseExperimentalFeature(rawFeature))
res.insert(*feature);
- }
return res;
}
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
index 5948ad7ad..8ef66263a 100644
--- a/src/libutil/experimental-features.hh
+++ b/src/libutil/experimental-features.hh
@@ -11,8 +11,9 @@ namespace nix {
/**
* The list of available experimental features.
*
- * If you update this, don’t forget to also change the map defining their
- * string representation in the corresponding `.cc` file.
+ * If you update this, don’t forget to also change the map defining
+ * their string representation and documentation in the corresponding
+ * `.cc` file as well.
*/
enum struct ExperimentalFeature
{
@@ -34,26 +35,52 @@ enum struct ExperimentalFeature
*/
using Xp = ExperimentalFeature;
+/**
+ * Parse an experimental feature (enum value) from its name. Experimental
+ * feature flag names are hyphenated and do not contain spaces.
+ */
const std::optional<ExperimentalFeature> parseExperimentalFeature(
const std::string_view & name);
+
+/**
+ * Show the name of an experimental feature. This is the opposite of
+ * parseExperimentalFeature().
+ */
std::string_view showExperimentalFeature(const ExperimentalFeature);
+/**
+ * Compute the documentation of all experimental features.
+ *
+ * See `doc/manual` for how this information is used.
+ */
+nlohmann::json documentExperimentalFeatures();
+
+/**
+ * Shorthand for `str << showExperimentalFeature(feature)`.
+ */
std::ostream & operator<<(
std::ostream & str,
const ExperimentalFeature & feature);
/**
- * Parse a set of strings to the corresponding set of experimental features,
- * ignoring (but warning for) any unkwown feature.
+ * Parse a set of strings to the corresponding set of experimental
+ * features, ignoring (but warning for) any unknown feature.
*/
std::set<ExperimentalFeature> parseFeatures(const std::set<std::string> &);
+/**
+ * An experimental feature was required for some (experimental)
+ * operation, but was not enabled.
+ */
class MissingExperimentalFeature : public Error
{
public:
+ /**
+ * The experimental feature that was required but not enabled.
+ */
ExperimentalFeature missingFeature;
- MissingExperimentalFeature(ExperimentalFeature);
+ MissingExperimentalFeature(ExperimentalFeature missingFeature);
};
/**
diff --git a/src/libutil/hash.cc b/src/libutil/hash.cc
index 02bddc8d9..2c36d9d94 100644
--- a/src/libutil/hash.cc
+++ b/src/libutil/hash.cc
@@ -343,7 +343,7 @@ HashSink::~HashSink()
delete ctx;
}
-void HashSink::write(std::string_view data)
+void HashSink::writeUnbuffered(std::string_view data)
{
bytes += data.size();
update(ht, *ctx, data);
diff --git a/src/libutil/hash.hh b/src/libutil/hash.hh
index be1fdba2a..ae3ee40f4 100644
--- a/src/libutil/hash.hh
+++ b/src/libutil/hash.hh
@@ -197,7 +197,7 @@ public:
HashSink(HashType ht);
HashSink(const HashSink & h);
~HashSink();
- void write(std::string_view data) override;
+ void writeUnbuffered(std::string_view data) override;
HashResult finish() override;
HashResult currentHash();
};
diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc
index 7476e6f6c..3d5121a19 100644
--- a/src/libutil/serialise.cc
+++ b/src/libutil/serialise.cc
@@ -20,7 +20,7 @@ void BufferedSink::operator () (std::string_view data)
buffer size. */
if (bufPos + data.size() >= bufSize) {
flush();
- write(data);
+ writeUnbuffered(data);
break;
}
/* Otherwise, copy the bytes to the buffer. Flush the buffer
@@ -38,7 +38,7 @@ void BufferedSink::flush()
if (bufPos == 0) return;
size_t n = bufPos;
bufPos = 0; // don't trigger the assert() in ~BufferedSink()
- write({buffer.get(), n});
+ writeUnbuffered({buffer.get(), n});
}
@@ -48,7 +48,7 @@ FdSink::~FdSink()
}
-void FdSink::write(std::string_view data)
+void FdSink::writeUnbuffered(std::string_view data)
{
written += data.size();
try {
@@ -186,6 +186,22 @@ static DefaultStackAllocator defaultAllocatorSingleton;
StackAllocator *StackAllocator::defaultAllocator = &defaultAllocatorSingleton;
+std::shared_ptr<void> (*create_coro_gc_hook)() = []() -> std::shared_ptr<void> {
+ return {};
+};
+
+/* This class is used for entry and exit hooks on coroutines */
+class CoroutineContext {
+ /* Disable GC when entering the coroutine without the boehm patch,
+ * since it doesn't find the main thread stack in this case.
+ * std::shared_ptr<void> performs type-erasure, so it will call the right
+ * deleter. */
+ const std::shared_ptr<void> coro_gc_hook = create_coro_gc_hook();
+public:
+ CoroutineContext() {};
+ ~CoroutineContext() {};
+};
+
std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
{
struct SourceToSink : FinishSink
@@ -206,7 +222,8 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
if (in.empty()) return;
cur = in;
- if (!coro)
+ if (!coro) {
+ CoroutineContext ctx;
coro = coro_t::push_type(VirtualStackAllocator{}, [&](coro_t::pull_type & yield) {
LambdaSource source([&](char *out, size_t out_len) {
if (cur.empty()) {
@@ -223,17 +240,24 @@ std::unique_ptr<FinishSink> sourceToSink(std::function<void(Source &)> fun)
});
fun(source);
});
+ }
if (!*coro) { abort(); }
- if (!cur.empty()) (*coro)(false);
+ if (!cur.empty()) {
+ CoroutineContext ctx;
+ (*coro)(false);
+ }
}
void finish() override
{
if (!coro) return;
if (!*coro) abort();
- (*coro)(true);
+ {
+ CoroutineContext ctx;
+ (*coro)(true);
+ }
if (*coro) abort();
}
};
@@ -264,18 +288,23 @@ std::unique_ptr<Source> sinkToSource(
size_t read(char * data, size_t len) override
{
- if (!coro)
+ if (!coro) {
+ CoroutineContext ctx;
coro = coro_t::pull_type(VirtualStackAllocator{}, [&](coro_t::push_type & yield) {
LambdaSink sink([&](std::string_view data) {
if (!data.empty()) yield(std::string(data));
});
fun(sink);
});
+ }
if (!*coro) { eof(); abort(); }
if (pos == cur.size()) {
- if (!cur.empty()) (*coro)();
+ if (!cur.empty()) {
+ CoroutineContext ctx;
+ (*coro)();
+ }
cur = coro->get();
pos = 0;
}
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index 2cf527023..333c254ea 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -53,7 +53,9 @@ struct BufferedSink : virtual Sink
void flush();
- virtual void write(std::string_view data) = 0;
+protected:
+
+ virtual void writeUnbuffered(std::string_view data) = 0;
};
@@ -133,7 +135,7 @@ struct FdSink : BufferedSink
~FdSink();
- void write(std::string_view data) override;
+ void writeUnbuffered(std::string_view data) override;
bool good() override;
@@ -520,7 +522,7 @@ struct FramedSink : nix::BufferedSink
}
}
- void write(std::string_view data) override
+ void writeUnbuffered(std::string_view data) override
{
/* Don't send more data if the remote has
encountered an error. */
@@ -551,4 +553,10 @@ struct StackAllocator {
static StackAllocator *defaultAllocator;
};
+/* Disabling GC when entering a coroutine (without the boehm patch).
+ mutable to avoid boehm gc dependency in libutil.
+ */
+extern std::shared_ptr<void> (*create_coro_gc_hook)();
+
+
}
diff --git a/src/libutil/tests/config.cc b/src/libutil/tests/config.cc
index 8be6730dd..f250e934e 100644
--- a/src/libutil/tests/config.cc
+++ b/src/libutil/tests/config.cc
@@ -156,12 +156,54 @@ namespace nix {
}
TEST(Config, toJSONOnNonEmptyConfig) {
+ using nlohmann::literals::operator "" _json;
Config config;
- std::map<std::string, Config::SettingInfo> settings;
- Setting<std::string> setting{&config, "", "name-of-the-setting", "description"};
+ Setting<std::string> setting{
+ &config,
+ "",
+ "name-of-the-setting",
+ "description",
+ };
+ setting.assign("value");
+
+ ASSERT_EQ(config.toJSON(),
+ R"#({
+ "name-of-the-setting": {
+ "aliases": [],
+ "defaultValue": "",
+ "description": "description\n",
+ "documentDefault": true,
+ "value": "value",
+ "experimentalFeature": null
+ }
+ })#"_json);
+ }
+
+ TEST(Config, toJSONOnNonEmptyConfigWithExperimentalSetting) {
+ using nlohmann::literals::operator "" _json;
+ Config config;
+ Setting<std::string> setting{
+ &config,
+ "",
+ "name-of-the-setting",
+ "description",
+ {},
+ true,
+ Xp::Flakes,
+ };
setting.assign("value");
- ASSERT_EQ(config.toJSON().dump(), R"#({"name-of-the-setting":{"aliases":[],"defaultValue":"","description":"description\n","documentDefault":true,"value":"value"}})#");
+ ASSERT_EQ(config.toJSON(),
+ R"#({
+ "name-of-the-setting": {
+ "aliases": [],
+ "defaultValue": "",
+ "description": "description\n",
+ "documentDefault": true,
+ "value": "value",
+ "experimentalFeature": "flakes"
+ }
+ })#"_json);
}
TEST(Config, setSettingAlias) {
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index 6ff9d2524..040fed68f 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -936,16 +936,16 @@ constexpr auto enumerate(T && iterable)
{
size_t i;
TIter iter;
- bool operator != (const iterator & other) const { return iter != other.iter; }
- void operator ++ () { ++i; ++iter; }
- auto operator * () const { return std::tie(i, *iter); }
+ constexpr bool operator != (const iterator & other) const { return iter != other.iter; }
+ constexpr void operator ++ () { ++i; ++iter; }
+ constexpr auto operator * () const { return std::tie(i, *iter); }
};
struct iterable_wrapper
{
T iterable;
- auto begin() { return iterator{ 0, std::begin(iterable) }; }
- auto end() { return iterator{ 0, std::end(iterable) }; }
+ constexpr auto begin() { return iterator{ 0, std::begin(iterable) }; }
+ constexpr auto end() { return iterator{ 0, std::end(iterable) }; }
};
return iterable_wrapper{ std::forward<T>(iterable) };
diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc
index 7e4a7ba86..7ae7b4ea6 100644
--- a/src/nix/daemon.cc
+++ b/src/nix/daemon.cc
@@ -1,3 +1,5 @@
+///@file
+
#include "command.hh"
#include "shared.hh"
#include "local-store.hh"
@@ -34,6 +36,19 @@
using namespace nix;
using namespace nix::daemon;
+/**
+ * Settings related to authenticating clients for the Nix daemon.
+ *
+ * For pipes we have little good information about the client side, but
+ * for Unix domain sockets we do. So currently these options implemented
+ * mandatory access control based on user names and group names (looked
+ * up and translated to UID/GIDs in the CLI process that runs the code
+ * in this file).
+ *
+ * No code outside of this file knows about these settings (this is not
+ * exposed in a header); all authentication and authorization happens in
+ * `daemon.cc`.
+ */
struct AuthorizationSettings : Config {
Setting<Strings> trustedUsers{
@@ -54,7 +69,9 @@ struct AuthorizationSettings : Config {
> directories that are otherwise inacessible to them.
)"};
- /* ?Who we trust to use the daemon in safe ways */
+ /**
+ * Who we trust to use the daemon in safe ways
+ */
Setting<Strings> allowedUsers{
this, {"*"}, "allowed-users",
R"(
@@ -112,8 +129,36 @@ static void setSigChldAction(bool autoReap)
throw SysError("setting SIGCHLD handler");
}
+/**
+ * @return Is the given user a member of this group?
+ *
+ * @param user User specified by username.
+ *
+ * @param group Group the user might be a member of.
+ */
+static bool matchUser(std::string_view user, const struct group & gr)
+{
+ for (char * * mem = gr.gr_mem; *mem; mem++)
+ if (user == std::string_view(*mem)) return true;
+ return false;
+}
+
-bool matchUser(const std::string & user, const std::string & group, const Strings & users)
+/**
+ * Does the given user (specified by user name and primary group name)
+ * match the given user/group whitelist?
+ *
+ * If the list allows all users: Yes.
+ *
+ * If the username is in the set: Yes.
+ *
+ * If the groupname is in the set: Yes.
+ *
+ * If the user is in another group which is in the set: yes.
+ *
+ * Otherwise: No.
+ */
+static bool matchUser(const std::string & user, const std::string & group, const Strings & users)
{
if (find(users.begin(), users.end(), "*") != users.end())
return true;
@@ -126,8 +171,7 @@ bool matchUser(const std::string & user, const std::string & group, const String
if (group == i.substr(1)) return true;
struct group * gr = getgrnam(i.c_str() + 1);
if (!gr) continue;
- for (char * * mem = gr->gr_mem; *mem; mem++)
- if (user == std::string(*mem)) return true;
+ if (matchUser(user, *gr)) return true;
}
return false;
@@ -145,7 +189,9 @@ struct PeerInfo
};
-// Get the identity of the caller, if possible.
+/**
+ * Get the identity of the caller, if possible.
+ */
static PeerInfo getPeerInfo(int remote)
{
PeerInfo peer = { false, 0, false, 0, false, 0 };
@@ -179,6 +225,9 @@ static PeerInfo getPeerInfo(int remote)
#define SD_LISTEN_FDS_START 3
+/**
+ * Open a store without a path info cache.
+ */
static ref<Store> openUncachedStore()
{
Store::Params params; // FIXME: get params from somewhere
@@ -187,7 +236,44 @@ static ref<Store> openUncachedStore()
return openStore(settings.storeUri, params);
}
+/**
+ * Authenticate a potential client
+ *
+ * @param peer Information about other end of the connection, the client which
+ * wants to communicate with us.
+ *
+ * @return A pair of a `TrustedFlag`, whether the potential client is trusted,
+ * and the name of the user (useful for printing messages).
+ *
+ * If the potential client is not allowed to talk to us, we throw an `Error`.
+ */
+static std::pair<TrustedFlag, std::string> authPeer(const PeerInfo & peer)
+{
+ TrustedFlag trusted = NotTrusted;
+
+ struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
+ std::string user = pw ? pw->pw_name : std::to_string(peer.uid);
+
+ struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
+ std::string group = gr ? gr->gr_name : std::to_string(peer.gid);
+
+ const Strings & trustedUsers = authorizationSettings.trustedUsers;
+ const Strings & allowedUsers = authorizationSettings.allowedUsers;
+
+ if (matchUser(user, group, trustedUsers))
+ trusted = Trusted;
+
+ if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
+ throw Error("user '%1%' is not allowed to connect to the Nix daemon", user);
+
+ return { trusted, std::move(user) };
+}
+
+/**
+ * Run a server. The loop opens a socket and accepts new connections from that
+ * socket.
+ */
static void daemonLoop()
{
if (chdir("/") == -1)
@@ -231,23 +317,9 @@ static void daemonLoop()
closeOnExec(remote.get());
- TrustedFlag trusted = NotTrusted;
PeerInfo peer = getPeerInfo(remote.get());
-
- struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0;
- std::string user = pw ? pw->pw_name : std::to_string(peer.uid);
-
- struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
- std::string group = gr ? gr->gr_name : std::to_string(peer.gid);
-
- Strings trustedUsers = authorizationSettings.trustedUsers;
- Strings allowedUsers = authorizationSettings.allowedUsers;
-
- if (matchUser(user, group, trustedUsers))
- trusted = Trusted;
-
- if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup)
- throw Error("user '%1%' is not allowed to connect to the Nix daemon", user);
+ auto [_trusted, user] = authPeer(peer);
+ auto trusted = _trusted;
printInfo((std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : ""),
peer.pidKnown ? std::to_string(peer.pid) : "<unknown>",
@@ -294,45 +366,74 @@ static void daemonLoop()
}
}
+/**
+ * Forward a standard IO connection to the given remote store.
+ *
+ * We just act as a middleman blindly ferry output between the standard
+ * input/output and the remote store connection, not processing anything.
+ *
+ * Loops until standard input disconnects, or an error is encountered.
+ */
+static void forwardStdioConnection(RemoteStore & store) {
+ auto conn = store.openConnectionWrapper();
+ int from = conn->from.fd;
+ int to = conn->to.fd;
+
+ auto nfds = std::max(from, STDIN_FILENO) + 1;
+ while (true) {
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(from, &fds);
+ FD_SET(STDIN_FILENO, &fds);
+ if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1)
+ throw SysError("waiting for data from client or server");
+ if (FD_ISSET(from, &fds)) {
+ auto res = splice(from, nullptr, STDOUT_FILENO, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
+ if (res == -1)
+ throw SysError("splicing data from daemon socket to stdout");
+ else if (res == 0)
+ throw EndOfFile("unexpected EOF from daemon socket");
+ }
+ if (FD_ISSET(STDIN_FILENO, &fds)) {
+ auto res = splice(STDIN_FILENO, nullptr, to, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
+ if (res == -1)
+ throw SysError("splicing data from stdin to daemon socket");
+ else if (res == 0)
+ return;
+ }
+ }
+}
+
+/**
+ * Process a client connecting to us via standard input/output
+ *
+ * Unlike `forwardStdioConnection()` we do process commands ourselves in
+ * this case, not delegating to another daemon.
+ *
+ * @note `Trusted` is unconditionally passed because in this mode we
+ * blindly trust the standard streams. Limiting access to those is
+ * explicitly not `nix-daemon`'s responsibility.
+ */
+static void processStdioConnection(ref<Store> store)
+{
+ FdSource from(STDIN_FILENO);
+ FdSink to(STDOUT_FILENO);
+ processConnection(store, from, to, Trusted, NotRecursive);
+}
+
+/**
+ * Entry point shared between the new CLI `nix daemon` and old CLI
+ * `nix-daemon`.
+ */
static void runDaemon(bool stdio)
{
if (stdio) {
- if (auto store = openUncachedStore().dynamic_pointer_cast<RemoteStore>()) {
- auto conn = store->openConnectionWrapper();
- int from = conn->from.fd;
- int to = conn->to.fd;
-
- auto nfds = std::max(from, STDIN_FILENO) + 1;
- while (true) {
- fd_set fds;
- FD_ZERO(&fds);
- FD_SET(from, &fds);
- FD_SET(STDIN_FILENO, &fds);
- if (select(nfds, &fds, nullptr, nullptr, nullptr) == -1)
- throw SysError("waiting for data from client or server");
- if (FD_ISSET(from, &fds)) {
- auto res = splice(from, nullptr, STDOUT_FILENO, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
- if (res == -1)
- throw SysError("splicing data from daemon socket to stdout");
- else if (res == 0)
- throw EndOfFile("unexpected EOF from daemon socket");
- }
- if (FD_ISSET(STDIN_FILENO, &fds)) {
- auto res = splice(STDIN_FILENO, nullptr, to, nullptr, SSIZE_MAX, SPLICE_F_MOVE);
- if (res == -1)
- throw SysError("splicing data from stdin to daemon socket");
- else if (res == 0)
- return;
- }
- }
- } else {
- FdSource from(STDIN_FILENO);
- FdSink to(STDOUT_FILENO);
- /* Auth hook is empty because in this mode we blindly trust the
- standard streams. Limiting access to those is explicitly
- not `nix-daemon`'s responsibility. */
- processConnection(openUncachedStore(), from, to, Trusted, NotRecursive);
- }
+ auto store = openUncachedStore();
+
+ if (auto remoteStore = store.dynamic_pointer_cast<RemoteStore>())
+ forwardStdioConnection(*remoteStore);
+ else
+ processStdioConnection(store);
} else
daemonLoop();
}
diff --git a/src/nix/main.cc b/src/nix/main.cc
index f943f77bb..705061d25 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -375,6 +375,11 @@ void mainWrapped(int argc, char * * argv)
return;
}
+ if (argc == 2 && std::string(argv[1]) == "__dump-xp-features") {
+ logger->cout(documentExperimentalFeatures().dump());
+ return;
+ }
+
Finally printCompletions([&]()
{
if (completions) {
diff --git a/src/nix/nix.md b/src/nix/nix.md
index e1865b31c..1ef6c7fcd 100644
--- a/src/nix/nix.md
+++ b/src/nix/nix.md
@@ -48,12 +48,17 @@ manual](https://nixos.org/manual/nix/stable/).
# Installables
+> **Warning** \
+> Installables are part of the unstable
+> [`nix-command` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-nix-command),
+> and subject to change without notice.
+
Many `nix` subcommands operate on one or more *installables*.
These are command line arguments that represent something that can be realised in the Nix store.
The following types of installable are supported by most commands:
-- [Flake output attribute](#flake-output-attribute)
+- [Flake output attribute](#flake-output-attribute) (experimental)
- [Store path](#store-path)
- [Nix file](#nix-file), optionally qualified by an attribute path
- [Nix expression](#nix-expression), optionally qualified by an attribute path
@@ -63,6 +68,13 @@ That is, Nix will operate on the default flake output attribute of the flake in
### Flake output attribute
+> **Warning** \
+> Flake output attribute installables depend on both the
+> [`flakes`](@docroot@/contributing/experimental-features.md#xp-feature-flakes)
+> and
+> [`nix-command`](@docroot@/contributing/experimental-features.md#xp-feature-nix-command)
+> experimental features, and subject to change without notice.
+
Example: `nixpkgs#hello`
These have the form *flakeref*[`#`*attrpath*], where *flakeref* is a