1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-11-16 23:42:43 +01:00

Merge remote-tracking branch 'upstream/master' into path-info

This commit is contained in:
John Ericson 2023-02-28 11:34:34 -05:00
commit d12f57c2c0
132 changed files with 2799 additions and 1267 deletions

View file

@ -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 (!mountAndPidNamespacesSupported()) {
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);
@ -372,12 +385,6 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
}
int childEntry(void * arg)
{
((LocalDerivationGoal *) arg)->runChild();
return 1;
}
#if __linux__
static void linkOrCopy(const Path & from, const Path & to)
{
@ -663,7 +670,8 @@ void LocalDerivationGoal::startBuilder()
nobody account. The latter is kind of a hack to support
Samba-in-QEMU. */
createDirs(chrootRootDir + "/etc");
chownToBuilder(chrootRootDir + "/etc");
if (parsedDrv->useUidRange())
chownToBuilder(chrootRootDir + "/etc");
if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536))
throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name);
@ -888,12 +896,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([&]() {
@ -908,76 +911,21 @@ void LocalDerivationGoal::startBuilder()
if (getuid() == 0 && setgroups(0, 0) == -1)
throw SysError("setgroups failed");
size_t stackSize = 1 * 1024 * 1024;
char * stack = (char *) mmap(0, stackSize,
PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
if (stack == MAP_FAILED) throw SysError("allocating stack");
int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
ProcessOptions options;
options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
if (privateNetwork)
flags |= CLONE_NEWNET;
options.cloneFlags |= CLONE_NEWNET;
if (usingUserNamespace)
flags |= CLONE_NEWUSER;
options.cloneFlags |= CLONE_NEWUSER;
pid_t child = startProcess([&]() { runChild(); }, options);
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()");
}
}
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 +993,6 @@ void LocalDerivationGoal::startBuilder()
} else
#endif
{
#if __linux__
fallback:
#endif
pid = startProcess([&]() {
runChild();
});
@ -1516,8 +1461,7 @@ void LocalDerivationGoal::startDaemon()
FdSink to(remote.get());
try {
daemon::processConnection(store, from, to,
daemon::NotTrusted, daemon::Recursive,
[&](Store & store) {});
daemon::NotTrusted, daemon::Recursive);
debug("terminated daemon connection");
} catch (SysError &) {
ignoreException();
@ -1907,6 +1851,10 @@ void LocalDerivationGoal::runChild()
}
}
/* Make /etc unwritable */
if (!parsedDrv->useUidRange())
chmod_(chrootRootDir + "/etc", 0555);
/* Unshare this mount namespace. This is necessary because
pivot_root() below changes the root of the mount
namespace. This means that the call to setns() in

View file

@ -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();

View file

@ -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);
}

View file

@ -5,6 +5,7 @@
#include "worker-protocol.hh"
#include "fs-accessor.hh"
#include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp>
namespace nix {
@ -890,4 +891,63 @@ std::optional<BasicDerivation> Derivation::tryResolve(
const Hash impureOutputHash = hashString(htSHA256, "impure");
nlohmann::json DerivationOutput::toJSON(
const Store & store, std::string_view drvName, std::string_view outputName) const
{
nlohmann::json res = nlohmann::json::object();
std::visit(overloaded {
[&](const DerivationOutput::InputAddressed & doi) {
res["path"] = store.printStorePath(doi.path);
},
[&](const DerivationOutput::CAFixed & dof) {
res["path"] = store.printStorePath(dof.path(store, drvName, outputName));
res["hashAlgo"] = dof.hash.printMethodAlgo();
res["hash"] = dof.hash.hash.to_string(Base16, false);
},
[&](const DerivationOutput::CAFloating & dof) {
res["hashAlgo"] = makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType);
},
[&](const DerivationOutput::Deferred &) {},
[&](const DerivationOutput::Impure & doi) {
res["hashAlgo"] = makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType);
res["impure"] = true;
},
}, raw());
return res;
}
nlohmann::json Derivation::toJSON(const Store & store) const
{
nlohmann::json res = nlohmann::json::object();
{
nlohmann::json & outputsObj = res["outputs"];
outputsObj = nlohmann::json::object();
for (auto & [outputName, output] : outputs) {
outputsObj[outputName] = output.toJSON(store, name, outputName);
}
}
{
auto& inputsList = res["inputSrcs"];
inputsList = nlohmann::json ::array();
for (auto & input : inputSrcs)
inputsList.emplace_back(store.printStorePath(input));
}
{
auto& inputDrvsObj = res["inputDrvs"];
inputDrvsObj = nlohmann::json ::object();
for (auto & input : inputDrvs)
inputDrvsObj[store.printStorePath(input.first)] = input.second;
}
res["system"] = platform;
res["builder"] = builder;
res["args"] = args;
res["env"] = env;
return res;
}
}

View file

@ -83,6 +83,11 @@ struct DerivationOutput : _DerivationOutputRaw
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
nlohmann::json toJSON(
const Store & store,
std::string_view drvName,
std::string_view outputName) const;
};
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
@ -210,6 +215,8 @@ struct Derivation : BasicDerivation
Derivation() = default;
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { }
nlohmann::json toJSON(const Store & store) const;
};

View file

@ -101,6 +101,7 @@ struct curlFileTransfer : public FileTransfer
this->result.data.append(data);
})
{
requestHeaders = curl_slist_append(requestHeaders, "Accept-Encoding: zstd, br, gzip, deflate, bzip2, xz");
if (!request.expectedETag.empty())
requestHeaders = curl_slist_append(requestHeaders, ("If-None-Match: " + request.expectedETag).c_str());
if (!request.mimeType.empty())
@ -828,7 +829,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
{
auto state(_state->lock());
while (state->data.empty()) {
if (state->data.empty()) {
if (state->quit) {
if (state->exc) std::rethrow_exception(state->exc);
@ -836,6 +837,8 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink)
}
state.wait(state->avail);
if (state->data.empty()) continue;
}
chunk = std::move(state->data);

View file

@ -201,7 +201,16 @@ public:
{"build-timeout"}};
PathSetting buildHook{this, true, "", "build-hook",
"The path of the helper program that executes builds to remote machines."};
R"(
The path to the helper program that executes remote builds.
Nix communicates with the build hook over `stdio` using a custom protocol to request builds that cannot be performed directly by the Nix daemon.
The default value is the internal Nix binary that implements remote building.
> **Important**
>
> Change this setting only if you really know what youre doing.
)"};
Setting<std::string> builders{
this, "@" + nixConfDir + "/machines", "builders",
@ -279,8 +288,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 +705,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 +727,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."};
@ -975,6 +954,27 @@ public:
resolves to a different location from that of the build machine. You
can enable this setting if you are sure you're not going to do that.
)"};
Setting<bool> useXDGBaseDirectories{
this, false, "use-xdg-base-directories",
R"(
If set to `true`, Nix will conform to the [XDG Base Directory Specification] for files in `$HOME`.
The environment variables used to implement this are documented in the [Environment Variables section](@docroot@/installation/env-variables.md).
[XDG Base Directory Specification]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
> **Warning**
> This changes the location of some well-known symlinks that Nix creates, which might break tools that rely on the old, non-XDG-conformant locations.
In particular, the following locations change:
| Old | New |
|-------------------|--------------------------------|
| `~/.nix-profile` | `$XDG_STATE_HOME/nix/profile` |
| `~/.nix-defexpr` | `$XDG_STATE_HOME/nix/defexpr` |
| `~/.nix-channels` | `$XDG_STATE_HOME/nix/channels` |
)"
};
};

View file

@ -56,7 +56,7 @@ public:
void init() override
{
// FIXME: do this lazily?
if (auto cacheInfo = diskCache->cacheExists(cacheUri)) {
if (auto cacheInfo = diskCache->upToDateCacheExists(cacheUri)) {
wantMassQuery.setDefault(cacheInfo->wantMassQuery);
priority.setDefault(cacheInfo->priority);
} else {

View file

@ -84,11 +84,10 @@ public:
Sync<State> _state;
NarInfoDiskCacheImpl()
NarInfoDiskCacheImpl(Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite")
{
auto state(_state.lock());
Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite";
createDirs(dirOf(dbPath));
state->db = SQLite(dbPath);
@ -98,7 +97,7 @@ public:
state->db.exec(schema);
state->insertCache.create(state->db,
"insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)");
"insert into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?1, ?2, ?3, ?4, ?5) on conflict (url) do update set timestamp = ?2, storeDir = ?3, wantMassQuery = ?4, priority = ?5 returning id;");
state->queryCache.create(state->db,
"select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ? and timestamp > ?");
@ -166,6 +165,8 @@ public:
return i->second;
}
private:
std::optional<Cache> queryCacheRaw(State & state, const std::string & uri)
{
auto i = state.caches.find(uri);
@ -173,15 +174,21 @@ public:
auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl));
if (!queryCache.next())
return std::nullopt;
state.caches.emplace(uri,
Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)});
auto cache = Cache {
.id = (int) queryCache.getInt(0),
.storeDir = queryCache.getStr(1),
.wantMassQuery = queryCache.getInt(2) != 0,
.priority = (int) queryCache.getInt(3),
};
state.caches.emplace(uri, cache);
}
return getCache(state, uri);
}
void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
public:
int createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override
{
retrySQLite<void>([&]() {
return retrySQLite<int>([&]() {
auto state(_state.lock());
SQLiteTxn txn(state->db);
@ -190,17 +197,29 @@ public:
auto cache(queryCacheRaw(*state, uri));
if (cache)
return;
return cache->id;
state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec();
assert(sqlite3_changes(state->db) == 1);
state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority};
Cache ret {
.id = -1, // set below
.storeDir = storeDir,
.wantMassQuery = wantMassQuery,
.priority = priority,
};
{
auto r(state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority));
assert(r.next());
ret.id = (int) r.getInt(0);
}
state->caches[uri] = ret;
txn.commit();
return ret.id;
});
}
std::optional<CacheInfo> cacheExists(const std::string & uri) override
std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) override
{
return retrySQLite<std::optional<CacheInfo>>([&]() -> std::optional<CacheInfo> {
auto state(_state.lock());
@ -208,6 +227,7 @@ public:
if (!cache)
return std::nullopt;
return CacheInfo {
.id = cache->id,
.wantMassQuery = cache->wantMassQuery,
.priority = cache->priority
};
@ -371,4 +391,9 @@ ref<NarInfoDiskCache> getNarInfoDiskCache()
return cache;
}
ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath)
{
return make_ref<NarInfoDiskCacheImpl>(dbPath);
}
}

View file

@ -13,16 +13,17 @@ public:
virtual ~NarInfoDiskCache() { }
virtual void createCache(const std::string & uri, const Path & storeDir,
virtual int createCache(const std::string & uri, const Path & storeDir,
bool wantMassQuery, int priority) = 0;
struct CacheInfo
{
int id;
bool wantMassQuery;
int priority;
};
virtual std::optional<CacheInfo> cacheExists(const std::string & uri) = 0;
virtual std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) = 0;
virtual std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo(
const std::string & uri, const std::string & hashPart) = 0;
@ -45,4 +46,6 @@ public:
multiple threads. */
ref<NarInfoDiskCache> getNarInfoDiskCache();
ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath);
}

