mirror of
https://github.com/NixOS/nix.git
synced 2025-11-29 21:50:58 +01:00
Merge remote-tracking branch 'origin/master' into lazy-trees
This commit is contained in:
commit
bb421ac80b
108 changed files with 1242 additions and 452 deletions
|
|
@ -380,10 +380,9 @@ Installable::getCursors(EvalState & state)
|
|||
ref<eval_cache::AttrCursor>
|
||||
Installable::getCursor(EvalState & state)
|
||||
{
|
||||
auto cursors = getCursors(state);
|
||||
if (cursors.empty())
|
||||
throw Error("cannot find flake attribute '%s'", what());
|
||||
return cursors[0];
|
||||
/* Although getCursors should return at least one element, in case it doesn't,
|
||||
bound check to avoid an undefined behavior for vector[0] */
|
||||
return getCursors(state).at(0);
|
||||
}
|
||||
|
||||
static StorePath getDeriver(
|
||||
|
|
@ -696,46 +695,28 @@ InstallableFlake::getCursors(EvalState & state)
|
|||
|
||||
std::vector<ref<eval_cache::AttrCursor>> res;
|
||||
|
||||
for (auto & attrPath : getActualAttrPaths()) {
|
||||
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
|
||||
if (attr) res.push_back(ref(*attr));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
ref<eval_cache::AttrCursor> InstallableFlake::getCursor(EvalState & state)
|
||||
{
|
||||
auto lockedFlake = getLockedFlake();
|
||||
|
||||
auto cache = openEvalCache(state, lockedFlake);
|
||||
auto root = cache->getRoot();
|
||||
|
||||
Suggestions suggestions;
|
||||
|
||||
auto attrPaths = getActualAttrPaths();
|
||||
|
||||
for (auto & attrPath : attrPaths) {
|
||||
debug("trying flake output attribute '%s'", attrPath);
|
||||
|
||||
auto attrOrSuggestions = root->findAlongAttrPath(
|
||||
parseAttrPath(state, attrPath),
|
||||
true
|
||||
);
|
||||
|
||||
if (!attrOrSuggestions) {
|
||||
suggestions += attrOrSuggestions.getSuggestions();
|
||||
continue;
|
||||
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
|
||||
if (attr) {
|
||||
res.push_back(ref(*attr));
|
||||
} else {
|
||||
suggestions += attr.getSuggestions();
|
||||
}
|
||||
|
||||
return *attrOrSuggestions;
|
||||
}
|
||||
|
||||
throw Error(
|
||||
suggestions,
|
||||
"flake '%s' does not provide attribute %s",
|
||||
flakeRef,
|
||||
showAttrPaths(attrPaths));
|
||||
if (res.size() == 0)
|
||||
throw Error(
|
||||
suggestions,
|
||||
"flake '%s' does not provide attribute %s",
|
||||
flakeRef,
|
||||
showAttrPaths(attrPaths));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
|
||||
|
|
|
|||
|
|
@ -103,9 +103,13 @@ struct Installable
|
|||
return {};
|
||||
}
|
||||
|
||||
/* Get a cursor to each value this Installable could refer to. However
|
||||
if none exists, throw exception instead of returning empty vector. */
|
||||
virtual std::vector<ref<eval_cache::AttrCursor>>
|
||||
getCursors(EvalState & state);
|
||||
|
||||
/* Get the first and most preferred cursor this Installable could refer
|
||||
to, or throw an exception if none exists. */
|
||||
virtual ref<eval_cache::AttrCursor>
|
||||
getCursor(EvalState & state);
|
||||
|
||||
|
|
@ -193,15 +197,11 @@ struct InstallableFlake : InstallableValue
|
|||
|
||||
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
|
||||
|
||||
/* Get a cursor to every attrpath in getActualAttrPaths() that
|
||||
exists. */
|
||||
/* Get a cursor to every attrpath in getActualAttrPaths()
|
||||
that exists. However if none exists, throw an exception. */
|
||||
std::vector<ref<eval_cache::AttrCursor>>
|
||||
getCursors(EvalState & state) override;
|
||||
|
||||
/* Get a cursor to the first attrpath in getActualAttrPaths() that
|
||||
exists, or throw an exception with suggestions if none exists. */
|
||||
ref<eval_cache::AttrCursor> getCursor(EvalState & state) override;
|
||||
|
||||
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
|
||||
|
||||
FlakeRef nixpkgsFlakeRef() const override;
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ Name: Nix
|
|||
Description: Nix Package Manager
|
||||
Version: @PACKAGE_VERSION@
|
||||
Libs: -L${libdir} -lnixcmd
|
||||
Cflags: -I${includedir}/nix -std=c++17
|
||||
Cflags: -I${includedir}/nix -std=c++20
|
||||
|
|
|
|||
|
|
@ -546,6 +546,7 @@ EvalState::EvalState(
|
|||
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
|
||||
|
||||
/* Initialise the Nix expression search path. */
|
||||
evalSettings.nixPath.setDefault(evalSettings.getDefaultNixPath());
|
||||
if (!evalSettings.pureEval) {
|
||||
for (auto & i : _searchPath) addToSearchPath(i);
|
||||
for (auto & i : evalSettings.nixPath.get()) addToSearchPath(i);
|
||||
|
|
@ -2467,30 +2468,35 @@ std::ostream & operator << (std::ostream & str, const ExternalValueBase & v) {
|
|||
|
||||
EvalSettings::EvalSettings()
|
||||
{
|
||||
auto var = getEnv("NIX_PATH");
|
||||
if (var) nixPath = parseNixPath(*var);
|
||||
}
|
||||
|
||||
/* impure => NIX_PATH or a default path
|
||||
* restrict-eval => NIX_PATH
|
||||
* pure-eval => empty
|
||||
*/
|
||||
Strings EvalSettings::getDefaultNixPath()
|
||||
{
|
||||
Strings res;
|
||||
auto add = [&](const Path & p, const std::string & s = std::string()) {
|
||||
if (pathExists(p)) {
|
||||
if (s.empty()) {
|
||||
res.push_back(p);
|
||||
} else {
|
||||
res.push_back(s + "=" + p);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (pureEval)
|
||||
return {};
|
||||
|
||||
auto var = getEnv("NIX_PATH");
|
||||
if (var) {
|
||||
return parseNixPath(*var);
|
||||
} else if (restrictEval) {
|
||||
return {};
|
||||
} else {
|
||||
Strings res;
|
||||
auto add = [&](const Path & p, const std::optional<std::string> & s = std::nullopt) {
|
||||
if (pathExists(p))
|
||||
res.push_back(s ? *s + "=" + p : p);
|
||||
};
|
||||
|
||||
if (!evalSettings.restrictEval && !evalSettings.pureEval) {
|
||||
add(getHome() + "/.nix-defexpr/channels");
|
||||
add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs");
|
||||
add(settings.nixStateDir + "/profiles/per-user/root/channels");
|
||||
}
|
||||
|
||||
return res;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
bool EvalSettings::isPseudoUrl(std::string_view s)
|
||||
|
|
|
|||
|
|
@ -587,7 +587,7 @@ struct EvalSettings : Config
|
|||
{
|
||||
EvalSettings();
|
||||
|
||||
static Strings getDefaultNixPath();
|
||||
Strings getDefaultNixPath();
|
||||
|
||||
static bool isPseudoUrl(std::string_view s);
|
||||
|
||||
|
|
@ -597,8 +597,15 @@ struct EvalSettings : Config
|
|||
"Whether builtin functions that allow executing native code should be enabled."};
|
||||
|
||||
Setting<Strings> nixPath{
|
||||
this, getDefaultNixPath(), "nix-path",
|
||||
"List of directories to be searched for `<...>` file references."};
|
||||
this, {}, "nix-path",
|
||||
R"(
|
||||
List of directories to be searched for `<...>` file references.
|
||||
|
||||
If [pure evaluation](#conf-pure-eval) is disabled,
|
||||
this is initialised using the [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH)
|
||||
environment variable, or, if it is unset and [restricted evaluation](#conf-restrict-eval)
|
||||
is disabled, a default search path including the user's and `root`'s channels.
|
||||
)"};
|
||||
|
||||
Setting<bool> restrictEval{
|
||||
this, false, "restrict-eval",
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
|
|||
visit(root);
|
||||
|
||||
for (auto & i : nodes) {
|
||||
if (i == root) continue;
|
||||
if (i == ref<const Node>(root)) continue;
|
||||
auto node = i.dynamic_pointer_cast<const LockedNode>();
|
||||
if (node
|
||||
&& !node->lockedRef.input.isLocked()
|
||||
|
|
|
|||
|
|
@ -7,4 +7,4 @@ Description: Nix Package Manager
|
|||
Version: @PACKAGE_VERSION@
|
||||
Requires: nix-store bdw-gc
|
||||
Libs: -L${libdir} -lnixexpr
|
||||
Cflags: -I${includedir}/nix -std=c++17
|
||||
Cflags: -I${includedir}/nix -std=c++20
|
||||
|
|
|
|||
|
|
@ -499,6 +499,17 @@ static RegisterPrimOp primop_fetchGit({
|
|||
> **Note**
|
||||
>
|
||||
> This behavior is disabled in *Pure evaluation mode*.
|
||||
|
||||
- To fetch the content of a checked-out work directory:
|
||||
|
||||
```nix
|
||||
builtins.fetchGit ./work-dir
|
||||
```
|
||||
|
||||
If the URL points to a local directory, and no `ref` or `rev` is
|
||||
given, `fetchGit` will use the current content of the checked-out
|
||||
files, even if they are not committed or added to Git's index. It will
|
||||
only consider files added to the Git repository, as listed by `git ls-files`.
|
||||
)",
|
||||
.fun = prim_fetchGit,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "libexprtests.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "libexprtests.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
#include "value-to-json.hh"
|
||||
|
||||
namespace nix {
|
||||
|
|
|
|||
|
|
@ -7,18 +7,19 @@
|
|||
#include "eval-inline.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include "tests/libstore.hh"
|
||||
|
||||
namespace nix {
|
||||
class LibExprTest : public ::testing::Test {
|
||||
class LibExprTest : public LibStoreTest {
|
||||
public:
|
||||
static void SetUpTestSuite() {
|
||||
initLibStore();
|
||||
LibStoreTest::SetUpTestSuite();
|
||||
initGC();
|
||||
}
|
||||
|
||||
protected:
|
||||
LibExprTest()
|
||||
: store(openStore("dummy://"))
|
||||
: LibStoreTest()
|
||||
, state({}, store)
|
||||
{
|
||||
}
|
||||
|
|
@ -36,7 +37,6 @@ namespace nix {
|
|||
return state.symbols.create(value);
|
||||
}
|
||||
|
||||
ref<Store> store;
|
||||
EvalState state;
|
||||
};
|
||||
|
||||
|
|
@ -2,6 +2,8 @@ check: libexpr-tests_RUN
|
|||
|
||||
programs += libexpr-tests
|
||||
|
||||
libexpr-tests_NAME := libnixexpr-tests
|
||||
|
||||
libexpr-tests_DIR := $(d)
|
||||
|
||||
libexpr-tests_INSTALL_DIR :=
|
||||
|
|
@ -12,6 +14,6 @@ libexpr-tests_SOURCES := \
|
|||
|
||||
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests -I src/libfetchers
|
||||
|
||||
libexpr-tests_LIBS = libexpr libutil libstore libfetchers
|
||||
libexpr-tests_LIBS = libstore-tests libutils-tests libexpr libutil libstore libfetchers
|
||||
|
||||
libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "libexprtests.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
|
||||
namespace nix {
|
||||
class CaptureLogger : public Logger
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#include "libexprtests.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
|
||||
namespace nix {
|
||||
// Testing of trivial expressions
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
#include "value/context.hh"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rapidcheck/gtest.h>
|
||||
|
||||
#include "libexprtests.hh"
|
||||
#include "tests/path.hh"
|
||||
#include "tests/libexpr.hh"
|
||||
#include "tests/value/context.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -70,3 +74,54 @@ TEST_F(NixStringContextElemTest, built) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
Gen<NixStringContextElem::Opaque> Arbitrary<NixStringContextElem::Opaque>::arbitrary()
|
||||
{
|
||||
return gen::just(NixStringContextElem::Opaque {
|
||||
.path = *gen::arbitrary<StorePath>(),
|
||||
});
|
||||
}
|
||||
|
||||
Gen<NixStringContextElem::DrvDeep> Arbitrary<NixStringContextElem::DrvDeep>::arbitrary()
|
||||
{
|
||||
return gen::just(NixStringContextElem::DrvDeep {
|
||||
.drvPath = *gen::arbitrary<StorePath>(),
|
||||
});
|
||||
}
|
||||
|
||||
Gen<NixStringContextElem::Built> Arbitrary<NixStringContextElem::Built>::arbitrary()
|
||||
{
|
||||
return gen::just(NixStringContextElem::Built {
|
||||
.drvPath = *gen::arbitrary<StorePath>(),
|
||||
.output = (*gen::arbitrary<StorePathName>()).name,
|
||||
});
|
||||
}
|
||||
|
||||
Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
|
||||
{
|
||||
switch (*gen::inRange<uint8_t>(0, 2)) {
|
||||
case 0:
|
||||
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Opaque>());
|
||||
case 1:
|
||||
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::DrvDeep>());
|
||||
default:
|
||||
return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Built>());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace nix {
|
||||
|
||||
RC_GTEST_FIXTURE_PROP(
|
||||
NixStringContextElemTest,
|
||||
prop_round_rip,
|
||||
(const NixStringContextElem & o))
|
||||
{
|
||||
RC_ASSERT(o == NixStringContextElem::parse(store(), o.to_string(store())));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
30
src/libexpr/tests/value/context.hh
Normal file
30
src/libexpr/tests/value/context.hh
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <rapidcheck/gen/Arbitrary.h>
|
||||
|
||||
#include <value/context.hh>
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem::Opaque> {
|
||||
static Gen<NixStringContextElem::Opaque> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem::Built> {
|
||||
static Gen<NixStringContextElem::Built> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem::DrvDeep> {
|
||||
static Gen<NixStringContextElem::DrvDeep> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<NixStringContextElem> {
|
||||
static Gen<NixStringContextElem> arbitrary();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include "util.hh"
|
||||
#include "comparator.hh"
|
||||
#include "path.hh"
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
|
|
@ -31,7 +32,9 @@ class Store;
|
|||
Encoded as just the path: ‘<path>’.
|
||||
*/
|
||||
struct NixStringContextElem_Opaque {
|
||||
StorePath path;
|
||||
StorePath path;
|
||||
|
||||
GENERATE_CMP(NixStringContextElem_Opaque, me->path);
|
||||
};
|
||||
|
||||
/* Path to a derivation and its entire build closure.
|
||||
|
|
@ -43,7 +46,9 @@ struct NixStringContextElem_Opaque {
|
|||
Encoded in the form ‘=<drvPath>’.
|
||||
*/
|
||||
struct NixStringContextElem_DrvDeep {
|
||||
StorePath drvPath;
|
||||
StorePath drvPath;
|
||||
|
||||
GENERATE_CMP(NixStringContextElem_DrvDeep, me->drvPath);
|
||||
};
|
||||
|
||||
/* Derivation output.
|
||||
|
|
@ -51,8 +56,10 @@ struct NixStringContextElem_DrvDeep {
|
|||
Encoded in the form ‘!<output>!<drvPath>’.
|
||||
*/
|
||||
struct NixStringContextElem_Built {
|
||||
StorePath drvPath;
|
||||
std::string output;
|
||||
StorePath drvPath;
|
||||
std::string output;
|
||||
|
||||
GENERATE_CMP(NixStringContextElem_Built, me->drvPath, me->output);
|
||||
};
|
||||
|
||||
using _NixStringContextElem_Raw = std::variant<
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ Name: Nix
|
|||
Description: Nix Package Manager
|
||||
Version: @PACKAGE_VERSION@
|
||||
Libs: -L${libdir} -lnixmain
|
||||
Cflags: -I${includedir}/nix -std=c++17
|
||||
Cflags: -I${includedir}/nix -std=c++20
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath,
|
|||
auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback));
|
||||
|
||||
getFile(narInfoFile,
|
||||
{[=](std::future<std::optional<std::string>> fut) {
|
||||
{[=,this](std::future<std::optional<std::string>> fut) {
|
||||
try {
|
||||
auto data = fut.get();
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include "json-utils.hh"
|
||||
#include "cgroup.hh"
|
||||
#include "personality.hh"
|
||||
#include "namespaces.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <queue>
|
||||
|
|
@ -167,7 +168,8 @@ void LocalDerivationGoal::killSandbox(bool getStats)
|
|||
}
|
||||
|
||||
|
||||
void LocalDerivationGoal::tryLocalBuild() {
|
||||
void LocalDerivationGoal::tryLocalBuild()
|
||||
{
|
||||
unsigned int curBuilds = worker.getNrLocalBuilds();
|
||||
if (curBuilds >= settings.maxBuildJobs) {
|
||||
state = &DerivationGoal::tryToBuild;
|
||||
|
|
@ -205,6 +207,17 @@ void LocalDerivationGoal::tryLocalBuild() {
|
|||
#endif
|
||||
}
|
||||
|
||||
#if __linux__
|
||||
if (useChroot) {
|
||||
if (!mountNamespacesSupported() || !pidNamespacesSupported()) {
|
||||
if (!settings.sandboxFallback)
|
||||
throw Error("this system does not support the kernel namespaces that are required for sandboxing; use '--no-sandbox' to disable sandboxing");
|
||||
debug("auto-disabling sandboxing because the prerequisite namespaces are not available");
|
||||
useChroot = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (useBuildUsers()) {
|
||||
if (!buildUser)
|
||||
buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1, useChroot);
|
||||
|
|
@ -888,12 +901,7 @@ void LocalDerivationGoal::startBuilder()
|
|||
|
||||
userNamespaceSync.create();
|
||||
|
||||
Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
|
||||
static bool userNamespacesEnabled =
|
||||
pathExists(maxUserNamespaces)
|
||||
&& trim(readFile(maxUserNamespaces)) != "0";
|
||||
|
||||
usingUserNamespace = userNamespacesEnabled;
|
||||
usingUserNamespace = userNamespacesSupported();
|
||||
|
||||
Pid helper = startProcess([&]() {
|
||||
|
||||
|
|
@ -920,64 +928,15 @@ void LocalDerivationGoal::startBuilder()
|
|||
flags |= CLONE_NEWUSER;
|
||||
|
||||
pid_t child = clone(childEntry, stack + stackSize, flags, this);
|
||||
if (child == -1 && errno == EINVAL) {
|
||||
/* Fallback for Linux < 2.13 where CLONE_NEWPID and
|
||||
CLONE_PARENT are not allowed together. */
|
||||
flags &= ~CLONE_NEWPID;
|
||||
child = clone(childEntry, stack + stackSize, flags, this);
|
||||
}
|
||||
if (usingUserNamespace && child == -1 && (errno == EPERM || errno == EINVAL)) {
|
||||
/* Some distros patch Linux to not allow unprivileged
|
||||
* user namespaces. If we get EPERM or EINVAL, try
|
||||
* without CLONE_NEWUSER and see if that works.
|
||||
* Details: https://salsa.debian.org/kernel-team/linux/-/commit/d98e00eda6bea437e39b9e80444eee84a32438a6
|
||||
*/
|
||||
usingUserNamespace = false;
|
||||
flags &= ~CLONE_NEWUSER;
|
||||
child = clone(childEntry, stack + stackSize, flags, this);
|
||||
}
|
||||
if (child == -1) {
|
||||
switch(errno) {
|
||||
case EPERM:
|
||||
case EINVAL: {
|
||||
int errno_ = errno;
|
||||
if (!userNamespacesEnabled && errno==EPERM)
|
||||
notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/user/max_user_namespaces");
|
||||
if (userNamespacesEnabled) {
|
||||
Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
|
||||
if (pathExists(procSysKernelUnprivilegedUsernsClone)
|
||||
&& trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0") {
|
||||
notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/kernel/unprivileged_userns_clone");
|
||||
}
|
||||
}
|
||||
Path procSelfNsUser = "/proc/self/ns/user";
|
||||
if (!pathExists(procSelfNsUser))
|
||||
notice("/proc/self/ns/user does not exist; your kernel was likely built without CONFIG_USER_NS=y, which is required for sandboxing");
|
||||
/* Otherwise exit with EPERM so we can handle this in the
|
||||
parent. This is only done when sandbox-fallback is set
|
||||
to true (the default). */
|
||||
if (settings.sandboxFallback)
|
||||
_exit(1);
|
||||
/* Mention sandbox-fallback in the error message so the user
|
||||
knows that having it disabled contributed to the
|
||||
unrecoverability of this failure */
|
||||
throw SysError(errno_, "creating sandboxed builder process using clone(), without sandbox-fallback");
|
||||
}
|
||||
default:
|
||||
throw SysError("creating sandboxed builder process using clone()");
|
||||
}
|
||||
}
|
||||
|
||||
if (child == -1)
|
||||
throw SysError("creating sandboxed builder process using clone()");
|
||||
writeFull(builderOut.writeSide.get(),
|
||||
fmt("%d %d\n", usingUserNamespace, child));
|
||||
_exit(0);
|
||||
});
|
||||
|
||||
int res = helper.wait();
|
||||
if (res != 0 && settings.sandboxFallback) {
|
||||
useChroot = false;
|
||||
initTmpDir();
|
||||
goto fallback;
|
||||
} else if (res != 0)
|
||||
if (helper.wait() != 0)
|
||||
throw Error("unable to start build process");
|
||||
|
||||
userNamespaceSync.readSide = -1;
|
||||
|
|
@ -1045,9 +1004,6 @@ void LocalDerivationGoal::startBuilder()
|
|||
} else
|
||||
#endif
|
||||
{
|
||||
#if __linux__
|
||||
fallback:
|
||||
#endif
|
||||
pid = startProcess([&]() {
|
||||
runChild();
|
||||
});
|
||||
|
|
@ -1516,8 +1472,7 @@ void LocalDerivationGoal::startDaemon()
|
|||
FdSink to(remote.get());
|
||||
try {
|
||||
daemon::processConnection(store, from, to,
|
||||
daemon::NotTrusted, daemon::Recursive,
|
||||
[&](Store & store) { store.createUser("nobody", 65535); });
|
||||
daemon::NotTrusted, daemon::Recursive);
|
||||
debug("terminated daemon connection");
|
||||
} catch (SysError &) {
|
||||
ignoreException();
|
||||
|
|
@ -2323,11 +2278,28 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
buildUser ? std::optional(buildUser->getUIDRange()) : std::nullopt,
|
||||
inodesSeen);
|
||||
|
||||
debug("scanning for references for output '%s' in temp location '%s'", outputName, actualPath);
|
||||
bool discardReferences = false;
|
||||
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
|
||||
if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) {
|
||||
settings.requireExperimentalFeature(Xp::DiscardReferences);
|
||||
if (auto output = get(*udr, outputName)) {
|
||||
if (!output->is_boolean())
|
||||
throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string());
|
||||
discardReferences = output->get<bool>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Pass blank Sink as we are not ready to hash data at this stage. */
|
||||
NullSink blank;
|
||||
auto references = scanForReferences(blank, actualPath, referenceablePaths);
|
||||
StorePathSet references;
|
||||
if (discardReferences)
|
||||
debug("discarding references of output '%s'", outputName);
|
||||
else {
|
||||
debug("scanning for references for output '%s' in temp location '%s'", outputName, actualPath);
|
||||
|
||||
/* Pass blank Sink as we are not ready to hash data at this stage. */
|
||||
NullSink blank;
|
||||
references = scanForReferences(blank, actualPath, referenceablePaths);
|
||||
}
|
||||
|
||||
outputReferencesIfUnregistered.insert_or_assign(
|
||||
outputName,
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ void Worker::run(const Goals & _topGoals)
|
|||
if (!children.empty() || !waitingForAWhile.empty())
|
||||
waitForInput();
|
||||
else {
|
||||
if (awake.empty() && 0 == settings.maxBuildJobs)
|
||||
if (awake.empty() && 0U == settings.maxBuildJobs)
|
||||
{
|
||||
if (getMachines().empty())
|
||||
throw Error("unable to start any build; either increase '--max-jobs' "
|
||||
|
|
|
|||
|
|
@ -236,6 +236,10 @@ struct ClientSettings
|
|||
// the daemon, as that could cause some pretty weird stuff
|
||||
if (parseFeatures(tokenizeString<StringSet>(value)) != settings.experimentalFeatures.get())
|
||||
debug("Ignoring the client-specified experimental features");
|
||||
} else if (name == settings.pluginFiles.name) {
|
||||
if (tokenizeString<Paths>(value) != settings.pluginFiles.get())
|
||||
warn("Ignoring the client-specified plugin-files.\n"
|
||||
"The client specifying plugins to the daemon never made sense, and was removed in Nix >=2.14.");
|
||||
}
|
||||
else if (trusted
|
||||
|| name == settings.buildTimeout.name
|
||||
|
|
@ -525,7 +529,14 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
mode = (BuildMode) readInt(from);
|
||||
|
||||
/* Repairing is not atomic, so disallowed for "untrusted"
|
||||
clients. */
|
||||
clients.
|
||||
|
||||
FIXME: layer violation in this message: the daemon code (i.e.
|
||||
this file) knows whether a client/connection is trusted, but it
|
||||
does not how how the client was authenticated. The mechanism
|
||||
need not be getting the UID of the other end of a Unix Domain
|
||||
Socket.
|
||||
*/
|
||||
if (mode == bmRepair && !trusted)
|
||||
throw Error("repairing is not allowed because you are not in 'trusted-users'");
|
||||
}
|
||||
|
|
@ -542,7 +553,9 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
|||
mode = (BuildMode) readInt(from);
|
||||
|
||||
/* Repairing is not atomic, so disallowed for "untrusted"
|
||||
clients. */
|
||||
clients.
|
||||
|
||||
FIXME: layer violation; see above. */
|
||||
if (mode == bmRepair && !trusted)
|
||||
throw Error("repairing is not allowed because you are not in 'trusted-users'");
|
||||
|
||||
|
|
@ -981,8 +994,7 @@ void processConnection(
|
|||
FdSource & from,
|
||||
FdSink & to,
|
||||
TrustedFlag trusted,
|
||||
RecursiveFlag recursive,
|
||||
std::function<void(Store &)> authHook)
|
||||
RecursiveFlag recursive)
|
||||
{
|
||||
auto monitor = !recursive ? std::make_unique<MonitorFdHup>(from.fd) : nullptr;
|
||||
|
||||
|
|
@ -1025,10 +1037,6 @@ void processConnection(
|
|||
|
||||
try {
|
||||
|
||||
/* If we can't accept clientVersion, then throw an error
|
||||
*here* (not above). */
|
||||
authHook(*store);
|
||||
|
||||
tunnelLogger->stopWork();
|
||||
to.flush();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,11 +13,6 @@ void processConnection(
|
|||
FdSource & from,
|
||||
FdSink & to,
|
||||
TrustedFlag trusted,
|
||||
RecursiveFlag recursive,
|
||||
/* Arbitrary hook to check authorization / initialize user data / whatever
|
||||
after the protocol has been negotiated. The idea is that this function
|
||||
and everything it calls doesn't know about this stuff, and the
|
||||
`nix-daemon` handles that instead. */
|
||||
std::function<void(Store &)> authHook);
|
||||
RecursiveFlag recursive);
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@
|
|||
#include "path.hh"
|
||||
#include "realisation.hh"
|
||||
#include "outputs-spec.hh"
|
||||
#include "comparator.hh"
|
||||
|
||||
#include <optional>
|
||||
#include <variant>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
|
|
@ -27,8 +28,7 @@ struct DerivedPathOpaque {
|
|||
std::string to_string(const Store & store) const;
|
||||
static DerivedPathOpaque parse(const Store & store, std::string_view);
|
||||
|
||||
bool operator < (const DerivedPathOpaque & b) const
|
||||
{ return path < b.path; }
|
||||
GENERATE_CMP(DerivedPathOpaque, me->path);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -51,8 +51,7 @@ struct DerivedPathBuilt {
|
|||
static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view);
|
||||
nlohmann::json toJSON(ref<Store> store) const;
|
||||
|
||||
bool operator < (const DerivedPathBuilt & b) const
|
||||
{ return std::make_pair(drvPath, outputs) < std::make_pair(b.drvPath, b.outputs); }
|
||||
GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs);
|
||||
};
|
||||
|
||||
using _DerivedPathRaw = std::variant<
|
||||
|
|
@ -96,6 +95,8 @@ struct BuiltPathBuilt {
|
|||
|
||||
nlohmann::json toJSON(ref<Store> store) const;
|
||||
static BuiltPathBuilt parse(const Store & store, std::string_view);
|
||||
|
||||
GENERATE_CMP(BuiltPathBuilt, me->drvPath, me->outputs);
|
||||
};
|
||||
|
||||
using _BuiltPathRaw = std::variant<
|
||||
|
|
|
|||
|
|
@ -222,19 +222,19 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s
|
|||
.longName = name,
|
||||
.description = "Enable sandboxing.",
|
||||
.category = category,
|
||||
.handler = {[=]() { override(smEnabled); }}
|
||||
.handler = {[this]() { override(smEnabled); }}
|
||||
});
|
||||
args.addFlag({
|
||||
.longName = "no-" + name,
|
||||
.description = "Disable sandboxing.",
|
||||
.category = category,
|
||||
.handler = {[=]() { override(smDisabled); }}
|
||||
.handler = {[this]() { override(smDisabled); }}
|
||||
});
|
||||
args.addFlag({
|
||||
.longName = "relaxed-" + name,
|
||||
.description = "Enable sandboxing, but allow builds to disable it.",
|
||||
.category = category,
|
||||
.handler = {[=]() { override(smRelaxed); }}
|
||||
.handler = {[this]() { override(smRelaxed); }}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -279,8 +279,8 @@ public:
|
|||
If the build users group is empty, builds will be performed under
|
||||
the uid of the Nix process (that is, the uid of the caller if
|
||||
`NIX_REMOTE` is empty, the uid under which the Nix daemon runs if
|
||||
`NIX_REMOTE` is `daemon`). Obviously, this should not be used in
|
||||
multi-user settings with untrusted users.
|
||||
`NIX_REMOTE` is `daemon`). Obviously, this should not be used
|
||||
with a nix daemon accessible to untrusted clients.
|
||||
|
||||
Defaults to `nixbld` when running as root, *empty* otherwise.
|
||||
)",
|
||||
|
|
@ -696,24 +696,6 @@ public:
|
|||
)",
|
||||
{"trusted-binary-caches"}};
|
||||
|
||||
Setting<Strings> trustedUsers{
|
||||
this, {"root"}, "trusted-users",
|
||||
R"(
|
||||
A list of names of users (separated by whitespace) that have
|
||||
additional rights when connecting to the Nix daemon, such as the
|
||||
ability to specify additional binary caches, or to import unsigned
|
||||
NARs. You can also specify groups by prefixing them with `@`; for
|
||||
instance, `@wheel` means all users in the `wheel` group. The default
|
||||
is `root`.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> Adding a user to `trusted-users` is essentially equivalent to
|
||||
> giving that user root access to the system. For example, the user
|
||||
> can set `sandbox-paths` and thereby obtain read access to
|
||||
> directories that are otherwise inacessible to them.
|
||||
)"};
|
||||
|
||||
Setting<unsigned int> ttlNegativeNarInfoCache{
|
||||
this, 3600, "narinfo-cache-negative-ttl",
|
||||
R"(
|
||||
|
|
@ -736,18 +718,6 @@ public:
|
|||
mismatch if the build isn't reproducible.
|
||||
)"};
|
||||
|
||||
/* ?Who we trust to use the daemon in safe ways */
|
||||
Setting<Strings> allowedUsers{
|
||||
this, {"*"}, "allowed-users",
|
||||
R"(
|
||||
A list of names of users (separated by whitespace) that are allowed
|
||||
to connect to the Nix daemon. As with the `trusted-users` option,
|
||||
you can specify groups by prefixing them with `@`. Also, you can
|
||||
allow all users by specifying `*`. The default is `*`.
|
||||
|
||||
Note that trusted users are always allowed to connect.
|
||||
)"};
|
||||
|
||||
Setting<bool> printMissing{this, true, "print-missing",
|
||||
"Whether to print what paths need to be built or downloaded."};
|
||||
|
||||
|
|
|
|||
|
|
@ -134,7 +134,6 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
|
|||
/* Hash will be set below. FIXME construct ValidPathInfo at end. */
|
||||
auto info = std::make_shared<ValidPathInfo>(path, Hash::dummy);
|
||||
|
||||
PathSet references;
|
||||
auto deriver = readString(conn->from);
|
||||
if (deriver != "")
|
||||
info->deriver = parseStorePath(deriver);
|
||||
|
|
|
|||
|
|
@ -201,8 +201,6 @@ LocalStore::LocalStore(const Params & params)
|
|||
throw SysError("could not set permissions on '%s' to 755", perUserDir);
|
||||
}
|
||||
|
||||
createUser(getUserName(), getuid());
|
||||
|
||||
/* Optionally, create directories and set permissions for a
|
||||
multi-user install. */
|
||||
if (getuid() == 0 && settings.buildUsersGroup != "") {
|
||||
|
|
@ -1824,20 +1822,6 @@ void LocalStore::signPathInfo(ValidPathInfo & info)
|
|||
}
|
||||
|
||||
|
||||
void LocalStore::createUser(const std::string & userName, uid_t userId)
|
||||
{
|
||||
for (auto & dir : {
|
||||
fmt("%s/profiles/per-user/%s", stateDir, userName),
|
||||
fmt("%s/gcroots/per-user/%s", stateDir, userName)
|
||||
}) {
|
||||
createDirs(dir);
|
||||
if (chmod(dir.c_str(), 0755) == -1)
|
||||
throw SysError("changing permissions of directory '%s'", dir);
|
||||
if (chown(dir.c_str(), userId, getgid()) == -1)
|
||||
throw SysError("changing owner of directory '%s'", dir);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<int64_t, Realisation>> LocalStore::queryRealisationCore_(
|
||||
LocalStore::State & state,
|
||||
const DrvOutput & id)
|
||||
|
|
|
|||
|
|
@ -281,8 +281,6 @@ private:
|
|||
void signPathInfo(ValidPathInfo & info);
|
||||
void signRealisation(Realisation &);
|
||||
|
||||
void createUser(const std::string & userName, uid_t userId) override;
|
||||
|
||||
// XXX: Make a generic `Store` method
|
||||
FixedOutputHash hashCAPath(
|
||||
const FileIngestionMethod & method,
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ Name: Nix
|
|||
Description: Nix Package Manager
|
||||
Version: @PACKAGE_VERSION@
|
||||
Libs: -L${libdir} -lnixstore -lnixutil
|
||||
Cflags: -I${includedir}/nix -std=c++17
|
||||
Cflags: -I${includedir}/nix -std=c++20
|
||||
|
|
|
|||
|
|
@ -81,12 +81,6 @@ struct ValidPathInfo
|
|||
/* Return true iff the path is verifiably content-addressed. */
|
||||
bool isContentAddressed(const Store & store) const;
|
||||
|
||||
/* Functions to view references + hasSelfReference as one set, mainly for
|
||||
compatibility's sake. */
|
||||
StorePathSet referencesPossiblyToSelf() const;
|
||||
void insertReferencePossiblyToSelf(StorePath && ref);
|
||||
void setReferencesPossiblyToSelf(StorePathSet && refs);
|
||||
|
||||
static const size_t maxSigs = std::numeric_limits<size_t>::max();
|
||||
|
||||
/* Return the number of signatures on this .narinfo that were
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "content-address.hh"
|
||||
#include <string_view>
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
|
@ -66,8 +67,6 @@ public:
|
|||
typedef std::set<StorePath> StorePathSet;
|
||||
typedef std::vector<StorePath> StorePaths;
|
||||
|
||||
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
|
||||
|
||||
/* Extension of derivations in the Nix store. */
|
||||
const std::string drvExtension = ".drv";
|
||||
|
||||
|
|
|
|||
|
|
@ -280,16 +280,24 @@ std::string optimisticLockProfile(const Path & profile)
|
|||
}
|
||||
|
||||
|
||||
Path profilesDir()
|
||||
{
|
||||
auto profileRoot = getDataDir() + "/nix/profiles";
|
||||
createDirs(profileRoot);
|
||||
return profileRoot;
|
||||
}
|
||||
|
||||
|
||||
Path getDefaultProfile()
|
||||
{
|
||||
Path profileLink = getHome() + "/.nix-profile";
|
||||
try {
|
||||
auto profile =
|
||||
getuid() == 0
|
||||
? settings.nixStateDir + "/profiles/default"
|
||||
: profilesDir() + "/profile";
|
||||
if (!pathExists(profileLink)) {
|
||||
replaceSymlink(
|
||||
getuid() == 0
|
||||
? settings.nixStateDir + "/profiles/default"
|
||||
: fmt("%s/profiles/per-user/%s/profile", settings.nixStateDir, getUserName()),
|
||||
profileLink);
|
||||
replaceSymlink(profile, profileLink);
|
||||
}
|
||||
return absPath(readLink(profileLink), dirOf(profileLink));
|
||||
} catch (Error &) {
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ void lockProfile(PathLocks & lock, const Path & profile);
|
|||
rebuilt. */
|
||||
std::string optimisticLockProfile(const Path & profile);
|
||||
|
||||
/* Creates and returns the path to a directory suitable for storing the user’s
|
||||
profiles. */
|
||||
Path profilesDir();
|
||||
|
||||
/* Resolve ~/.nix-profile. If ~/.nix-profile doesn't exist yet, create
|
||||
it. */
|
||||
Path getDefaultProfile();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "hash.hh"
|
||||
#include "path.hh"
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
#include "comparator.hh"
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ void RemoteStore::setOptions(Connection & conn)
|
|||
overrides.erase(settings.useSubstitutes.name);
|
||||
overrides.erase(loggerSettings.showTrace.name);
|
||||
overrides.erase(settings.experimentalFeatures.name);
|
||||
overrides.erase(settings.pluginFiles.name);
|
||||
conn.to << overrides.size();
|
||||
for (auto & i : overrides)
|
||||
conn.to << i.first << i.second.value;
|
||||
|
|
|
|||
|
|
@ -746,13 +746,13 @@ StorePathSet Store::queryValidPaths(const StorePathSet & paths, SubstituteFlag m
|
|||
std::condition_variable wakeup;
|
||||
ThreadPool pool;
|
||||
|
||||
auto doQuery = [&](const Path & path) {
|
||||
auto doQuery = [&](const StorePath & path) {
|
||||
checkInterrupt();
|
||||
queryPathInfo(parseStorePath(path), {[path, this, &state_, &wakeup](std::future<ref<const ValidPathInfo>> fut) {
|
||||
queryPathInfo(path, {[path, &state_, &wakeup](std::future<ref<const ValidPathInfo>> fut) {
|
||||
auto state(state_.lock());
|
||||
try {
|
||||
auto info = fut.get();
|
||||
state->valid.insert(parseStorePath(path));
|
||||
state->valid.insert(path);
|
||||
} catch (InvalidPath &) {
|
||||
} catch (...) {
|
||||
state->exc = std::current_exception();
|
||||
|
|
@ -764,7 +764,7 @@ StorePathSet Store::queryValidPaths(const StorePathSet & paths, SubstituteFlag m
|
|||
};
|
||||
|
||||
for (auto & path : paths)
|
||||
pool.enqueue(std::bind(doQuery, printStorePath(path))); // FIXME
|
||||
pool.enqueue(std::bind(doQuery, path));
|
||||
|
||||
pool.process();
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,8 @@ enum BuildMode { bmNormal, bmRepair, bmCheck };
|
|||
struct BuildResult;
|
||||
|
||||
|
||||
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;
|
||||
|
||||
struct StoreConfig : public Config
|
||||
{
|
||||
using Config::Config;
|
||||
|
|
@ -659,9 +661,6 @@ public:
|
|||
return toRealPath(printStorePath(storePath));
|
||||
}
|
||||
|
||||
virtual void createUser(const std::string & userName, uid_t userId)
|
||||
{ }
|
||||
|
||||
/*
|
||||
* Synchronises the options of the client with those of the daemon
|
||||
* (a no-op when there’s no daemon)
|
||||
|
|
|
|||
62
src/libstore/tests/derived-path.cc
Normal file
62
src/libstore/tests/derived-path.cc
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include <regex>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rapidcheck/gtest.h>
|
||||
|
||||
#include "tests/derived-path.hh"
|
||||
#include "tests/libstore.hh"
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
Gen<DerivedPath::Opaque> Arbitrary<DerivedPath::Opaque>::arbitrary()
|
||||
{
|
||||
return gen::just(DerivedPath::Opaque {
|
||||
.path = *gen::arbitrary<StorePath>(),
|
||||
});
|
||||
}
|
||||
|
||||
Gen<DerivedPath::Built> Arbitrary<DerivedPath::Built>::arbitrary()
|
||||
{
|
||||
return gen::just(DerivedPath::Built {
|
||||
.drvPath = *gen::arbitrary<StorePath>(),
|
||||
.outputs = *gen::arbitrary<OutputsSpec>(),
|
||||
});
|
||||
}
|
||||
|
||||
Gen<DerivedPath> Arbitrary<DerivedPath>::arbitrary()
|
||||
{
|
||||
switch (*gen::inRange<uint8_t>(0, 1)) {
|
||||
case 0:
|
||||
return gen::just<DerivedPath>(*gen::arbitrary<DerivedPath::Opaque>());
|
||||
default:
|
||||
return gen::just<DerivedPath>(*gen::arbitrary<DerivedPath::Built>());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace nix {
|
||||
|
||||
class DerivedPathTest : public LibStoreTest
|
||||
{
|
||||
};
|
||||
|
||||
// FIXME: `RC_GTEST_FIXTURE_PROP` isn't calling `SetUpTestSuite` because it is
|
||||
// no a real fixture.
|
||||
//
|
||||
// See https://github.com/emil-e/rapidcheck/blob/master/doc/gtest.md#rc_gtest_fixture_propfixture-name-args
|
||||
TEST_F(DerivedPathTest, force_init)
|
||||
{
|
||||
}
|
||||
|
||||
RC_GTEST_FIXTURE_PROP(
|
||||
DerivedPathTest,
|
||||
prop_round_rip,
|
||||
(const DerivedPath & o))
|
||||
{
|
||||
RC_ASSERT(o == DerivedPath::parse(*store, o.to_string(*store)));
|
||||
}
|
||||
|
||||
}
|
||||
28
src/libstore/tests/derived-path.hh
Normal file
28
src/libstore/tests/derived-path.hh
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <rapidcheck/gen/Arbitrary.h>
|
||||
|
||||
#include <derived-path.hh>
|
||||
|
||||
#include "tests/path.hh"
|
||||
#include "tests/outputs-spec.hh"
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<DerivedPath::Opaque> {
|
||||
static Gen<DerivedPath::Opaque> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<DerivedPath::Built> {
|
||||
static Gen<DerivedPath::Built> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<DerivedPath> {
|
||||
static Gen<DerivedPath> arbitrary();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,20 @@
|
|||
check: libstore-tests_RUN
|
||||
check: libstore-tests-exe_RUN
|
||||
|
||||
programs += libstore-tests
|
||||
programs += libstore-tests-exe
|
||||
|
||||
libstore-tests-exe_NAME = libnixstore-tests
|
||||
|
||||
libstore-tests-exe_DIR := $(d)
|
||||
|
||||
libstore-tests-exe_INSTALL_DIR :=
|
||||
|
||||
libstore-tests-exe_LIBS = libstore-tests
|
||||
|
||||
libstore-tests-exe_LDFLAGS := $(GTEST_LIBS)
|
||||
|
||||
libraries += libstore-tests
|
||||
|
||||
libstore-tests_NAME = libnixstore-tests
|
||||
|
||||
libstore-tests_DIR := $(d)
|
||||
|
||||
|
|
@ -10,6 +24,6 @@ libstore-tests_SOURCES := $(wildcard $(d)/*.cc)
|
|||
|
||||
libstore-tests_CXXFLAGS += -I src/libstore -I src/libutil
|
||||
|
||||
libstore-tests_LIBS = libstore libutil
|
||||
libstore-tests_LIBS = libutil-tests libstore libutil
|
||||
|
||||
libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rapidcheck/gtest.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -199,3 +200,34 @@ TEST_JSON(ExtendedOutputsSpec, names, R"(["a","b"])", (ExtendedOutputsSpec::Expl
|
|||
#undef TEST_JSON
|
||||
|
||||
}
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
Gen<OutputsSpec> Arbitrary<OutputsSpec>::arbitrary()
|
||||
{
|
||||
switch (*gen::inRange<uint8_t>(0, 1)) {
|
||||
case 0:
|
||||
return gen::just((OutputsSpec) OutputsSpec::All { });
|
||||
default:
|
||||
return gen::just((OutputsSpec) OutputsSpec::Names {
|
||||
*gen::nonEmpty(gen::container<StringSet>(gen::map(
|
||||
gen::arbitrary<StorePathName>(),
|
||||
[](StorePathName n) { return n.name; }))),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace nix {
|
||||
|
||||
RC_GTEST_PROP(
|
||||
OutputsSpec,
|
||||
prop_round_rip,
|
||||
(const OutputsSpec & o))
|
||||
{
|
||||
RC_ASSERT(o == OutputsSpec::parse(o.to_string()));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
17
src/libstore/tests/outputs-spec.hh
Normal file
17
src/libstore/tests/outputs-spec.hh
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <rapidcheck/gen/Arbitrary.h>
|
||||
|
||||
#include <outputs-spec.hh>
|
||||
|
||||
#include <tests/path.hh>
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<OutputsSpec> {
|
||||
static Gen<OutputsSpec> arbitrary();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@
|
|||
#include "path-regex.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include "libstoretests.hh"
|
||||
#include "tests/hash.hh"
|
||||
#include "tests/libstore.hh"
|
||||
#include "tests/path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -73,17 +75,14 @@ void showValue(const StorePath & p, std::ostream & os) {
|
|||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<StorePath> {
|
||||
static Gen<StorePath> arbitrary();
|
||||
};
|
||||
|
||||
Gen<StorePath> Arbitrary<StorePath>::arbitrary()
|
||||
Gen<StorePathName> Arbitrary<StorePathName>::arbitrary()
|
||||
{
|
||||
auto len = *gen::inRange<size_t>(1, StorePath::MaxPathLen);
|
||||
auto len = *gen::inRange<size_t>(
|
||||
1,
|
||||
StorePath::MaxPathLen - std::string_view { HASH_PART }.size());
|
||||
|
||||
std::string pre { HASH_PART "-" };
|
||||
pre.reserve(pre.size() + len);
|
||||
std::string pre;
|
||||
pre.reserve(len);
|
||||
|
||||
for (size_t c = 0; c < len; ++c) {
|
||||
switch (auto i = *gen::inRange<uint8_t>(0, 10 + 2 * 26 + 6)) {
|
||||
|
|
@ -118,7 +117,17 @@ Gen<StorePath> Arbitrary<StorePath>::arbitrary()
|
|||
}
|
||||
}
|
||||
|
||||
return gen::just(StorePath { pre });
|
||||
return gen::just(StorePathName {
|
||||
.name = std::move(pre),
|
||||
});
|
||||
}
|
||||
|
||||
Gen<StorePath> Arbitrary<StorePath>::arbitrary()
|
||||
{
|
||||
return gen::just(StorePath {
|
||||
*gen::arbitrary<Hash>(),
|
||||
(*gen::arbitrary<StorePathName>()).name,
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace rc
|
||||
|
|
|
|||
28
src/libstore/tests/path.hh
Normal file
28
src/libstore/tests/path.hh
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <rapidcheck/gen/Arbitrary.h>
|
||||
|
||||
#include <path.hh>
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct StorePathName {
|
||||
std::string name;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<StorePathName> {
|
||||
static Gen<StorePathName> arbitrary();
|
||||
};
|
||||
|
||||
template<>
|
||||
struct Arbitrary<StorePath> {
|
||||
static Gen<StorePath> arbitrary();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -324,7 +324,7 @@ MultiCommand::MultiCommand(const Commands & commands_)
|
|||
expectArgs({
|
||||
.label = "subcommand",
|
||||
.optional = true,
|
||||
.handler = {[=](std::string s) {
|
||||
.handler = {[=,this](std::string s) {
|
||||
assert(!command);
|
||||
auto i = commands.find(s);
|
||||
if (i == commands.end()) {
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
|||
.description = fmt("Set the `%s` setting.", name),
|
||||
.category = category,
|
||||
.labels = {"value"},
|
||||
.handler = {[=](std::string s) { overridden = true; set(s); }},
|
||||
.handler = {[this](std::string s) { overridden = true; set(s); }},
|
||||
});
|
||||
|
||||
if (isAppendable())
|
||||
|
|
@ -218,7 +218,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
|||
.description = fmt("Append to the `%s` setting.", name),
|
||||
.category = category,
|
||||
.labels = {"value"},
|
||||
.handler = {[=](std::string s) { overridden = true; set(s, true); }},
|
||||
.handler = {[this](std::string s) { overridden = true; set(s, true); }},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -270,13 +270,13 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string &
|
|||
.longName = name,
|
||||
.description = fmt("Enable the `%s` setting.", name),
|
||||
.category = category,
|
||||
.handler = {[=]() { override(true); }}
|
||||
.handler = {[this]() { override(true); }}
|
||||
});
|
||||
args.addFlag({
|
||||
.longName = "no-" + name,
|
||||
.description = fmt("Disable the `%s` setting.", name),
|
||||
.category = category,
|
||||
.handler = {[=]() { override(false); }}
|
||||
.handler = {[this]() { override(false); }}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -250,11 +250,15 @@ public:
|
|||
operator const T &() const { return value; }
|
||||
operator T &() { return value; }
|
||||
const T & get() const { return value; }
|
||||
bool operator ==(const T & v2) const { return value == v2; }
|
||||
bool operator !=(const T & v2) const { return value != v2; }
|
||||
void operator =(const T & v) { assign(v); }
|
||||
template<typename U>
|
||||
bool operator ==(const U & v2) const { return value == v2; }
|
||||
template<typename U>
|
||||
bool operator !=(const U & v2) const { return value != v2; }
|
||||
template<typename U>
|
||||
void operator =(const U & v) { assign(v); }
|
||||
virtual void assign(const T & v) { value = v; }
|
||||
void setDefault(const T & v) { if (!overridden) value = v; }
|
||||
template<typename U>
|
||||
void setDefault(const U & v) { if (!overridden) value = v; }
|
||||
|
||||
void set(const std::string & str, bool append = false) override;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
|
|||
{ Xp::ReplFlake, "repl-flake" },
|
||||
{ Xp::AutoAllocateUids, "auto-allocate-uids" },
|
||||
{ Xp::Cgroups, "cgroups" },
|
||||
{ Xp::DiscardReferences, "discard-references" },
|
||||
};
|
||||
|
||||
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ enum struct ExperimentalFeature
|
|||
ReplFlake,
|
||||
AutoAllocateUids,
|
||||
Cgroups,
|
||||
DiscardReferences,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
100
src/libutil/namespaces.cc
Normal file
100
src/libutil/namespaces.cc
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#if __linux__
|
||||
|
||||
#include "namespaces.hh"
|
||||
#include "util.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
#include <mntent.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
bool userNamespacesSupported()
|
||||
{
|
||||
static auto res = [&]() -> bool
|
||||
{
|
||||
if (!pathExists("/proc/self/ns/user")) {
|
||||
debug("'/proc/self/ns/user' does not exist; your kernel was likely built without CONFIG_USER_NS=y");
|
||||
return false;
|
||||
}
|
||||
|
||||
Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
|
||||
if (!pathExists(maxUserNamespaces) ||
|
||||
trim(readFile(maxUserNamespaces)) == "0")
|
||||
{
|
||||
debug("user namespaces appear to be disabled; check '/proc/sys/user/max_user_namespaces'");
|
||||
return false;
|
||||
}
|
||||
|
||||
Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
|
||||
if (pathExists(procSysKernelUnprivilegedUsernsClone)
|
||||
&& trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0")
|
||||
{
|
||||
debug("user namespaces appear to be disabled; check '/proc/sys/kernel/unprivileged_userns_clone'");
|
||||
return false;
|
||||
}
|
||||
|
||||
Pid pid = startProcess([&]()
|
||||
{
|
||||
auto res = unshare(CLONE_NEWUSER);
|
||||
_exit(res ? 1 : 0);
|
||||
});
|
||||
|
||||
bool supported = pid.wait() == 0;
|
||||
|
||||
if (!supported)
|
||||
debug("user namespaces do not work on this system");
|
||||
|
||||
return supported;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool mountNamespacesSupported()
|
||||
{
|
||||
static auto res = [&]() -> bool
|
||||
{
|
||||
bool useUserNamespace = userNamespacesSupported();
|
||||
|
||||
Pid pid = startProcess([&]()
|
||||
{
|
||||
auto res = unshare(CLONE_NEWNS | (useUserNamespace ? CLONE_NEWUSER : 0));
|
||||
_exit(res ? 1 : 0);
|
||||
});
|
||||
|
||||
bool supported = pid.wait() == 0;
|
||||
|
||||
if (!supported)
|
||||
debug("mount namespaces do not work on this system");
|
||||
|
||||
return supported;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
bool pidNamespacesSupported()
|
||||
{
|
||||
static auto res = [&]() -> bool
|
||||
{
|
||||
/* Check whether /proc is fully visible, i.e. there are no
|
||||
filesystems mounted on top of files inside /proc. If this
|
||||
is not the case, then we cannot mount a new /proc inside
|
||||
the sandbox that matches the sandbox's PID namespace.
|
||||
See https://lore.kernel.org/lkml/87tvsrjai0.fsf@xmission.com/T/. */
|
||||
auto fp = fopen("/proc/mounts", "r");
|
||||
if (!fp) return false;
|
||||
Finally delFP = [&]() { fclose(fp); };
|
||||
|
||||
while (auto ent = getmntent(fp))
|
||||
if (hasPrefix(std::string_view(ent->mnt_dir), "/proc/")) {
|
||||
debug("PID namespaces do not work because /proc is not fully visible; disabling sandboxing");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}();
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
15
src/libutil/namespaces.hh
Normal file
15
src/libutil/namespaces.hh
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
namespace nix {
|
||||
|
||||
#if __linux__
|
||||
|
||||
bool userNamespacesSupported();
|
||||
|
||||
bool mountNamespacesSupported();
|
||||
|
||||
bool pidNamespacesSupported();
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
#include "hash.hh"
|
||||
#include <regex>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <rapidcheck/gtest.h>
|
||||
|
||||
#include <hash.hh>
|
||||
|
||||
#include "tests/hash.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
|
@ -73,3 +80,16 @@ namespace nix {
|
|||
"c7d329eeb6dd26545e96e55b874be909");
|
||||
}
|
||||
}
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
Gen<Hash> Arbitrary<Hash>::arbitrary()
|
||||
{
|
||||
Hash hash(htSHA1);
|
||||
for (size_t i = 0; i < hash.hashSize; ++i)
|
||||
hash.hash[i] = *gen::arbitrary<uint8_t>();
|
||||
return gen::just(hash);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
15
src/libutil/tests/hash.hh
Normal file
15
src/libutil/tests/hash.hh
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <rapidcheck/gen/Arbitrary.h>
|
||||
|
||||
#include <hash.hh>
|
||||
|
||||
namespace rc {
|
||||
using namespace nix;
|
||||
|
||||
template<>
|
||||
struct Arbitrary<Hash> {
|
||||
static Gen<Hash> arbitrary();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -2,14 +2,28 @@ check: libutil-tests_RUN
|
|||
|
||||
programs += libutil-tests
|
||||
|
||||
libutil-tests-exe_NAME = libnixutil-tests
|
||||
|
||||
libutil-tests-exe_DIR := $(d)
|
||||
|
||||
libutil-tests-exe_INSTALL_DIR :=
|
||||
|
||||
libutil-tests-exe_LIBS = libutil-tests
|
||||
|
||||
libutil-tests-exe_LDFLAGS := $(GTEST_LIBS)
|
||||
|
||||
libraries += libutil-tests
|
||||
|
||||
libutil-tests_NAME = libnixutil-tests
|
||||
|
||||
libutil-tests_DIR := $(d)
|
||||
|
||||
libutil-tests_INSTALL_DIR :=
|
||||
|
||||
libutil-tests_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
libutil-tests_CXXFLAGS += -I src/libutil -I src/libexpr
|
||||
libutil-tests_CXXFLAGS += -I src/libutil
|
||||
|
||||
libutil-tests_LIBS = libutil
|
||||
|
||||
libutil-tests_LDFLAGS := $(GTEST_LIBS)
|
||||
libutil-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS)
|
||||
|
|
|
|||
|
|
@ -99,6 +99,27 @@ namespace nix {
|
|||
ASSERT_EQ(parsed, expected);
|
||||
}
|
||||
|
||||
TEST(parseURL, parsesFilePlusHttpsUrl) {
|
||||
auto s = "file+https://www.example.org/video.mp4";
|
||||
auto parsed = parseURL(s);
|
||||
|
||||
ParsedURL expected {
|
||||
.url = "file+https://www.example.org/video.mp4",
|
||||
.base = "https://www.example.org/video.mp4",
|
||||
.scheme = "file+https",
|
||||
.authority = "www.example.org",
|
||||
.path = "/video.mp4",
|
||||
.query = (StringMap) { },
|
||||
.fragment = "",
|
||||
};
|
||||
|
||||
ASSERT_EQ(parsed, expected);
|
||||
}
|
||||
|
||||
TEST(parseURL, rejectsAuthorityInUrlsWithFileTransportation) {
|
||||
auto s = "file://www.example.org/video.mp4";
|
||||
ASSERT_THROW(parseURL(s), Error);
|
||||
}
|
||||
|
||||
TEST(parseURL, parseIPv4Address) {
|
||||
auto s = "http://127.0.0.1:8080/file.tar.gz?download=fast&when=now#hello";
|
||||
|
|
|
|||
|
|
@ -30,13 +30,13 @@ ParsedURL parseURL(const std::string & url)
|
|||
auto & query = match[6];
|
||||
auto & fragment = match[7];
|
||||
|
||||
auto isFile = scheme.find("file") != std::string::npos;
|
||||
auto transportIsFile = parseUrlScheme(scheme).transport == "file";
|
||||
|
||||
if (authority && *authority != "" && isFile)
|
||||
if (authority && *authority != "" && transportIsFile)
|
||||
throw BadURL("file:// URL '%s' has unexpected authority '%s'",
|
||||
url, *authority);
|
||||
|
||||
if (isFile && path.empty())
|
||||
if (transportIsFile && path.empty())
|
||||
path = "/";
|
||||
|
||||
return ParsedURL{
|
||||
|
|
|
|||
|
|
@ -537,6 +537,16 @@ std::string getUserName()
|
|||
return name;
|
||||
}
|
||||
|
||||
Path getHomeOf(uid_t userId)
|
||||
{
|
||||
std::vector<char> buf(16384);
|
||||
struct passwd pwbuf;
|
||||
struct passwd * pw;
|
||||
if (getpwuid_r(userId, &pwbuf, buf.data(), buf.size(), &pw) != 0
|
||||
|| !pw || !pw->pw_dir || !pw->pw_dir[0])
|
||||
throw Error("cannot determine user's home directory");
|
||||
return pw->pw_dir;
|
||||
}
|
||||
|
||||
Path getHome()
|
||||
{
|
||||
|
|
@ -558,13 +568,7 @@ Path getHome()
|
|||
}
|
||||
}
|
||||
if (!homeDir) {
|
||||
std::vector<char> buf(16384);
|
||||
struct passwd pwbuf;
|
||||
struct passwd * pw;
|
||||
if (getpwuid_r(geteuid(), &pwbuf, buf.data(), buf.size(), &pw) != 0
|
||||
|| !pw || !pw->pw_dir || !pw->pw_dir[0])
|
||||
throw Error("cannot determine user's home directory");
|
||||
homeDir = pw->pw_dir;
|
||||
homeDir = getHomeOf(geteuid());
|
||||
if (unownedUserHomeDir.has_value() && unownedUserHomeDir != homeDir) {
|
||||
warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file ('%s')", *unownedUserHomeDir, *homeDir);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ void deletePath(const Path & path, uint64_t & bytesFreed);
|
|||
|
||||
std::string getUserName();
|
||||
|
||||
/* Return the given user's home directory from /etc/passwd. */
|
||||
Path getHomeOf(uid_t userId);
|
||||
|
||||
/* Return $HOME or the user's home directory from /etc/passwd. */
|
||||
Path getHome();
|
||||
|
||||
|
|
|
|||
|
|
@ -541,7 +541,9 @@ static void main_nix_build(int argc, char * * argv)
|
|||
"SHELL=%5%; "
|
||||
"BASH=%5%; "
|
||||
"set +e; "
|
||||
R"s([ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '; )s"
|
||||
R"s([ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && )s" +
|
||||
(getuid() == 0 ? R"s(PS1='\n\[\033[1;31m\][nix-shell:\w]\$\[\033[0m\] '; )s"
|
||||
: R"s(PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '; )s") +
|
||||
"if [ \"$(type -t runHook)\" = function ]; then runHook shellHook; fi; "
|
||||
"unset NIX_ENFORCE_PURITY; "
|
||||
"shopt -u nullglob; "
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include "profiles.hh"
|
||||
#include "shared.hh"
|
||||
#include "globals.hh"
|
||||
#include "filetransfer.hh"
|
||||
#include "store-api.hh"
|
||||
#include "legacy.hh"
|
||||
#include "util.hh"
|
||||
#include "tarball.hh"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
|
@ -166,7 +168,7 @@ static int main_nix_channel(int argc, char ** argv)
|
|||
nixDefExpr = home + "/.nix-defexpr";
|
||||
|
||||
// Figure out the name of the channels profile.
|
||||
profile = fmt("%s/profiles/per-user/%s/channels", settings.nixStateDir, getUserName());
|
||||
profile = profilesDir() + "/channels";
|
||||
|
||||
enum {
|
||||
cNone,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,43 @@
|
|||
using namespace nix;
|
||||
using namespace nix::daemon;
|
||||
|
||||
struct UserSettings : Config {
|
||||
|
||||
Setting<Strings> trustedUsers{
|
||||
this, {"root"}, "trusted-users",
|
||||
R"(
|
||||
A list of names of users (separated by whitespace) that have
|
||||
additional rights when connecting to the Nix daemon, such as the
|
||||
ability to specify additional binary caches, or to import unsigned
|
||||
NARs. You can also specify groups by prefixing them with `@`; for
|
||||
instance, `@wheel` means all users in the `wheel` group. The default
|
||||
is `root`.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> Adding a user to `trusted-users` is essentially equivalent to
|
||||
> giving that user root access to the system. For example, the user
|
||||
> can set `sandbox-paths` and thereby obtain read access to
|
||||
> directories that are otherwise inacessible to them.
|
||||
)"};
|
||||
|
||||
/* ?Who we trust to use the daemon in safe ways */
|
||||
Setting<Strings> allowedUsers{
|
||||
this, {"*"}, "allowed-users",
|
||||
R"(
|
||||
A list of names of users (separated by whitespace) that are allowed
|
||||
to connect to the Nix daemon. As with the `trusted-users` option,
|
||||
you can specify groups by prefixing them with `@`. Also, you can
|
||||
allow all users by specifying `*`. The default is `*`.
|
||||
|
||||
Note that trusted users are always allowed to connect.
|
||||
)"};
|
||||
};
|
||||
|
||||
UserSettings userSettings;
|
||||
|
||||
static GlobalConfig::Register rSettings(&userSettings);
|
||||
|
||||
#ifndef __linux__
|
||||
#define SPLICE_F_MOVE 0
|
||||
static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags)
|
||||
|
|
@ -203,8 +240,8 @@ static void daemonLoop()
|
|||
struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0;
|
||||
std::string group = gr ? gr->gr_name : std::to_string(peer.gid);
|
||||
|
||||
Strings trustedUsers = settings.trustedUsers;
|
||||
Strings allowedUsers = settings.allowedUsers;
|
||||
Strings trustedUsers = userSettings.trustedUsers;
|
||||
Strings allowedUsers = userSettings.allowedUsers;
|
||||
|
||||
if (matchUser(user, group, trustedUsers))
|
||||
trusted = Trusted;
|
||||
|
|
@ -241,15 +278,7 @@ static void daemonLoop()
|
|||
// Handle the connection.
|
||||
FdSource from(remote.get());
|
||||
FdSink to(remote.get());
|
||||
processConnection(openUncachedStore(), from, to, trusted, NotRecursive, [&](Store & store) {
|
||||
#if 0
|
||||
/* Prevent users from doing something very dangerous. */
|
||||
if (geteuid() == 0 &&
|
||||
querySetting("build-users-group", "") == "")
|
||||
throw Error("if you run 'nix-daemon' as root, then you MUST set 'build-users-group'!");
|
||||
#endif
|
||||
store.createUser(user, peer.uid);
|
||||
});
|
||||
processConnection(openUncachedStore(), from, to, trusted, NotRecursive);
|
||||
|
||||
exit(0);
|
||||
}, options);
|
||||
|
|
@ -302,7 +331,7 @@ static void runDaemon(bool stdio)
|
|||
/* 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, [&](Store & _){});
|
||||
processConnection(openUncachedStore(), from, to, Trusted, NotRecursive);
|
||||
}
|
||||
} else
|
||||
daemonLoop();
|
||||
|
|
|
|||
|
|
@ -954,6 +954,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON
|
|||
struct CmdFlakeShow : FlakeCommand, MixJSON
|
||||
{
|
||||
bool showLegacy = false;
|
||||
bool showAllSystems = false;
|
||||
|
||||
CmdFlakeShow()
|
||||
{
|
||||
|
|
@ -962,6 +963,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
.description = "Show the contents of the `legacyPackages` output.",
|
||||
.handler = {&showLegacy, true}
|
||||
});
|
||||
addFlag({
|
||||
.longName = "all-systems",
|
||||
.description = "Show the contents of outputs for all systems.",
|
||||
.handler = {&showAllSystems, true}
|
||||
});
|
||||
}
|
||||
|
||||
std::string description() override
|
||||
|
|
@ -982,6 +988,62 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
|
||||
auto state = getEvalState();
|
||||
auto flake = std::make_shared<LockedFlake>(lockFlake());
|
||||
auto localSystem = std::string(settings.thisSystem.get());
|
||||
|
||||
std::function<bool(
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> &attrPath,
|
||||
const Symbol &attr)> hasContent;
|
||||
|
||||
// For frameworks it's important that structures are as lazy as possible
|
||||
// to prevent infinite recursions, performance issues and errors that
|
||||
// aren't related to the thing to evaluate. As a consequence, they have
|
||||
// to emit more attributes than strictly (sic) necessary.
|
||||
// However, these attributes with empty values are not useful to the user
|
||||
// so we omit them.
|
||||
hasContent = [&](
|
||||
eval_cache::AttrCursor & visitor,
|
||||
const std::vector<Symbol> &attrPath,
|
||||
const Symbol &attr) -> bool
|
||||
{
|
||||
auto attrPath2(attrPath);
|
||||
attrPath2.push_back(attr);
|
||||
auto attrPathS = state->symbols.resolve(attrPath2);
|
||||
const auto & attrName = state->symbols[attr];
|
||||
|
||||
auto visitor2 = visitor.getAttr(attrName);
|
||||
|
||||
if ((attrPathS[0] == "apps"
|
||||
|| attrPathS[0] == "checks"
|
||||
|| attrPathS[0] == "devShells"
|
||||
|| attrPathS[0] == "legacyPackages"
|
||||
|| attrPathS[0] == "packages")
|
||||
&& (attrPathS.size() == 1 || attrPathS.size() == 2)) {
|
||||
for (const auto &subAttr : visitor2->getAttrs()) {
|
||||
if (hasContent(*visitor2, attrPath2, subAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((attrPathS.size() == 1)
|
||||
&& (attrPathS[0] == "formatter"
|
||||
|| attrPathS[0] == "nixosConfigurations"
|
||||
|| attrPathS[0] == "nixosModules"
|
||||
|| attrPathS[0] == "overlays"
|
||||
)) {
|
||||
for (const auto &subAttr : visitor2->getAttrs()) {
|
||||
if (hasContent(*visitor2, attrPath2, subAttr)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we don't recognize it, it's probably content
|
||||
return true;
|
||||
};
|
||||
|
||||
std::function<nlohmann::json(
|
||||
eval_cache::AttrCursor & visitor,
|
||||
|
|
@ -1008,7 +1070,12 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
{
|
||||
if (!json)
|
||||
logger->cout("%s", headerPrefix);
|
||||
auto attrs = visitor.getAttrs();
|
||||
std::vector<Symbol> attrs;
|
||||
for (const auto &attr : visitor.getAttrs()) {
|
||||
if (hasContent(visitor, attrPath, attr))
|
||||
attrs.push_back(attr);
|
||||
}
|
||||
|
||||
for (const auto & [i, attr] : enumerate(attrs)) {
|
||||
const auto & attrName = state->symbols[attr];
|
||||
bool last = i + 1 == attrs.size();
|
||||
|
|
@ -1072,10 +1139,18 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
|| (attrPath.size() == 3 && (attrPathS[0] == "checks" || attrPathS[0] == "packages" || attrPathS[0] == "devShells"))
|
||||
)
|
||||
{
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else
|
||||
throw Error("expected a derivation");
|
||||
if (!showAllSystems && std::string(attrPathS[1]) != localSystem) {
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS)));
|
||||
}
|
||||
} else {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
else
|
||||
throw Error("expected a derivation");
|
||||
}
|
||||
}
|
||||
|
||||
else if (attrPath.size() > 0 && attrPathS[0] == "hydraJobs") {
|
||||
|
|
@ -1094,6 +1169,12 @@ struct CmdFlakeShow : FlakeCommand, MixJSON
|
|||
else {
|
||||
logger->warn(fmt("%s omitted (use '--legacy' to show)", concatStringsSep(".", attrPathS)));
|
||||
}
|
||||
} else if (!showAllSystems && std::string(attrPathS[1]) != localSystem) {
|
||||
if (!json)
|
||||
logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix));
|
||||
else {
|
||||
logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS)));
|
||||
}
|
||||
} else {
|
||||
if (visitor.isDerivation())
|
||||
showDerivation();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
#include "command.hh"
|
||||
#include "shared.hh"
|
||||
#include "store-api.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace nix;
|
||||
|
||||
struct CmdPingStore : StoreCommand
|
||||
struct CmdPingStore : StoreCommand, MixJSON
|
||||
{
|
||||
std::string description() override
|
||||
{
|
||||
|
|
@ -20,10 +23,21 @@ struct CmdPingStore : StoreCommand
|
|||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
notice("Store URL: %s", store->getUri());
|
||||
store->connect();
|
||||
if (auto version = store->getVersion())
|
||||
notice("Version: %s", *version);
|
||||
if (!json) {
|
||||
notice("Store URL: %s", store->getUri());
|
||||
store->connect();
|
||||
if (auto version = store->getVersion())
|
||||
notice("Version: %s", *version);
|
||||
} else {
|
||||
nlohmann::json res;
|
||||
Finally printRes([&]() {
|
||||
logger->cout("%s", res);
|
||||
});
|
||||
res["url"] = store->getUri();
|
||||
store->connect();
|
||||
if (auto version = store->getVersion())
|
||||
res["version"] = *version;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ struct CmdSearch : InstallableCommand, MixJSON
|
|||
Strings getDefaultFlakeAttrPaths() override
|
||||
{
|
||||
return {
|
||||
"packages." + settings.thisSystem.get() + ".",
|
||||
"legacyPackages." + settings.thisSystem.get() + "."
|
||||
"packages." + settings.thisSystem.get(),
|
||||
"legacyPackages." + settings.thisSystem.get()
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,14 +81,14 @@ struct CmdVerify : StorePathsCommand
|
|||
|
||||
ThreadPool pool;
|
||||
|
||||
auto doPath = [&](const Path & storePath) {
|
||||
auto doPath = [&](const StorePath & storePath) {
|
||||
try {
|
||||
checkInterrupt();
|
||||
|
||||
MaintainCount<std::atomic<size_t>> mcActive(active);
|
||||
update();
|
||||
|
||||
auto info = store->queryPathInfo(store->parseStorePath(storePath));
|
||||
auto info = store->queryPathInfo(storePath);
|
||||
|
||||
// Note: info->path can be different from storePath
|
||||
// for binary cache stores when using --all (since we
|
||||
|
|
@ -173,7 +173,7 @@ struct CmdVerify : StorePathsCommand
|
|||
};
|
||||
|
||||
for (auto & storePath : storePaths)
|
||||
pool.enqueue(std::bind(doPath, store->printStorePath(storePath)));
|
||||
pool.enqueue(std::bind(doPath, storePath));
|
||||
|
||||
pool.process();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue