diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/libexpr/eval-settings.cc | 8 | ||||
-rw-r--r-- | src/libexpr/eval-settings.hh | 14 | ||||
-rw-r--r-- | src/libexpr/eval.cc | 135 | ||||
-rw-r--r-- | src/libexpr/primops.cc | 12 | ||||
-rw-r--r-- | src/libfetchers/tarball.cc | 2 | ||||
-rw-r--r-- | src/libmain/common-args.cc | 5 | ||||
-rw-r--r-- | src/libstore/build/derivation-goal.cc | 3 | ||||
-rw-r--r-- | src/libstore/filetransfer.cc | 8 | ||||
-rw-r--r-- | src/libstore/filetransfer.hh | 9 | ||||
-rw-r--r-- | src/libstore/http-binary-cache-store.cc | 4 | ||||
-rw-r--r-- | src/libstore/s3-binary-cache-store.cc | 11 | ||||
-rw-r--r-- | src/libstore/store-api.cc | 41 | ||||
-rw-r--r-- | src/libutil/compression.cc | 94 | ||||
-rw-r--r-- | src/libutil/error.hh | 2 | ||||
-rw-r--r-- | src/libutil/logging.cc | 7 | ||||
-rw-r--r-- | src/libutil/sync.hh | 76 | ||||
-rw-r--r-- | src/nix/diff-closures.cc | 13 | ||||
-rw-r--r-- | src/nix/upgrade-nix.cc | 2 |
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(); |