View file

@ -6,4 +6,4 @@ Name: Nix
Description: Nix Package Manager
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixstore -lnixutil
Cflags: -I${includedir}/nix -std=c++20
Cflags: -I${includedir}/nix -std=c++2a

View file

@ -282,7 +282,7 @@ std::string optimisticLockProfile(const Path & profile)
Path profilesDir()
{
auto profileRoot = getDataDir() + "/nix/profiles";
auto profileRoot = createNixStateDir() + "/profiles";
createDirs(profileRoot);
return profileRoot;
}
@ -290,7 +290,7 @@ Path profilesDir()
Path getDefaultProfile()
{
Path profileLink = getHome() + "/.nix-profile";
Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile";
try {
auto profile =
getuid() == 0

View file

@ -72,8 +72,9 @@ std::string optimisticLockProfile(const Path & profile);
profiles. */
Path profilesDir();
/* Resolve ~/.nix-profile. If ~/.nix-profile doesn't exist yet, create
it. */
/* Resolve the default profile (~/.nix-profile by default, $XDG_STATE_HOME/
nix/profile if XDG Base Directory Support is enabled), and create if doesn't
exist */
Path getDefaultProfile();
}

View file

@ -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;

View file

@ -238,7 +238,7 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
void init() override
{
if (auto cacheInfo = diskCache->cacheExists(getUri())) {
if (auto cacheInfo = diskCache->upToDateCacheExists(getUri())) {
wantMassQuery.setDefault(cacheInfo->wantMassQuery);
priority.setDefault(cacheInfo->priority);
} else {

View file

@ -41,6 +41,15 @@ SQLiteError::SQLiteError(const char *path, const char *errMsg, int errNo, int ex
throw SQLiteError(path, errMsg, err, exterr, offset, std::move(hf));
}
static void traceSQL(void * x, const char * sql)
{
// wacky delimiters:
// so that we're quite unambiguous without escaping anything
// notice instead of trace:
// so that this can be enabled without getting the firehose in our face.
notice("SQL<[%1%]>", sql);
};
SQLite::SQLite(const Path & path, bool create)
{
// useSQLiteWAL also indicates what virtual file system we need. Using
@ -58,6 +67,11 @@ SQLite::SQLite(const Path & path, bool create)
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
SQLiteError::throw_(db, "setting timeout");
if (getEnv("NIX_DEBUG_SQLITE_TRACES") == "1") {
// To debug sqlite statements; trace all of them
sqlite3_trace(db, &traceSQL, nullptr);
}
exec("pragma foreign_keys = 1");
}

View file

@ -0,0 +1,124 @@
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include "derivations.hh"
#include "tests/libstore.hh"
namespace nix {
class DerivationTest : public LibStoreTest
{
};
#define TEST_JSON(TYPE, NAME, STR, VAL, ...) \
TEST_F(DerivationTest, TYPE ## _ ## NAME ## _to_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
STR ## _json, \
(TYPE { VAL }).toJSON(*store __VA_OPT__(,) __VA_ARGS__)); \
}
TEST_JSON(DerivationOutput, inputAddressed,
R"({
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
})",
(DerivationOutput::InputAddressed {
.path = store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"),
}),
"drv-name", "output-name")
TEST_JSON(DerivationOutput, caFixed,
R"({
"hashAlgo": "r:sha256",
"hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f",
"path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"
})",
(DerivationOutput::CAFixed {
.hash = {
.method = FileIngestionMethod::Recursive,
.hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="),
},
}),
"drv-name", "output-name")
TEST_JSON(DerivationOutput, caFloating,
R"({
"hashAlgo": "r:sha256"
})",
(DerivationOutput::CAFloating {
.method = FileIngestionMethod::Recursive,
.hashType = htSHA256,
}),
"drv-name", "output-name")
TEST_JSON(DerivationOutput, deferred,
R"({ })",
DerivationOutput::Deferred { },
"drv-name", "output-name")
TEST_JSON(DerivationOutput, impure,
R"({
"hashAlgo": "r:sha256",
"impure": true
})",
(DerivationOutput::Impure {
.method = FileIngestionMethod::Recursive,
.hashType = htSHA256,
}),
"drv-name", "output-name")
TEST_JSON(Derivation, impure,
R"({
"inputSrcs": [
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
],
"inputDrvs": {
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": [
"cat",
"dog"
]
},
"system": "wasm-sel4",
"builder": "foo",
"args": [
"bar",
"baz"
],
"env": {
"BIG_BAD": "WOLF"
},
"outputs": {}
})",
({
Derivation drv;
drv.inputSrcs = {
store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"),
};
drv.inputDrvs = {
{
store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"),
{
"cat",
"dog",
},
}
};
drv.platform = "wasm-sel4";
drv.builder = "foo";
drv.args = {
"bar",
"baz",
};
drv.env = {
{
"BIG_BAD",
"WOLF",
},
};
drv;
}))
#undef TEST_JSON
}

