libstore: move the builtin builders to our new executable

this means that builtinFetchurl runs in a real process now, and thus we
no longer need its workarounds for running in a forked process. forking
dropped the signal handler thread and broke the curl state via sharing,
neither of which happens any more now. we can run fetchurl builtins and
their actions straight from the main thread of our builder now, and the
temporary files and settings overrides we did are now also unnecessary.

Change-Id: I738171bc120ffcd541b7ff1424fed7924c2cdc1d
This commit is contained in:
eldritch horrors
2026-01-25 19:31:38 +01:00
parent 58c3ee4c8e
commit 049c7b3369
4 changed files with 86 additions and 82 deletions
+24 -1
View File
@@ -62,7 +62,30 @@ static int main_builtin_builder(AsyncIoRoot & aio, std::string programName, Stri
const auto builder = getAttr("builder");
throw Error("unknown builtin builder %s", builder);
if (builder == "builtin:fetchurl") {
const auto outputHashMode = getAttr("outputHashMode");
const auto hash = outputHashMode == "flat" ? [&] -> std::optional<Hash> {
const auto ht = parseHashTypeOpt(getAttr("outputHashAlgo"));
return newHashAllowEmpty(getAttr("outputHash"), ht);
}()
: std::nullopt;
BuiltinFetchurl{
.storePath = getAttr("out"),
.mainUrl = getAttr("url"),
.unpack = getOr(env, "unpack", "0") == "1",
.executable = getOr(env, "executable", "0") == "1",
.hash = hash,
}
.run(aio);
} else if (builder == "builtin:buildenv") {
builtinBuildenv(getAttr("out"), tokenizeString<Strings>(getAttr("derivations")), getAttr("manifest"));
} else if (builder == "builtin:unpack-channel") {
builtinUnpackChannel(getAttr("out"), getAttr("channelName"), getAttr("src"));
} else {
throw Error("unknown builtin builder %s", builder);
}
return 0;
}
void registerLegacyBuiltinBuilder()
+41 -42
View File
@@ -72,6 +72,8 @@
#include <grp.h>
#include <iostream>
using std::literals::operator""sv;
namespace nix {
static kj::Promise<Result<void>> handleDiffHook(
@@ -1230,51 +1232,48 @@ void LocalDerivationGoal::runChild(
/* Execute the program. This should not return. */
if (drv->isBuiltin()) {
try {
logger = makeJSONLogger(*logger);
Strings args{
"builtin-builder",
};
BasicDerivation & drv2(*drv);
for (auto & e : drv2.env)
e.second = rewriteStrings(e.second, inputRewrites);
std::map<std::string, AbstractConfig::SettingInfo> overriddenSettings;
settings.getSettings(overriddenSettings, true);
auto getAttr = [&](const std::string & name) {
auto i = drv2.env.find(name);
if (i == drv2.env.end()) {
throw Error("attribute '%s' missing", name);
}
return i->second;
};
if (drv->builder == "builtin:fetchurl") {
const auto hash = getAttr("outputHashMode") == "flat" ? [&] -> std::optional<Hash> {
const auto ht = parseHashTypeOpt(getAttr("outputHashAlgo"));
return newHashAllowEmpty(getAttr("outputHash"), ht);
}()
: std::nullopt;
BuiltinFetchurl{
.storePath = getAttr("out"),
.mainUrl = getAttr("url"),
.unpack = getOr(drv2.env, "unpack", "") == "1",
.executable = getOr(drv2.env, "executable", "") == "1",
.hash = hash,
.netrcData = netrcData,
.caFileData = caFileData,
}
.run();
} else if (drv->builder == "builtin:buildenv") {
builtinBuildenv(
getAttr("out"), tokenizeString<Strings>(getAttr("derivations")), getAttr("manifest")
);
} else if (drv->builder == "builtin:unpack-channel") {
builtinUnpackChannel(getAttr("out"), getAttr("channelName"), getAttr("src"));
} else {
throw Error("unsupported builtin builder '%1%'", drv->builder.substr(8));
}
_exit(0);
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
writeFull(STDERR_FILENO, e.what() + std::string("\n"));
_exit(1);
if (!netrcData.empty()) {
auto path = tmpDirInSandbox + "/netrc";
overriddenSettings[settings.netrcFile.name].value = path;
writeFile(path, netrcData, 0600);
}
if (!caFileData.empty()) {
auto path = tmpDirInSandbox + "/cafile";
overriddenSettings[settings.caFile.name].value = path;
writeFile(path, caFileData, 0600);
}
for (const auto & [setting, value] : overriddenSettings) {
args.push_back("--" + setting);
args.push_back(escapeNul(value.value));
}
args.push_back("--");
for (const auto & [k, v] : drv->env) {
args.push_back("--" + escapeNul(k));
args.push_back(rewriteStrings(escapeNul(v), inputRewrites));
}
// we pass on the *entire* daemon env to the builtin builder for historical
// reasons (libcurl inspects some env vars to modify how it behaves, and we
// are not fully sure yet that we pass through all of them to sandboxes. we
// should change this eventually. TODO: investigate curl env var use first)
Strings envs;
for (auto & [k, v] : getEnv()) {
envs.emplace_back(k + "=" + v);
}
execBuilder(LIX_LIBEXEC_DIR "/builtin-builder", std::move(args), envs);
throw Error("builtin builder '%1%' failed to exec", drv->builder.substr(8));
}
execBuilder(drv->builder, args, envStrs);
+2 -3
View File
@@ -1,6 +1,7 @@
#pragma once
///@file
#include "lix/libutil/async.hh"
#include "lix/libutil/hash.hh"
#include "lix/libutil/types.hh"
@@ -14,10 +15,8 @@ struct BuiltinFetchurl
bool unpack;
bool executable;
std::optional<Hash> hash;
std::string netrcData;
std::string caFileData;
void run();
void run(AsyncIoRoot & aio);
};
void builtinUnpackChannel(const Path & out, const std::string & channelName, const std::string & src);
+19 -36
View File
@@ -9,22 +9,9 @@
namespace nix {
void BuiltinFetchurl::run()
void BuiltinFetchurl::run(AsyncIoRoot & aio)
{
/* Make the host's netrc data available. Too bad curl requires
this to be stored in a file. It would be nice if we could just
pass a pointer to the data. */
if (netrcData != "") {
settings.netrcFile.override("netrc");
writeFile(settings.netrcFile, netrcData, 0600);
}
settings.caFile.override("ca-certificates.crt");
writeFile(settings.caFile, caFileData, 0600);
/* Note: have to use a fresh fileTransfer here because we're in
a forked process. */
auto fileTransfer = makeFileTransfer();
auto fileTransfer = getFileTransfer();
// we also have to run the remainder of this function in a fresh thread so
// we can have an aio root. the existing root on the current thread is not
@@ -47,31 +34,27 @@ void BuiltinFetchurl::run()
}
};
std::async(std::launch::async, [&] {
AsyncIoRoot aio;
/* Try the hashed mirrors first. */
if (hash) {
for (auto hashedMirror : settings.hashedMirrors.get()) {
try {
if (!hashedMirror.ends_with("/")) {
hashedMirror += '/';
}
fetch(
aio,
hashedMirror + printHashType(hash->type) + "/"
+ hash->to_string(HashFormat::Base16, false)
);
return;
} catch (Error & e) {
debug("%1%", Uncolored(e.what()));
/* Try the hashed mirrors first. */
if (hash) {
for (auto hashedMirror : settings.hashedMirrors.get()) {
try {
if (!hashedMirror.ends_with("/")) {
hashedMirror += '/';
}
fetch(
aio,
hashedMirror + printHashType(hash->type) + "/"
+ hash->to_string(HashFormat::Base16, false)
);
return;
} catch (Error & e) {
debug("%1%", Uncolored(e.what()));
}
}
}
/* Otherwise try the specified URL. */
fetch(aio, mainUrl);
}).get();
/* Otherwise try the specified URL. */
fetch(aio, mainUrl);
}
}