aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libexpr/eval-settings.cc8
-rw-r--r--src/libexpr/eval-settings.hh14
-rw-r--r--src/libexpr/eval.cc135
-rw-r--r--src/libexpr/primops.cc12
-rw-r--r--src/libfetchers/tarball.cc2
-rw-r--r--src/libmain/common-args.cc5
-rw-r--r--src/libstore/build/derivation-goal.cc3
-rw-r--r--src/libstore/filetransfer.cc8
-rw-r--r--src/libstore/filetransfer.hh9
-rw-r--r--src/libstore/http-binary-cache-store.cc4
-rw-r--r--src/libstore/s3-binary-cache-store.cc11
-rw-r--r--src/libstore/store-api.cc41
-rw-r--r--src/libutil/compression.cc94
-rw-r--r--src/libutil/error.hh2
-rw-r--r--src/libutil/logging.cc7
-rw-r--r--src/libutil/sync.hh76
-rw-r--r--src/nix/diff-closures.cc13
-rw-r--r--src/nix/upgrade-nix.cc2
18 files changed, 293 insertions, 153 deletions
diff --git a/src/libexpr/eval-settings.cc b/src/libexpr/eval-settings.cc
index 105fd3e9d..0bdf1b9a5 100644
--- a/src/libexpr/eval-settings.cc
+++ b/src/libexpr/eval-settings.cc
@@ -63,11 +63,9 @@ Strings EvalSettings::getDefaultNixPath()
}
};
- if (!evalSettings.restrictEval && !evalSettings.pureEval) {
- add(getNixDefExpr() + "/channels");
- add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
- add(rootChannelsDir());
- }
+ add(getNixDefExpr() + "/channels");
+ add(rootChannelsDir() + "/nixpkgs", "nixpkgs");
+ add(rootChannelsDir());
return res;
}
diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh
index cd73d195f..4673c509b 100644
--- a/src/libexpr/eval-settings.hh
+++ b/src/libexpr/eval-settings.hh
@@ -75,8 +75,17 @@ struct EvalSettings : Config
R"(
Pure evaluation mode ensures that the result of Nix expressions is fully determined by explicitly declared inputs, and not influenced by external state:
- - Restrict file system and network access to files specified by cryptographic hash
- - Disable [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) and [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime)
+ - File system and network access is restricted to accesses to immutable data only:
+ - Path literals relative to the home directory like `~/lix` are rejected at parse time.
+ - Access to absolute paths that did not result from Nix language evaluation is rejected when such paths are given as parameters to builtins like, for example, [`builtins.readFile`](@docroot@/language/builtins.md#builtins-readFile).
+
+ Access is nonetheless allowed to (absolute) paths in the Nix store that are returned by builtins like [`builtins.filterSource`](@docroot@/language/builtins.md#builtins-filterSource), [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball) and similar.
+ - Impure fetches such as not specifying a commit ID for `builtins.fetchGit` or not specifying a hash for `builtins.fetchTarball` are rejected.
+ - In flakes, access to relative paths outside of the root of the flake's source tree (often, a git repository) is rejected.
+ - The evaluator ignores `NIX_PATH`, `-I` and the `nix-path` setting. Thus, [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath) is an empty list.
+ - The builtins [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) and [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime) are absent from `builtins`.
+ - [`builtins.getEnv`](@docroot@/language/builtin-constants.md#builtins-currentSystem) always returns empty string for any variable.
+ - [`builtins.storePath`](@docroot@/language/builtins.md#builtins-storePath) throws an error (Lix may change this, tracking issue: <https://git.lix.systems/lix-project/lix/issues/402>)
)"
};
@@ -98,6 +107,7 @@ struct EvalSettings : Config
allowed to access `https://github.com/NixOS/patchelf.git`.
)"};
+
Setting<bool> traceFunctionCalls{this, false, "trace-function-calls",
R"(
If set to `true`, the Nix evaluator will trace every function call.
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 25d98b23b..9bd27e22d 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -418,7 +418,7 @@ EvalState::EvalState(
}
if (evalSettings.restrictEval || evalSettings.pureEval) {
- allowedPaths = PathSet();
+ allowedPaths = std::optional(PathSet());
for (auto & i : searchPath.elements) {
auto r = resolveSearchPathPath(i.path);
@@ -1533,6 +1533,66 @@ public:
};
};
+/** Currently these each just take one, but maybe in the future we could have diagnostics
+ * for all unexpected and missing arguments?
+ */
+struct FormalsMatch
+{
+ std::vector<Symbol> missing;
+ std::vector<Symbol> unexpected;
+};
+
+/** Matchup an attribute argument set to a lambda's formal arguments,
+ * or return what arguments were required but not given, or given but not allowed.
+ * (currently returns only one, for each).
+ */
+FormalsMatch matchupFormals(EvalState & state, Env & env, Displacement & displ, ExprLambda const & lambda, Bindings & attrs)
+{
+ size_t attrsUsed = 0;
+
+ for (auto const & formal : lambda.formals->formals) {
+
+ // The attribute whose name matches the name of the formal we're matching up, if it exists.
+ Attr const * matchingArg = attrs.get(formal.name);
+ if (matchingArg) {
+ attrsUsed += 1;
+ env.values[displ] = matchingArg->value;
+ displ += 1;
+
+ // We're done here. Move on to the next formal.
+ continue;
+ }
+
+ // The argument for this formal wasn't given.
+ // If the formal has a default, use it.
+ if (formal.def) {
+ env.values[displ] = formal.def->maybeThunk(state, env);
+ displ += 1;
+ } else {
+ // Otherwise, let our caller know what was missing.
+ return FormalsMatch{
+ .missing = {formal.name},
+ };
+ }
+ }
+
+ // Check for unexpected extra arguments.
+ if (!lambda.formals->ellipsis && attrsUsed != attrs.size()) {
+ // Return the first unexpected argument.
+ for (Attr const & attr : attrs) {
+ if (!lambda.formals->has(attr.name)) {
+ return FormalsMatch{
+ .unexpected = {attr.name},
+ };
+ }
+ }
+
+ abort(); // unreachable.
+ }
+
+ return FormalsMatch{};
+}
+
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{
if (callDepth > evalSettings.maxCallDepth)
@@ -1586,53 +1646,42 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (lambda.arg)
env2.values[displ++] = args[0];
- /* For each formal argument, get the actual argument. If
- there is no matching actual argument but the formal
- argument has a default, use the default. */
- size_t attrsUsed = 0;
- for (auto & i : lambda.formals->formals) {
- auto j = args[0]->attrs->get(i.name);
- if (!j) {
- if (!i.def) {
- error<TypeError>("function '%1%' called without required argument '%2%'",
- lambda.getName(symbols),
- symbols[i.name])
- .atPos(lambda.pos)
- .withTrace(pos, "from call site")
- .withFrame(*fun.lambda.env, lambda)
- .debugThrow();
- }
- env2.values[displ++] = i.def->maybeThunk(*this, env2);
- } else {
- attrsUsed++;
- env2.values[displ++] = j->value;
+ ///* For each formal argument, get the actual argument. If
+ // there is no matching actual argument but the formal
+ // argument has a default, use the default. */
+ auto const formalsMatch = matchupFormals(
+ *this,
+ env2,
+ displ,
+ lambda,
+ *args[0]->attrs
+ );
+ for (auto const & missingArg : formalsMatch.missing) {
+ auto const missing = symbols[missingArg];
+ error<TypeError>("function '%s' called without required argument '%s'", lambda.getName(symbols), missing)
+ .atPos(lambda.pos)
+ .withTrace(pos, "from call site")
+ .withFrame(*fun.lambda.env, lambda)
+ .debugThrow();
+ }
+ for (auto const & unexpectedArg : formalsMatch.unexpected) {
+ auto const unex = symbols[unexpectedArg];
+ std::set<std::string> formalNames;
+ for (auto const & formal : lambda.formals->formals) {
+ formalNames.insert(symbols[formal.name]);
}
+ auto sug = Suggestions::bestMatches(formalNames, unex);
+ error<TypeError>("function '%s' called with unexpected argument '%s'", lambda.getName(symbols), unex)
+ .atPos(lambda.pos)
+ .withTrace(pos, "from call site")
+ .withSuggestions(sug)
+ .withFrame(*fun.lambda.env, lambda)
+ .debugThrow();
}
- /* Check that each actual argument is listed as a formal
- argument (unless the attribute match specifies a `...'). */
- if (!lambda.formals->ellipsis && attrsUsed != args[0]->attrs->size()) {
- /* Nope, so show the first unexpected argument to the
- user. */
- for (auto & i : *args[0]->attrs)
- if (!lambda.formals->has(i.name)) {
- std::set<std::string> formalNames;
- for (auto & formal : lambda.formals->formals)
- formalNames.insert(symbols[formal.name]);
- auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
- error<TypeError>("function '%1%' called with unexpected argument '%2%'",
- lambda.getName(symbols),
- symbols[i.name])
- .atPos(lambda.pos)
- .withTrace(pos, "from call site")
- .withSuggestions(suggestions)
- .withFrame(*fun.lambda.env, lambda)
- .debugThrow();
- }
- abort(); // can't happen
- }
}
+
nrFunctionCalls++;
if (countCalls) incrFunctionCall(&lambda);
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 4b7e61dfe..20851da70 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -923,14 +923,15 @@ static RegisterPrimOp primop_getEnv({
.args = {"s"},
.doc = R"(
`getEnv` returns the value of the environment variable *s*, or an
- empty string if the variable doesn’t exist. This function should be
+ empty string if the variable doesn't exist. This function should be
used with care, as it can introduce all sorts of nasty environment
dependencies in your Nix expression.
- `getEnv` is used in Nix Packages to locate the file
- `~/.nixpkgs/config.nix`, which contains user-local settings for Nix
- Packages. (That is, it does a `getEnv "HOME"` to locate the user’s
- home directory.)
+ `getEnv` is used in nixpkgs for evil impurities such as locating the file
+ `~/.config/nixpkgs/config.nix` which contains user-local settings for nixpkgs.
+ (That is, it does a `getEnv "HOME"` to locate the user's home directory.)
+
+ When in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval), this function always returns an empty string.
)",
.fun = prim_getEnv,
});
@@ -1506,6 +1507,7 @@ static RegisterPrimOp primop_storePath({
in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
+ Lix may change this, tracking issue: <https://git.lix.systems/lix-project/lix/issues/402>
See also [`builtins.fetchClosure`](#builtins-fetchClosure).
)",
diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc
index cda6b7acb..c903895e2 100644
--- a/src/libfetchers/tarball.cc
+++ b/src/libfetchers/tarball.cc
@@ -46,7 +46,7 @@ DownloadFileResult downloadFile(
request.expectedETag = getStrAttr(cached->infoAttrs, "etag");
FileTransferResult res;
try {
- res = getFileTransfer()->download(request);
+ res = getFileTransfer()->transfer(request);
} catch (FileTransferError & e) {
if (cached) {
warn("%s; using cached version", e.msg());
diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc
index 20b5befe4..8fcb10325 100644
--- a/src/libmain/common-args.cc
+++ b/src/libmain/common-args.cc
@@ -1,5 +1,6 @@
#include "common-args.hh"
#include "args/root.hh"
+#include "error.hh"
#include "globals.hh"
#include "loggers.hh"
#include "logging.hh"
@@ -14,14 +15,14 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
.shortName = 'v',
.description = "Increase the logging verbosity level.",
.category = loggingCategory,
- .handler = {[]() { verbosity = (Verbosity) (verbosity + 1); }},
+ .handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) + 1); }},
});
addFlag({
.longName = "quiet",
.description = "Decrease the logging verbosity level.",
.category = loggingCategory,
- .handler = {[]() { verbosity = verbosity > lvlError ? (Verbosity) (verbosity - 1) : lvlError; }},
+ .handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) - 1); }},
});
addFlag({
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 97ba994ad..46399b0a8 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -1553,11 +1553,12 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
Goal::waiteeDone(waitee, result);
if (!useDerivation) return;
- auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
if (!dg) return;
+ auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
+
auto * nodeP = fullDrv.inputDrvs.findSlot(DerivedPath::Opaque { .path = dg->drvPath });
if (!nodeP) return;
auto & outputs = nodeP->value;
diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc
index 156ab6f7a..065f38a0c 100644
--- a/src/libstore/filetransfer.cc
+++ b/src/libstore/filetransfer.cc
@@ -817,17 +817,11 @@ ref<FileTransfer> makeFileTransfer()
return makeCurlFileTransfer();
}
-FileTransferResult FileTransfer::download(const FileTransferRequest & request)
+FileTransferResult FileTransfer::transfer(const FileTransferRequest & request)
{
return enqueueFileTransfer(request).get();
}
-FileTransferResult FileTransfer::upload(const FileTransferRequest & request)
-{
- /* Note: this method is the same as download, but helps in readability */
- return enqueueFileTransfer(request).get();
-}
-
template<typename... Args>
FileTransferError::FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args)
: Error(args...), error(error), response(response)
diff --git a/src/libstore/filetransfer.hh b/src/libstore/filetransfer.hh
index 23105e245..5d739112b 100644
--- a/src/libstore/filetransfer.hh
+++ b/src/libstore/filetransfer.hh
@@ -99,14 +99,9 @@ struct FileTransfer
virtual std::future<FileTransferResult> enqueueFileTransfer(const FileTransferRequest & request) = 0;
/**
- * Synchronously download a file.
+ * Synchronously transfer a file.
*/
- FileTransferResult download(const FileTransferRequest & request);
-
- /**
- * Synchronously upload a file.
- */
- FileTransferResult upload(const FileTransferRequest & request);
+ FileTransferResult transfer(const FileTransferRequest & request);
/**
* Download a file, writing its data to a sink. The sink will be
diff --git a/src/libstore/http-binary-cache-store.cc b/src/libstore/http-binary-cache-store.cc
index 9fafabe65..8cbb50ee9 100644
--- a/src/libstore/http-binary-cache-store.cc
+++ b/src/libstore/http-binary-cache-store.cc
@@ -115,7 +115,7 @@ protected:
try {
FileTransferRequest request(makeRequest(path));
request.head = true;
- getFileTransfer()->download(request);
+ getFileTransfer()->transfer(request);
return true;
} catch (FileTransferError & e) {
/* S3 buckets return 403 if a file doesn't exist and the
@@ -135,7 +135,7 @@ protected:
req.data = StreamToSourceAdapter(istream).drain();
req.mimeType = mimeType;
try {
- getFileTransfer()->upload(req);
+ getFileTransfer()->transfer(req);
} catch (FileTransferError & e) {
throw UploadToHTTP("while uploading to HTTP binary cache at '%s': %s", cacheUri, e.msg());
}
diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc
index 13a709104..4aed791ce 100644
--- a/src/libstore/s3-binary-cache-store.cc
+++ b/src/libstore/s3-binary-cache-store.cc
@@ -55,12 +55,14 @@ class AwsLogger : public Aws::Utils::Logging::FormattedLogSystem
void ProcessFormattedStatement(Aws::String && statement) override
{
+ // FIXME: workaround for truly excessive log spam in debug level: https://github.com/aws/aws-sdk-cpp/pull/3003
+ if ((statement.find("(SSLDataIn)") != std::string::npos || statement.find("(SSLDataOut)") != std::string::npos) && verbosity <= lvlDebug) {
+ return;
+ }
debug("AWS: %s", chomp(statement));
}
-#if !(AWS_VERSION_MAJOR <= 1 && AWS_VERSION_MINOR <= 7 && AWS_VERSION_PATCH <= 115)
void Flush() override {}
-#endif
};
static void initAWS()
@@ -100,12 +102,7 @@ S3Helper::S3Helper(
: std::dynamic_pointer_cast<Aws::Auth::AWSCredentialsProvider>(
std::make_shared<Aws::Auth::ProfileConfigFileAWSCredentialsProvider>(profile.c_str())),
*config,
- // FIXME: https://github.com/aws/aws-sdk-cpp/issues/759
-#if AWS_VERSION_MAJOR == 1 && AWS_VERSION_MINOR < 3
- false,
-#else
Aws::Client::AWSAuthV4Signer::PayloadSigningPolicy::Never,
-#endif
endpoint.empty()))
{
}
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index a10e5df0a..55083e834 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -1174,25 +1174,30 @@ std::map<StorePath, StorePath> copyPaths(
ValidPathInfo infoForDst = *info;
infoForDst.path = storePathForDst;
- auto source = sinkToSource([&](Sink & sink) {
- // We can reasonably assume that the copy will happen whenever we
- // read the path, so log something about that at that point
- auto srcUri = srcStore.getUri();
- auto dstUri = dstStore.getUri();
- auto storePathS = srcStore.printStorePath(missingPath);
- Activity act(*logger, lvlInfo, actCopyPath,
- makeCopyPathMessage(srcUri, dstUri, storePathS),
- {storePathS, srcUri, dstUri});
- PushActivity pact(act.id);
-
- LambdaSink progressSink([&, total = 0ULL](std::string_view data) mutable {
- total += data.size();
- act.progress(total, info->narSize);
- });
- TeeSink tee { sink, progressSink };
+ auto source =
+ sinkToSource([&srcStore, &dstStore, missingPath = missingPath, info = std::move(info)](Sink & sink) {
+ // We can reasonably assume that the copy will happen whenever we
+ // read the path, so log something about that at that point
+ auto srcUri = srcStore.getUri();
+ auto dstUri = dstStore.getUri();
+ auto storePathS = srcStore.printStorePath(missingPath);
+ Activity act(
+ *logger,
+ lvlInfo,
+ actCopyPath,
+ makeCopyPathMessage(srcUri, dstUri, storePathS),
+ {storePathS, srcUri, dstUri}
+ );
+ PushActivity pact(act.id);
+
+ LambdaSink progressSink([&, total = 0ULL](std::string_view data) mutable {
+ total += data.size();
+ act.progress(total, info->narSize);
+ });
+ TeeSink tee{sink, progressSink};
- srcStore.narFromPath(missingPath, tee);
- });
+ srcStore.narFromPath(missingPath, tee);
+ });
pathsToCopy.push_back(std::pair{infoForDst, std::move(source)});
}
diff --git a/src/libutil/compression.cc b/src/libutil/compression.cc
index 678557a58..e78d76500 100644
--- a/src/libutil/compression.cc
+++ b/src/libutil/compression.cc
@@ -137,53 +137,55 @@ struct NoneSink : CompressionSink
void writeUnbuffered(std::string_view data) override { nextSink(data); }
};
-struct BrotliDecompressionSink : ChunkedCompressionSink
+struct BrotliDecompressionSource : Source
{
- Sink & nextSink;
- BrotliDecoderState * state;
- bool finished = false;
-
- BrotliDecompressionSink(Sink & nextSink) : nextSink(nextSink)
+ static constexpr size_t BUF_SIZE = 32 * 1024;
+ std::unique_ptr<char[]> buf;
+ size_t avail_in = 0;
+ const uint8_t * next_in;
+
+ Source * inner;
+ std::unique_ptr<BrotliDecoderState, void (*)(BrotliDecoderState *)> state;
+
+ BrotliDecompressionSource(Source & inner)
+ : buf(std::make_unique<char[]>(BUF_SIZE))
+ , inner(&inner)
+ , state{
+ BrotliDecoderCreateInstance(nullptr, nullptr, nullptr), BrotliDecoderDestroyInstance}
{
- state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
- if (!state)
+ if (!state) {
throw CompressionError("unable to initialize brotli decoder");
+ }
}
- ~BrotliDecompressionSink()
- {
- BrotliDecoderDestroyInstance(state);
- }
-
- void finish() override
- {
- flush();
- writeInternal({});
- }
-
- void writeInternal(std::string_view data) override
+ size_t read(char * data, size_t len) override
{
- auto next_in = (const uint8_t *) data.data();
- size_t avail_in = data.size();
- uint8_t * next_out = outbuf;
- size_t avail_out = sizeof(outbuf);
-
- while (!finished && (!data.data() || avail_in)) {
- checkInterrupt();
-
- if (!BrotliDecoderDecompressStream(state,
- &avail_in, &next_in,
- &avail_out, &next_out,
- nullptr))
- throw CompressionError("error while decompressing brotli file");
-
- if (avail_out < sizeof(outbuf) || avail_in == 0) {
- nextSink({(char *) outbuf, sizeof(outbuf) - avail_out});
- next_out = outbuf;
- avail_out = sizeof(outbuf);
+ uint8_t * out = (uint8_t *) data;
+ const auto * begin = out;
+
+ try {
+ while (len && !BrotliDecoderIsFinished(state.get())) {
+ checkInterrupt();
+
+ while (avail_in == 0) {
+ avail_in = inner->read(buf.get(), BUF_SIZE);
+ next_in = (const uint8_t *) buf.get();
+ }
+
+ if (!BrotliDecoderDecompressStream(
+ state.get(), &avail_in, &next_in, &len, &out, nullptr
+ ))
+ {
+ throw CompressionError("error while decompressing brotli file");
+ }
}
+ } catch (EndOfFile &) {
+ }
- finished = BrotliDecoderIsFinished(state);
+ if (begin != out) {
+ return out - begin;
+ } else {
+ throw EndOfFile("brotli stream exhausted");
}
}
};
@@ -202,7 +204,19 @@ std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Si
if (method == "none" || method == "")
return std::make_unique<NoneSink>(nextSink);
else if (method == "br")
- return std::make_unique<BrotliDecompressionSink>(nextSink);
+ return sourceToSink([&](Source & source) {
+ BrotliDecompressionSource wrapped{source};
+ wrapped.drainInto(nextSink);
+ // special handling because sourceToSink is screwy: try
+ // to read the source one final time and fail when that
+ // succeeds (to reject trailing garbage in input data).
+ try {
+ char buf;
+ source(&buf, 1);
+ throw Error("garbage at end of brotli stream detected");
+ } catch (EndOfFile &) {
+ }
+ });
else
return sourceToSink([&](Source & source) {
auto decompressionSource = std::make_unique<ArchiveDecompressionSource>(source);
diff --git a/src/libutil/error.hh b/src/libutil/error.hh
index 0884f9f32..06dfba0df 100644
--- a/src/libutil/error.hh
+++ b/src/libutil/error.hh
@@ -45,6 +45,8 @@ typedef enum {
lvlVomit
} Verbosity;
+Verbosity verbosityFromIntClamped(int val);
+
/**
* The lines of code surrounding an error.
*/
diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc
index b01bb4dd4..fecbc89ac 100644
--- a/src/libutil/logging.cc
+++ b/src/libutil/logging.cc
@@ -5,6 +5,7 @@
#include "position.hh"
#include "terminal.hh"
+#include <algorithm>
#include <atomic>
#include <sstream>
#include <nlohmann/json.hpp>
@@ -110,6 +111,12 @@ public:
Verbosity verbosity = lvlInfo;
+Verbosity verbosityFromIntClamped(int val)
+{
+ int clamped = std::clamp(val, int(lvlError), int(lvlVomit));
+ return static_cast<Verbosity>(clamped);
+}
+
void writeToStderr(std::string_view s)
{
try {
diff --git a/src/libutil/sync.hh b/src/libutil/sync.hh
index 47e4512b1..2c5424f2a 100644
--- a/src/libutil/sync.hh
+++ b/src/libutil/sync.hh
@@ -40,50 +40,106 @@ public:
class Lock
{
private:
+ // Non-owning pointer. This would be an
+ // optional<reference_wrapper<Sync>> if it didn't break gdb accessing
+ // Lock values (as of 2024-06-15, gdb 14.2)
Sync * s;
std::unique_lock<M> lk;
friend Sync;
- Lock(Sync * s) : s(s), lk(s->mutex) { }
+ Lock(Sync &s) : s(&s), lk(s.mutex) { }
+
+ inline void checkLockingInvariants()
+ {
+ assert(s);
+ assert(lk.owns_lock());
+ }
+
public:
- Lock(Lock && l) : s(l.s) { abort(); }
+ Lock(Lock && l) : s(l.s), lk(std::move(l.lk))
+ {
+ l.s = nullptr;
+ }
+
+ Lock & operator=(Lock && other)
+ {
+ if (this != &other) {
+ s = other.s;
+ lk = std::move(other.lk);
+ other.s = nullptr;
+ }
+ return *this;
+ }
+
Lock(const Lock & l) = delete;
- ~Lock() { }
- T * operator -> () { return &s->data; }
- T & operator * () { return s->data; }
+ ~Lock() = default;
+
+ T * operator -> ()
+ {
+ checkLockingInvariants();
+ return &s->data;
+ }
+
+ T & operator * ()
+ {
+ checkLockingInvariants();
+ return s->data;
+ }
+
+ /**
+ * Wait for the given condition variable with no timeout.
+ *
+ * May spuriously wake up.
+ */
void wait(std::condition_variable & cv)
{
- assert(s);
+ checkLockingInvariants();
cv.wait(lk);
}
+ /**
+ * Wait for the given condition variable for a maximum elapsed time of \p duration.
+ *
+ * May spuriously wake up.
+ */
template<class Rep, class Period>
std::cv_status wait_for(std::condition_variable & cv,
const std::chrono::duration<Rep, Period> & duration)
{
- assert(s);
+ checkLockingInvariants();
return cv.wait_for(lk, duration);
}
+ /**
+ * Wait for the given condition variable for a maximum elapsed time of \p duration.
+ * Calls \p pred to check if the wakeup should be heeded: \p pred
+ * returning false will ignore the wakeup.
+ */
template<class Rep, class Period, class Predicate>
bool wait_for(std::condition_variable & cv,
const std::chrono::duration<Rep, Period> & duration,
Predicate pred)
{
- assert(s);
+ checkLockingInvariants();
return cv.wait_for(lk, duration, pred);
}
+ /**
+ * Wait for the given condition variable or until the time point \p duration.
+ */
template<class Clock, class Duration>
std::cv_status wait_until(std::condition_variable & cv,
const std::chrono::time_point<Clock, Duration> & duration)
{
- assert(s);
+ checkLockingInvariants();
return cv.wait_until(lk, duration);
}
};
- Lock lock() { return Lock(this); }
+ /**
+ * Lock this Sync and return a RAII guard object.
+ */
+ Lock lock() { return Lock(*this); }
};
}
diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc
index 736cbf55d..5eda309d5 100644
--- a/src/nix/diff-closures.cc
+++ b/src/nix/diff-closures.cc
@@ -30,9 +30,18 @@ GroupedPaths getClosureInfo(ref<Store> store, const StorePath & toplevel)
version suffixes like "unstable"). */
static std::regex regex("(.*)-([a-z]+|lib32|lib64)");
std::smatch match;
- std::string name(path.name());
+ std::string name{path.name()};
+ // Used to keep name alive through being potentially overwritten below
+ // (to not invalidate the references from the regex result)
+ //
+ // n.b. cannot be just path.name().{begin,end}() since that returns const
+ // char *, which does not, for some reason, convert as required on
+ // libstdc++. Seems like a libstdc++ bug or standard bug to me... we
+ // can afford the allocation in any case.
+ const std::string origName{path.name()};
std::string outputName;
- if (std::regex_match(name, match, regex)) {
+
+ if (std::regex_match(origName, match, regex)) {
name = match[1];
outputName = match[2];
}
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
index cbc28fdd7..c7f31f3fb 100644
--- a/src/nix/upgrade-nix.cc
+++ b/src/nix/upgrade-nix.cc
@@ -286,7 +286,7 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand
// FIXME: use nixos.org?
auto req = FileTransferRequest(storePathsUrl);
- auto res = getFileTransfer()->download(req);
+ auto res = getFileTransfer()->transfer(req);
auto state = std::make_unique<EvalState>(SearchPath{}, store);
auto v = state->allocValue();