View file

@ -0,0 +1,123 @@
#include "nar-info-disk-cache.hh"
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "sqlite.hh"
#include <sqlite3.h>
namespace nix {
TEST(NarInfoDiskCacheImpl, create_and_read) {
// This is a large single test to avoid some setup overhead.
int prio = 12345;
bool wantMassQuery = true;
Path tmpDir = createTempDir();
AutoDelete delTmpDir(tmpDir);
Path dbPath(tmpDir + "/test-narinfo-disk-cache.sqlite");
int savedId;
int barId;
SQLite db;
SQLiteStmt getIds;
{
auto cache = getTestNarInfoDiskCache(dbPath);
// Set up "background noise" and check that different caches receive different ids
{
auto bc1 = cache->createCache("https://bar", "/nix/storedir", wantMassQuery, prio);
auto bc2 = cache->createCache("https://xyz", "/nix/storedir", false, 12);
ASSERT_NE(bc1, bc2);
barId = bc1;
}
// Check that the fields are saved and returned correctly. This does not test
// the select statement yet, because of in-memory caching.
savedId = cache->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);;
{
auto r = cache->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
ASSERT_EQ(savedId, r->id);
}
// We're going to pay special attention to the id field because we had a bug
// that changed it.
db = SQLite(dbPath);
getIds.create(db, "select id from BinaryCaches where url = 'http://foo'");
{
auto q(getIds.use());
ASSERT_TRUE(q.next());
ASSERT_EQ(savedId, q.getInt(0));
ASSERT_FALSE(q.next());
}
// Pretend that the caches are older, but keep one up to date, as "background noise"
db.exec("update BinaryCaches set timestamp = timestamp - 1 - 7 * 24 * 3600 where url <> 'https://xyz';");
// This shows that the in-memory cache works
{
auto r = cache->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
}
}
{
// We can't clear the in-memory cache, so we use a new cache object. This is
// more realistic anyway.
auto cache2 = getTestNarInfoDiskCache(dbPath);
{
auto r = cache2->upToDateCacheExists("http://foo");
ASSERT_FALSE(r);
}
// "Update", same data, check that the id number is reused
cache2->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);
{
auto r = cache2->upToDateCacheExists("http://foo");
ASSERT_TRUE(r);
ASSERT_EQ(r->priority, prio);
ASSERT_EQ(r->wantMassQuery, wantMassQuery);
ASSERT_EQ(r->id, savedId);
}
{
auto q(getIds.use());
ASSERT_TRUE(q.next());
auto currentId = q.getInt(0);
ASSERT_FALSE(q.next());
ASSERT_EQ(currentId, savedId);
}
// Check that the fields can be modified, and the id remains the same
{
auto r0 = cache2->upToDateCacheExists("https://bar");
ASSERT_FALSE(r0);
cache2->createCache("https://bar", "/nix/storedir", !wantMassQuery, prio + 10);
auto r = cache2->upToDateCacheExists("https://bar");
ASSERT_EQ(r->wantMassQuery, !wantMassQuery);
ASSERT_EQ(r->priority, prio + 10);
ASSERT_EQ(r->id, barId);
}
// // Force update (no use case yet; we only retrieve cache metadata when stale based on timestamp)
// {
// cache2->createCache("https://bar", "/nix/storedir", wantMassQuery, prio + 20);
// auto r = cache2->upToDateCacheExists("https://bar");
// ASSERT_EQ(r->wantMassQuery, wantMassQuery);
// ASSERT_EQ(r->priority, prio + 20);
// }
}
}
}