mirror of
https://github.com/NixOS/nix
synced 2025-07-07 14:21:48 +02:00
Merge branch 'read-only-local-store' into overlayfs-store
This commit is contained in:
commit
71f3bad749
79 changed files with 1152 additions and 367 deletions
|
@ -239,9 +239,7 @@ void MixProfile::updateProfile(const StorePath & storePath)
|
|||
if (!store) throw Error("'--profile' is not supported for this Nix store");
|
||||
auto profile2 = absPath(*profile);
|
||||
switchLink(profile2,
|
||||
createGeneration(
|
||||
ref<LocalFSStore>(store),
|
||||
profile2, storePath));
|
||||
createGeneration(*store, profile2, storePath));
|
||||
}
|
||||
|
||||
void MixProfile::updateProfile(const BuiltPaths & buildables)
|
||||
|
|
|
@ -165,7 +165,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s)
|
|||
{
|
||||
if (EvalSettings::isPseudoUrl(s)) {
|
||||
auto storePath = fetchers::downloadTarball(
|
||||
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath;
|
||||
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).tree.storePath;
|
||||
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
||||
}
|
||||
|
||||
|
|
|
@ -701,7 +701,7 @@ RawInstallablesCommand::RawInstallablesCommand()
|
|||
{
|
||||
addFlag({
|
||||
.longName = "stdin",
|
||||
.description = "Read installables from the standard input.",
|
||||
.description = "Read installables from the standard input. No default installable applied.",
|
||||
.handler = {&readFromStdIn, true}
|
||||
});
|
||||
|
||||
|
@ -730,9 +730,9 @@ void RawInstallablesCommand::run(ref<Store> store)
|
|||
while (std::cin >> word) {
|
||||
rawInstallables.emplace_back(std::move(word));
|
||||
}
|
||||
} else {
|
||||
applyDefaultInstallables(rawInstallables);
|
||||
}
|
||||
|
||||
applyDefaultInstallables(rawInstallables);
|
||||
run(store, std::move(rawInstallables));
|
||||
}
|
||||
|
||||
|
|
|
@ -741,7 +741,8 @@ struct EvalSettings : Config
|
|||
If set to `true`, the Nix evaluator will not allow access to any
|
||||
files outside of the Nix search path (as set via the `NIX_PATH`
|
||||
environment variable or the `-I` option), or to URIs outside of
|
||||
`allowed-uri`. The default is `false`.
|
||||
[`allowed-uris`](../command-ref/conf-file.md#conf-allowed-uris).
|
||||
The default is `false`.
|
||||
)"};
|
||||
|
||||
Setting<bool> pureEval{this, false, "pure-eval",
|
||||
|
|
|
@ -793,7 +793,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
|
|||
if (EvalSettings::isPseudoUrl(elem.second)) {
|
||||
try {
|
||||
auto storePath = fetchers::downloadTarball(
|
||||
store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).first.storePath;
|
||||
store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).tree.storePath;
|
||||
res = { true, store->toRealPath(storePath) };
|
||||
} catch (FileTransferError & e) {
|
||||
logWarning({
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include "globals.hh"
|
||||
#include "json-to-value.hh"
|
||||
#include "names.hh"
|
||||
#include "references.hh"
|
||||
#include "path-references.hh"
|
||||
#include "store-api.hh"
|
||||
#include "util.hh"
|
||||
#include "value-to-json.hh"
|
||||
|
@ -4058,18 +4058,6 @@ static RegisterPrimOp primop_splitVersion({
|
|||
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
|
||||
|
||||
|
||||
RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
|
||||
{
|
||||
if (!primOps) primOps = new PrimOps;
|
||||
primOps->push_back({
|
||||
.name = name,
|
||||
.args = {},
|
||||
.arity = arity,
|
||||
.fun = fun,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
RegisterPrimOp::RegisterPrimOp(Info && info)
|
||||
{
|
||||
if (!primOps) primOps = new PrimOps;
|
||||
|
|
|
@ -28,11 +28,6 @@ struct RegisterPrimOp
|
|||
* will get called during EvalState initialization, so there
|
||||
* may be primops not yet added and builtins is not yet sorted.
|
||||
*/
|
||||
RegisterPrimOp(
|
||||
std::string name,
|
||||
size_t arity,
|
||||
PrimOpFun fun);
|
||||
|
||||
RegisterPrimOp(Info && info);
|
||||
};
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos,
|
|||
v.mkString(*s);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
|
||||
static RegisterPrimOp primop_unsafeDiscardStringContext({
|
||||
.name = "__unsafeDiscardStringContext",
|
||||
.arity = 1,
|
||||
.fun = prim_unsafeDiscardStringContext
|
||||
});
|
||||
|
||||
|
||||
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||
|
@ -22,7 +26,16 @@ static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args,
|
|||
v.mkBool(!context.empty());
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
|
||||
static RegisterPrimOp primop_hasContext({
|
||||
.name = "__hasContext",
|
||||
.args = {"s"},
|
||||
.doc = R"(
|
||||
Return `true` if string *s* has a non-empty context. The
|
||||
context can be obtained with
|
||||
[`getContext`](#builtins-getContext).
|
||||
)",
|
||||
.fun = prim_hasContext
|
||||
});
|
||||
|
||||
|
||||
/* Sometimes we want to pass a derivation path (i.e. pkg.drvPath) to a
|
||||
|
@ -51,7 +64,11 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx p
|
|||
v.mkString(*s, context2);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutputDependency", 1, prim_unsafeDiscardOutputDependency);
|
||||
static RegisterPrimOp primop_unsafeDiscardOutputDependency({
|
||||
.name = "__unsafeDiscardOutputDependency",
|
||||
.arity = 1,
|
||||
.fun = prim_unsafeDiscardOutputDependency
|
||||
});
|
||||
|
||||
|
||||
/* Extract the context of a string as a structured Nix value.
|
||||
|
@ -119,7 +136,30 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
|||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
|
||||
static RegisterPrimOp primop_getContext({
|
||||
.name = "__getContext",
|
||||
.args = {"s"},
|
||||
.doc = R"(
|
||||
Return the string context of *s*.
|
||||
|
||||
The string context tracks references to derivations within a string.
|
||||
It is represented as an attribute set of [store derivation](@docroot@/glossary.md#gloss-store-derivation) paths mapping to output names.
|
||||
|
||||
Using [string interpolation](@docroot@/language/string-interpolation.md) on a derivation will add that derivation to the string context.
|
||||
For example,
|
||||
|
||||
```nix
|
||||
builtins.getContext "${derivation { name = "a"; builder = "b"; system = "c"; }}"
|
||||
```
|
||||
|
||||
evaluates to
|
||||
|
||||
```
|
||||
{ "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; }
|
||||
```
|
||||
)",
|
||||
.fun = prim_getContext
|
||||
});
|
||||
|
||||
|
||||
/* Append the given context to a given string.
|
||||
|
@ -192,6 +232,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
|||
v.mkString(orig, context);
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_appendContext("__appendContext", 2, prim_appendContext);
|
||||
static RegisterPrimOp primop_appendContext({
|
||||
.name = "__appendContext",
|
||||
.arity = 2,
|
||||
.fun = prim_appendContext
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -88,6 +88,10 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
|||
state.allowPath(tree.storePath);
|
||||
}
|
||||
|
||||
static RegisterPrimOp r_fetchMercurial("fetchMercurial", 1, prim_fetchMercurial);
|
||||
static RegisterPrimOp r_fetchMercurial({
|
||||
.name = "fetchMercurial",
|
||||
.arity = 1,
|
||||
.fun = prim_fetchMercurial
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -194,7 +194,11 @@ static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args,
|
|||
}
|
||||
|
||||
// FIXME: document
|
||||
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree);
|
||||
static RegisterPrimOp primop_fetchTree({
|
||||
.name = "fetchTree",
|
||||
.arity = 1,
|
||||
.fun = prim_fetchTree
|
||||
});
|
||||
|
||||
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
|
||||
const std::string & who, bool unpack, std::string name)
|
||||
|
@ -262,7 +266,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
|||
// https://github.com/NixOS/nix/issues/4313
|
||||
auto storePath =
|
||||
unpack
|
||||
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).first.storePath
|
||||
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).tree.storePath
|
||||
: fetchers::downloadFile(state.store, *url, name, (bool) expectedHash).storePath;
|
||||
|
||||
if (expectedHash) {
|
||||
|
|
|
@ -90,6 +90,24 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
|
|||
}
|
||||
}
|
||||
|
||||
static RegisterPrimOp primop_fromTOML("fromTOML", 1, prim_fromTOML);
|
||||
static RegisterPrimOp primop_fromTOML({
|
||||
.name = "fromTOML",
|
||||
.args = {"e"},
|
||||
.doc = R"(
|
||||
Convert a TOML string to a Nix value. For example,
|
||||
|
||||
```nix
|
||||
builtins.fromTOML ''
|
||||
x=1
|
||||
s="a"
|
||||
[table]
|
||||
y=2
|
||||
''
|
||||
```
|
||||
|
||||
returns the value `{ s = "a"; table = { y = 2; }; x = 1; }`.
|
||||
)",
|
||||
.fun = prim_fromTOML
|
||||
});
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
///@file
|
||||
|
||||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
|
||||
#include <variant>
|
||||
|
||||
|
|
|
@ -159,6 +159,12 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
|
|||
input.to_string(), *prevLastModified);
|
||||
}
|
||||
|
||||
if (auto prevRev = getRev()) {
|
||||
if (input.getRev() != prevRev)
|
||||
throw Error("'rev' attribute mismatch in input '%s', expected %s",
|
||||
input.to_string(), prevRev->gitRev());
|
||||
}
|
||||
|
||||
if (auto prevRevCount = getRevCount()) {
|
||||
if (input.getRevCount() != prevRevCount)
|
||||
throw Error("'revCount' attribute mismatch in input '%s', expected %d",
|
||||
|
|
|
@ -158,6 +158,7 @@ struct DownloadFileResult
|
|||
StorePath storePath;
|
||||
std::string etag;
|
||||
std::string effectiveUrl;
|
||||
std::optional<std::string> immutableUrl;
|
||||
};
|
||||
|
||||
DownloadFileResult downloadFile(
|
||||
|
@ -167,7 +168,14 @@ DownloadFileResult downloadFile(
|
|||
bool locked,
|
||||
const Headers & headers = {});
|
||||
|
||||
std::pair<Tree, time_t> downloadTarball(
|
||||
struct DownloadTarballResult
|
||||
{
|
||||
Tree tree;
|
||||
time_t lastModified;
|
||||
std::optional<std::string> immutableUrl;
|
||||
};
|
||||
|
||||
DownloadTarballResult downloadTarball(
|
||||
ref<Store> store,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
|
|
|
@ -207,21 +207,21 @@ struct GitArchiveInputScheme : InputScheme
|
|||
|
||||
auto url = getDownloadUrl(input);
|
||||
|
||||
auto [tree, lastModified] = downloadTarball(store, url.url, input.getName(), true, url.headers);
|
||||
auto result = downloadTarball(store, url.url, input.getName(), true, url.headers);
|
||||
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(lastModified));
|
||||
input.attrs.insert_or_assign("lastModified", uint64_t(result.lastModified));
|
||||
|
||||
getCache()->add(
|
||||
store,
|
||||
lockedAttrs,
|
||||
{
|
||||
{"rev", rev->gitRev()},
|
||||
{"lastModified", uint64_t(lastModified)}
|
||||
{"lastModified", uint64_t(result.lastModified)}
|
||||
},
|
||||
tree.storePath,
|
||||
result.tree.storePath,
|
||||
true);
|
||||
|
||||
return {std::move(tree.storePath), input};
|
||||
return {result.tree.storePath, input};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,8 @@ DownloadFileResult downloadFile(
|
|||
return {
|
||||
.storePath = std::move(cached->storePath),
|
||||
.etag = getStrAttr(cached->infoAttrs, "etag"),
|
||||
.effectiveUrl = getStrAttr(cached->infoAttrs, "url")
|
||||
.effectiveUrl = getStrAttr(cached->infoAttrs, "url"),
|
||||
.immutableUrl = maybeGetStrAttr(cached->infoAttrs, "immutableUrl"),
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -55,12 +56,14 @@ DownloadFileResult downloadFile(
|
|||
}
|
||||
|
||||
// FIXME: write to temporary file.
|
||||
|
||||
Attrs infoAttrs({
|
||||
{"etag", res.etag},
|
||||
{"url", res.effectiveUri},
|
||||
});
|
||||
|
||||
if (res.immutableUrl)
|
||||
infoAttrs.emplace("immutableUrl", *res.immutableUrl);
|
||||
|
||||
std::optional<StorePath> storePath;
|
||||
|
||||
if (res.cached) {
|
||||
|
@ -111,10 +114,11 @@ DownloadFileResult downloadFile(
|
|||
.storePath = std::move(*storePath),
|
||||
.etag = res.etag,
|
||||
.effectiveUrl = res.effectiveUri,
|
||||
.immutableUrl = res.immutableUrl,
|
||||
};
|
||||
}
|
||||
|
||||
std::pair<Tree, time_t> downloadTarball(
|
||||
DownloadTarballResult downloadTarball(
|
||||
ref<Store> store,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
|
@ -131,8 +135,9 @@ std::pair<Tree, time_t> downloadTarball(
|
|||
|
||||
if (cached && !cached->expired)
|
||||
return {
|
||||
Tree { .actualPath = store->toRealPath(cached->storePath), .storePath = std::move(cached->storePath) },
|
||||
getIntAttr(cached->infoAttrs, "lastModified")
|
||||
.tree = Tree { .actualPath = store->toRealPath(cached->storePath), .storePath = std::move(cached->storePath) },
|
||||
.lastModified = (time_t) getIntAttr(cached->infoAttrs, "lastModified"),
|
||||
.immutableUrl = maybeGetStrAttr(cached->infoAttrs, "immutableUrl"),
|
||||
};
|
||||
|
||||
auto res = downloadFile(store, url, name, locked, headers);
|
||||
|
@ -160,6 +165,9 @@ std::pair<Tree, time_t> downloadTarball(
|
|||
{"etag", res.etag},
|
||||
});
|
||||
|
||||
if (res.immutableUrl)
|
||||
infoAttrs.emplace("immutableUrl", *res.immutableUrl);
|
||||
|
||||
getCache()->add(
|
||||
store,
|
||||
inAttrs,
|
||||
|
@ -168,8 +176,9 @@ std::pair<Tree, time_t> downloadTarball(
|
|||
locked);
|
||||
|
||||
return {
|
||||
Tree { .actualPath = store->toRealPath(*unpackedStorePath), .storePath = std::move(*unpackedStorePath) },
|
||||
lastModified,
|
||||
.tree = Tree { .actualPath = store->toRealPath(*unpackedStorePath), .storePath = std::move(*unpackedStorePath) },
|
||||
.lastModified = lastModified,
|
||||
.immutableUrl = res.immutableUrl,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -189,21 +198,33 @@ struct CurlInputScheme : InputScheme
|
|||
|
||||
virtual bool isValidURL(const ParsedURL & url) const = 0;
|
||||
|
||||
std::optional<Input> inputFromURL(const ParsedURL & url) const override
|
||||
std::optional<Input> inputFromURL(const ParsedURL & _url) const override
|
||||
{
|
||||
if (!isValidURL(url))
|
||||
if (!isValidURL(_url))
|
||||
return std::nullopt;
|
||||
|
||||
Input input;
|
||||
|
||||
auto urlWithoutApplicationScheme = url;
|
||||
urlWithoutApplicationScheme.scheme = parseUrlScheme(url.scheme).transport;
|
||||
auto url = _url;
|
||||
|
||||
url.scheme = parseUrlScheme(url.scheme).transport;
|
||||
|
||||
input.attrs.insert_or_assign("type", inputType());
|
||||
input.attrs.insert_or_assign("url", urlWithoutApplicationScheme.to_string());
|
||||
auto narHash = url.query.find("narHash");
|
||||
if (narHash != url.query.end())
|
||||
input.attrs.insert_or_assign("narHash", narHash->second);
|
||||
|
||||
if (auto i = get(url.query, "rev"))
|
||||
input.attrs.insert_or_assign("rev", *i);
|
||||
|
||||
if (auto i = get(url.query, "revCount"))
|
||||
if (auto n = string2Int<uint64_t>(*i))
|
||||
input.attrs.insert_or_assign("revCount", *n);
|
||||
|
||||
url.query.erase("rev");
|
||||
url.query.erase("revCount");
|
||||
|
||||
input.attrs.insert_or_assign("type", inputType());
|
||||
input.attrs.insert_or_assign("url", url.to_string());
|
||||
return input;
|
||||
}
|
||||
|
||||
|
@ -212,7 +233,8 @@ struct CurlInputScheme : InputScheme
|
|||
auto type = maybeGetStrAttr(attrs, "type");
|
||||
if (type != inputType()) return {};
|
||||
|
||||
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack"};
|
||||
// FIXME: some of these only apply to TarballInputScheme.
|
||||
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount"};
|
||||
for (auto & [name, value] : attrs)
|
||||
if (!allowedNames.count(name))
|
||||
throw Error("unsupported %s input attribute '%s'", *type, name);
|
||||
|
@ -275,10 +297,22 @@ struct TarballInputScheme : CurlInputScheme
|
|||
: hasTarballExtension(url.path));
|
||||
}
|
||||
|
||||
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
|
||||
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
|
||||
{
|
||||
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
|
||||
return {std::move(tree.storePath), input};
|
||||
Input input(_input);
|
||||
auto url = getStrAttr(input.attrs, "url");
|
||||
auto result = downloadTarball(store, url, input.getName(), false);
|
||||
|
||||
if (result.immutableUrl) {
|
||||
auto immutableInput = Input::fromURL(*result.immutableUrl);
|
||||
// FIXME: would be nice to support arbitrary flakerefs
|
||||
// here, e.g. git flakes.
|
||||
if (immutableInput.getType() != "tarball")
|
||||
throw Error("tarball 'Link' headers that redirect to non-tarball URLs are not supported");
|
||||
input = immutableInput;
|
||||
}
|
||||
|
||||
return {result.tree.storePath, std::move(input)};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "worker.hh"
|
||||
#include "builtins.hh"
|
||||
#include "builtins/buildenv.hh"
|
||||
#include "references.hh"
|
||||
#include "path-references.hh"
|
||||
#include "finally.hh"
|
||||
#include "util.hh"
|
||||
#include "archive.hh"
|
||||
|
@ -2389,18 +2389,21 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
continue;
|
||||
auto references = *referencesOpt;
|
||||
|
||||
auto rewriteOutput = [&]() {
|
||||
auto rewriteOutput = [&](const StringMap & rewrites) {
|
||||
/* Apply hash rewriting if necessary. */
|
||||
if (!outputRewrites.empty()) {
|
||||
if (!rewrites.empty()) {
|
||||
debug("rewriting hashes in '%1%'; cross fingers", actualPath);
|
||||
|
||||
/* FIXME: this is in-memory. */
|
||||
StringSink sink;
|
||||
dumpPath(actualPath, sink);
|
||||
/* FIXME: Is this actually streaming? */
|
||||
auto source = sinkToSource([&](Sink & nextSink) {
|
||||
RewritingSink rsink(rewrites, nextSink);
|
||||
dumpPath(actualPath, rsink);
|
||||
rsink.flush();
|
||||
});
|
||||
Path tmpPath = actualPath + ".tmp";
|
||||
restorePath(tmpPath, *source);
|
||||
deletePath(actualPath);
|
||||
sink.s = rewriteStrings(sink.s, outputRewrites);
|
||||
StringSource source(sink.s);
|
||||
restorePath(actualPath, source);
|
||||
movePath(tmpPath, actualPath);
|
||||
|
||||
/* FIXME: set proper permissions in restorePath() so
|
||||
we don't have to do another traversal. */
|
||||
|
@ -2449,7 +2452,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
"since recursive hashing is not enabled (one of outputHashMode={flat,text} is true)",
|
||||
actualPath);
|
||||
}
|
||||
rewriteOutput();
|
||||
rewriteOutput(outputRewrites);
|
||||
/* FIXME optimize and deduplicate with addToStore */
|
||||
std::string oldHashPart { scratchPath->hashPart() };
|
||||
HashModuloSink caSink { outputHash.hashType, oldHashPart };
|
||||
|
@ -2487,16 +2490,14 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
Hash::dummy,
|
||||
};
|
||||
if (*scratchPath != newInfo0.path) {
|
||||
// Also rewrite the output path
|
||||
auto source = sinkToSource([&](Sink & nextSink) {
|
||||
RewritingSink rsink2(oldHashPart, std::string(newInfo0.path.hashPart()), nextSink);
|
||||
dumpPath(actualPath, rsink2);
|
||||
rsink2.flush();
|
||||
});
|
||||
Path tmpPath = actualPath + ".tmp";
|
||||
restorePath(tmpPath, *source);
|
||||
deletePath(actualPath);
|
||||
movePath(tmpPath, actualPath);
|
||||
// If the path has some self-references, we need to rewrite
|
||||
// them.
|
||||
// (note that this doesn't invalidate the ca hash we calculated
|
||||
// above because it's computed *modulo the self-references*, so
|
||||
// it already takes this rewrite into account).
|
||||
rewriteOutput(
|
||||
StringMap{{oldHashPart,
|
||||
std::string(newInfo0.path.hashPart())}});
|
||||
}
|
||||
|
||||
HashResult narHashAndSize = hashPath(htSHA256, actualPath);
|
||||
|
@ -2518,7 +2519,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
|||
outputRewrites.insert_or_assign(
|
||||
std::string { scratchPath->hashPart() },
|
||||
std::string { requiredFinalPath.hashPart() });
|
||||
rewriteOutput();
|
||||
rewriteOutput(outputRewrites);
|
||||
auto narHashAndSize = hashPath(htSHA256, actualPath);
|
||||
ValidPathInfo newInfo0 { requiredFinalPath, narHashAndSize.first };
|
||||
newInfo0.narSize = narHashAndSize.second;
|
||||
|
|
|
@ -186,9 +186,9 @@ struct curlFileTransfer : public FileTransfer
|
|||
size_t realSize = size * nmemb;
|
||||
std::string line((char *) contents, realSize);
|
||||
printMsg(lvlVomit, "got header for '%s': %s", request.uri, trim(line));
|
||||
|
||||
static std::regex statusLine("HTTP/[^ ]+ +[0-9]+(.*)", std::regex::extended | std::regex::icase);
|
||||
std::smatch match;
|
||||
if (std::regex_match(line, match, statusLine)) {
|
||||
if (std::smatch match; std::regex_match(line, match, statusLine)) {
|
||||
result.etag = "";
|
||||
result.data.clear();
|
||||
result.bodySize = 0;
|
||||
|
@ -196,9 +196,11 @@ struct curlFileTransfer : public FileTransfer
|
|||
acceptRanges = false;
|
||||
encoding = "";
|
||||
} else {
|
||||
|
||||
auto i = line.find(':');
|
||||
if (i != std::string::npos) {
|
||||
std::string name = toLower(trim(line.substr(0, i)));
|
||||
|
||||
if (name == "etag") {
|
||||
result.etag = trim(line.substr(i + 1));
|
||||
/* Hack to work around a GitHub bug: it sends
|
||||
|
@ -212,10 +214,22 @@ struct curlFileTransfer : public FileTransfer
|
|||
debug("shutting down on 200 HTTP response with expected ETag");
|
||||
return 0;
|
||||
}
|
||||
} else if (name == "content-encoding")
|
||||
}
|
||||
|
||||
else if (name == "content-encoding")
|
||||
encoding = trim(line.substr(i + 1));
|
||||
|
||||
else if (name == "accept-ranges" && toLower(trim(line.substr(i + 1))) == "bytes")
|
||||
acceptRanges = true;
|
||||
|
||||
else if (name == "link" || name == "x-amz-meta-link") {
|
||||
auto value = trim(line.substr(i + 1));
|
||||
static std::regex linkRegex("<([^>]*)>; rel=\"immutable\"", std::regex::extended | std::regex::icase);
|
||||
if (std::smatch match; std::regex_match(value, match, linkRegex))
|
||||
result.immutableUrl = match.str(1);
|
||||
else
|
||||
debug("got invalid link header '%s'", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return realSize;
|
||||
|
@ -345,7 +359,7 @@ struct curlFileTransfer : public FileTransfer
|
|||
{
|
||||
auto httpStatus = getHTTPStatus();
|
||||
|
||||
char * effectiveUriCStr;
|
||||
char * effectiveUriCStr = nullptr;
|
||||
curl_easy_getinfo(req, CURLINFO_EFFECTIVE_URL, &effectiveUriCStr);
|
||||
if (effectiveUriCStr)
|
||||
result.effectiveUri = effectiveUriCStr;
|
||||
|
|
|
@ -80,6 +80,10 @@ struct FileTransferResult
|
|||
std::string effectiveUri;
|
||||
std::string data;
|
||||
uint64_t bodySize = 0;
|
||||
/* An "immutable" URL for this resource (i.e. one whose contents
|
||||
will never change), as returned by the `Link: <url>;
|
||||
rel="immutable"` header. */
|
||||
std::optional<std::string> immutableUrl;
|
||||
};
|
||||
|
||||
class Store;
|
||||
|
|
|
@ -77,7 +77,30 @@ Settings::Settings()
|
|||
allowedImpureHostPrefixes = tokenizeString<StringSet>("/System/Library /usr/lib /dev /bin/sh");
|
||||
#endif
|
||||
|
||||
buildHook = getSelfExe().value_or("nix") + " __build-remote";
|
||||
/* Set the build hook location
|
||||
|
||||
For builds we perform a self-invocation, so Nix has to be self-aware.
|
||||
That is, it has to know where it is installed. We don't think it's sentient.
|
||||
|
||||
Normally, nix is installed according to `nixBinDir`, which is set at compile time,
|
||||
but can be overridden. This makes for a great default that works even if this
|
||||
code is linked as a library into some other program whose main is not aware
|
||||
that it might need to be a build remote hook.
|
||||
|
||||
However, it may not have been installed at all. For example, if it's a static build,
|
||||
there's a good chance that it has been moved out of its installation directory.
|
||||
That makes `nixBinDir` useless. Instead, we'll query the OS for the path to the
|
||||
current executable, using `getSelfExe()`.
|
||||
|
||||
As a last resort, we resort to `PATH`. Hopefully we find a `nix` there that's compatible.
|
||||
If you're porting Nix to a new platform, that might be good enough for a while, but
|
||||
you'll want to improve `getSelfExe()` to work on your platform.
|
||||
*/
|
||||
std::string nixExePath = nixBinDir + "/nix";
|
||||
if (!pathExists(nixExePath)) {
|
||||
nixExePath = getSelfExe().value_or("nix");
|
||||
}
|
||||
buildHook = nixExePath + " __build-remote";
|
||||
}
|
||||
|
||||
void loadConfFile()
|
||||
|
|
|
@ -710,20 +710,19 @@ public:
|
|||
Strings{"https://cache.nixos.org/"},
|
||||
"substituters",
|
||||
R"(
|
||||
A list of [URLs of Nix stores](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format)
|
||||
to be used as substituters, separated by whitespace.
|
||||
Substituters are tried based on their Priority value, which each substituter can set
|
||||
independently. Lower value means higher priority.
|
||||
The default is `https://cache.nixos.org`, with a Priority of 40.
|
||||
A list of [URLs of Nix stores](@docroot@/command-ref/new-cli/nix3-help-stores.md#store-url-format) to be used as substituters, separated by whitespace.
|
||||
A substituter is an additional [store]{@docroot@/glossary.md##gloss-store} from which Nix can obtain [store objects](@docroot@/glossary.md#gloss-store-object) instead of building them.
|
||||
|
||||
At least one of the following conditions must be met for Nix to use
|
||||
a substituter:
|
||||
Substituters are tried based on their priority value, which each substituter can set independently.
|
||||
Lower value means higher priority.
|
||||
The default is `https://cache.nixos.org`, which has a priority of 40.
|
||||
|
||||
At least one of the following conditions must be met for Nix to use a substituter:
|
||||
|
||||
- the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
|
||||
- the user calling Nix is in the [`trusted-users`](#conf-trusted-users) list
|
||||
|
||||
In addition, each store path should be trusted as described
|
||||
in [`trusted-public-keys`](#conf-trusted-public-keys)
|
||||
In addition, each store path should be trusted as described in [`trusted-public-keys`](#conf-trusted-public-keys)
|
||||
)",
|
||||
{"binary-caches"}};
|
||||
|
||||
|
|
|
@ -52,14 +52,15 @@ struct LocalStoreConfig : virtual LocalFSStoreConfig
|
|||
R"(
|
||||
Allow this store to be opened when its [database](@docroot@/glossary.md#gloss-nix-database) is on a read-only filesystem.
|
||||
|
||||
Normally Nix will attempt to open the store database in read-write mode, even for querying (when write access is not needed).
|
||||
This causes it to fail if the database is on a read-only filesystem.
|
||||
Normally Nix will attempt to open the store database in read-write mode, even for querying (when write access is not needed), causing it to fail if the database is on a read-only filesystem.
|
||||
|
||||
Enable read-only mode to disable locking and open the SQLite database with the [`immutable` parameter](https://www.sqlite.org/c3ref/open.html) set.
|
||||
|
||||
**Warning**
|
||||
Do not use this unless the filesystem is read-only.
|
||||
Using it when the filesystem is writable can cause incorrect query results or corruption errors if the database is changed by another process.
|
||||
> **Warning**
|
||||
> Do not use this unless the filesystem is read-only.
|
||||
>
|
||||
> Using it when the filesystem is writable can cause incorrect query results or corruption errors if the database is changed by another process.
|
||||
> While the filesystem the database resides on might appear to be read-only, consider whether another user or system might have write access to it.
|
||||
)"};
|
||||
|
||||
const std::string name() override { return "Local Store"; }
|
||||
|
|
73
src/libstore/path-references.cc
Normal file
73
src/libstore/path-references.cc
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include "path-references.hh"
|
||||
#include "hash.hh"
|
||||
#include "util.hh"
|
||||
#include "archive.hh"
|
||||
|
||||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
||||
|
||||
PathRefScanSink::PathRefScanSink(StringSet && hashes, std::map<std::string, StorePath> && backMap)
|
||||
: RefScanSink(std::move(hashes))
|
||||
, backMap(std::move(backMap))
|
||||
{ }
|
||||
|
||||
PathRefScanSink PathRefScanSink::fromPaths(const StorePathSet & refs)
|
||||
{
|
||||
StringSet hashes;
|
||||
std::map<std::string, StorePath> backMap;
|
||||
|
||||
for (auto & i : refs) {
|
||||
std::string hashPart(i.hashPart());
|
||||
auto inserted = backMap.emplace(hashPart, i).second;
|
||||
assert(inserted);
|
||||
hashes.insert(hashPart);
|
||||
}
|
||||
|
||||
return PathRefScanSink(std::move(hashes), std::move(backMap));
|
||||
}
|
||||
|
||||
StorePathSet PathRefScanSink::getResultPaths()
|
||||
{
|
||||
/* Map the hashes found back to their store paths. */
|
||||
StorePathSet found;
|
||||
for (auto & i : getResult()) {
|
||||
auto j = backMap.find(i);
|
||||
assert(j != backMap.end());
|
||||
found.insert(j->second);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
std::pair<StorePathSet, HashResult> scanForReferences(
|
||||
const std::string & path,
|
||||
const StorePathSet & refs)
|
||||
{
|
||||
HashSink hashSink { htSHA256 };
|
||||
auto found = scanForReferences(hashSink, path, refs);
|
||||
auto hash = hashSink.finish();
|
||||
return std::pair<StorePathSet, HashResult>(found, hash);
|
||||
}
|
||||
|
||||
StorePathSet scanForReferences(
|
||||
Sink & toTee,
|
||||
const Path & path,
|
||||
const StorePathSet & refs)
|
||||
{
|
||||
PathRefScanSink refsSink = PathRefScanSink::fromPaths(refs);
|
||||
TeeSink sink { refsSink, toTee };
|
||||
|
||||
/* Look for the hashes in the NAR dump of the path. */
|
||||
dumpPath(path, sink);
|
||||
|
||||
return refsSink.getResultPaths();
|
||||
}
|
||||
|
||||
}
|
25
src/libstore/path-references.hh
Normal file
25
src/libstore/path-references.hh
Normal file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include "references.hh"
|
||||
#include "path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::pair<StorePathSet, HashResult> scanForReferences(const Path & path, const StorePathSet & refs);
|
||||
|
||||
StorePathSet scanForReferences(Sink & toTee, const Path & path, const StorePathSet & refs);
|
||||
|
||||
class PathRefScanSink : public RefScanSink
|
||||
{
|
||||
std::map<std::string, StorePath> backMap;
|
||||
|
||||
PathRefScanSink(StringSet && hashes, std::map<std::string, StorePath> && backMap);
|
||||
|
||||
public:
|
||||
|
||||
static PathRefScanSink fromPaths(const StorePathSet & refs);
|
||||
|
||||
StorePathSet getResultPaths();
|
||||
};
|
||||
|
||||
}
|
|
@ -13,8 +13,10 @@
|
|||
namespace nix {
|
||||
|
||||
|
||||
/* Parse a generation name of the format
|
||||
`<profilename>-<number>-link'. */
|
||||
/**
|
||||
* Parse a generation name of the format
|
||||
* `<profilename>-<number>-link'.
|
||||
*/
|
||||
static std::optional<GenerationNumber> parseName(const std::string & profileName, const std::string & name)
|
||||
{
|
||||
if (name.substr(0, profileName.size() + 1) != profileName + "-") return {};
|
||||
|
@ -28,7 +30,6 @@ static std::optional<GenerationNumber> parseName(const std::string & profileName
|
|||
}
|
||||
|
||||
|
||||
|
||||
std::pair<Generations, std::optional<GenerationNumber>> findGenerations(Path profile)
|
||||
{
|
||||
Generations gens;
|
||||
|
@ -61,15 +62,16 @@ std::pair<Generations, std::optional<GenerationNumber>> findGenerations(Path pro
|
|||
}
|
||||
|
||||
|
||||
static void makeName(const Path & profile, GenerationNumber num,
|
||||
Path & outLink)
|
||||
/**
|
||||
* Create a generation name that can be parsed by `parseName()`.
|
||||
*/
|
||||
static Path makeName(const Path & profile, GenerationNumber num)
|
||||
{
|
||||
Path prefix = fmt("%1%-%2%", profile, num);
|
||||
outLink = prefix + "-link";
|
||||
return fmt("%s-%s-link", profile, num);
|
||||
}
|
||||
|
||||
|
||||
Path createGeneration(ref<LocalFSStore> store, Path profile, StorePath outPath)
|
||||
Path createGeneration(LocalFSStore & store, Path profile, StorePath outPath)
|
||||
{
|
||||
/* The new generation number should be higher than old the
|
||||
previous ones. */
|
||||
|
@ -79,7 +81,7 @@ Path createGeneration(ref<LocalFSStore> store, Path profile, StorePath outPath)
|
|||
if (gens.size() > 0) {
|
||||
Generation last = gens.back();
|
||||
|
||||
if (readLink(last.path) == store->printStorePath(outPath)) {
|
||||
if (readLink(last.path) == store.printStorePath(outPath)) {
|
||||
/* We only create a new generation symlink if it differs
|
||||
from the last one.
|
||||
|
||||
|
@ -89,7 +91,7 @@ Path createGeneration(ref<LocalFSStore> store, Path profile, StorePath outPath)
|
|||
return last.path;
|
||||
}
|
||||
|
||||
num = gens.back().number;
|
||||
num = last.number;
|
||||
} else {
|
||||
num = 0;
|
||||
}
|
||||
|
@ -100,9 +102,8 @@ Path createGeneration(ref<LocalFSStore> store, Path profile, StorePath outPath)
|
|||
to the permanent roots (of which the GC would have a stale
|
||||
view). If we didn't do it this way, the GC might remove the
|
||||
user environment etc. we've just built. */
|
||||
Path generation;
|
||||
makeName(profile, num + 1, generation);
|
||||
store->addPermRoot(outPath, generation);
|
||||
Path generation = makeName(profile, num + 1);
|
||||
store.addPermRoot(outPath, generation);
|
||||
|
||||
return generation;
|
||||
}
|
||||
|
@ -117,12 +118,19 @@ static void removeFile(const Path & path)
|
|||
|
||||
void deleteGeneration(const Path & profile, GenerationNumber gen)
|
||||
{
|
||||
Path generation;
|
||||
makeName(profile, gen, generation);
|
||||
Path generation = makeName(profile, gen);
|
||||
removeFile(generation);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete a generation with dry-run mode.
|
||||
*
|
||||
* Like `deleteGeneration()` but:
|
||||
*
|
||||
* - We log what we are going to do.
|
||||
*
|
||||
* - We only actually delete if `dryRun` is false.
|
||||
*/
|
||||
static void deleteGeneration2(const Path & profile, GenerationNumber gen, bool dryRun)
|
||||
{
|
||||
if (dryRun)
|
||||
|
@ -150,27 +158,36 @@ void deleteGenerations(const Path & profile, const std::set<GenerationNumber> &
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advanced the iterator until the given predicate `cond` returns `true`.
|
||||
*/
|
||||
static inline void iterDropUntil(Generations & gens, auto && i, auto && cond)
|
||||
{
|
||||
for (; i != gens.rend() && !cond(*i); ++i);
|
||||
}
|
||||
|
||||
void deleteGenerationsGreaterThan(const Path & profile, GenerationNumber max, bool dryRun)
|
||||
{
|
||||
if (max == 0)
|
||||
throw Error("Must keep at least one generation, otherwise the current one would be deleted");
|
||||
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
bool fromCurGen = false;
|
||||
auto [gens, curGen] = findGenerations(profile);
|
||||
for (auto i = gens.rbegin(); i != gens.rend(); ++i) {
|
||||
if (i->number == curGen) {
|
||||
fromCurGen = true;
|
||||
max--;
|
||||
continue;
|
||||
}
|
||||
if (fromCurGen) {
|
||||
if (max) {
|
||||
max--;
|
||||
continue;
|
||||
}
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
}
|
||||
}
|
||||
auto [gens, _curGen] = findGenerations(profile);
|
||||
auto curGen = _curGen;
|
||||
|
||||
auto i = gens.rbegin();
|
||||
|
||||
// Find the current generation
|
||||
iterDropUntil(gens, i, [&](auto & g) { return g.number == curGen; });
|
||||
|
||||
// Skip over `max` generations, preserving them
|
||||
for (auto keep = 0; i != gens.rend() && keep < max; ++i, ++keep);
|
||||
|
||||
// Delete the rest
|
||||
for (; i != gens.rend(); ++i)
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
}
|
||||
|
||||
void deleteOldGenerations(const Path & profile, bool dryRun)
|
||||
|
@ -193,23 +210,33 @@ void deleteGenerationsOlderThan(const Path & profile, time_t t, bool dryRun)
|
|||
|
||||
auto [gens, curGen] = findGenerations(profile);
|
||||
|
||||
bool canDelete = false;
|
||||
for (auto i = gens.rbegin(); i != gens.rend(); ++i)
|
||||
if (canDelete) {
|
||||
assert(i->creationTime < t);
|
||||
if (i->number != curGen)
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
} else if (i->creationTime < t) {
|
||||
/* We may now start deleting generations, but we don't
|
||||
delete this generation yet, because this generation was
|
||||
still the one that was active at the requested point in
|
||||
time. */
|
||||
canDelete = true;
|
||||
}
|
||||
auto i = gens.rbegin();
|
||||
|
||||
// Predicate that the generation is older than the given time.
|
||||
auto older = [&](auto & g) { return g.creationTime < t; };
|
||||
|
||||
// Find the first older generation, if one exists
|
||||
iterDropUntil(gens, i, older);
|
||||
|
||||
/* Take the previous generation
|
||||
|
||||
We don't want delete this one yet because it
|
||||
existed at the requested point in time, and
|
||||
we want to be able to roll back to it. */
|
||||
if (i != gens.rend()) ++i;
|
||||
|
||||
// Delete all previous generations (unless current).
|
||||
for (; i != gens.rend(); ++i) {
|
||||
/* Creating date and generations should be monotonic, so lower
|
||||
numbered derivations should also be older. */
|
||||
assert(older(*i));
|
||||
if (i->number != curGen)
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void deleteGenerationsOlderThan(const Path & profile, std::string_view timeSpec, bool dryRun)
|
||||
time_t parseOlderThanTimeSpec(std::string_view timeSpec)
|
||||
{
|
||||
if (timeSpec.empty() || timeSpec[timeSpec.size() - 1] != 'd')
|
||||
throw UsageError("invalid number of days specifier '%1%', expected something like '14d'", timeSpec);
|
||||
|
@ -221,9 +248,7 @@ void deleteGenerationsOlderThan(const Path & profile, std::string_view timeSpec,
|
|||
if (!days || *days < 1)
|
||||
throw UsageError("invalid number of days specifier '%1%'", timeSpec);
|
||||
|
||||
time_t oldTime = curTime - *days * 24 * 3600;
|
||||
|
||||
deleteGenerationsOlderThan(profile, oldTime, dryRun);
|
||||
return curTime - *days * 24 * 3600;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
#pragma once
|
||||
///@file
|
||||
/**
|
||||
* @file Implementation of Profiles.
|
||||
*
|
||||
* See the manual for additional information.
|
||||
*/
|
||||
|
||||
#include "types.hh"
|
||||
#include "types.hh"
|
||||
#include "pathlocks.hh"
|
||||
|
||||
#include <time.h>
|
||||
|
@ -12,41 +16,166 @@ namespace nix {
|
|||
class StorePath;
|
||||
|
||||
|
||||
/**
|
||||
* A positive number identifying a generation for a given profile.
|
||||
*
|
||||
* Generation numbers are assigned sequentially. Each new generation is
|
||||
* assigned 1 + the current highest generation number.
|
||||
*/
|
||||
typedef uint64_t GenerationNumber;
|
||||
|
||||
/**
|
||||
* A generation is a revision of a profile.
|
||||
*
|
||||
* Each generation is a mapping (key-value pair) from an identifier
|
||||
* (`number`) to a store object (specified by `path`).
|
||||
*/
|
||||
struct Generation
|
||||
{
|
||||
/**
|
||||
* The number of a generation is its unique identifier within the
|
||||
* profile.
|
||||
*/
|
||||
GenerationNumber number;
|
||||
/**
|
||||
* The store path identifies the store object that is the contents
|
||||
* of the generation.
|
||||
*
|
||||
* These store paths / objects are not unique to the generation
|
||||
* within a profile. Nix tries to ensure successive generations have
|
||||
* distinct contents to avoid bloat, but nothing stops two
|
||||
* non-adjacent generations from having the same contents.
|
||||
*
|
||||
* @todo Use `StorePath` instead of `Path`?
|
||||
*/
|
||||
Path path;
|
||||
|
||||
/**
|
||||
* When the generation was created. This is extra metadata about the
|
||||
* generation used to make garbage collecting old generations more
|
||||
* convenient.
|
||||
*/
|
||||
time_t creationTime;
|
||||
};
|
||||
|
||||
/**
|
||||
* All the generations of a profile
|
||||
*/
|
||||
typedef std::list<Generation> Generations;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the list of currently present generations for the specified
|
||||
* profile, sorted by generation number. Also returns the number of
|
||||
* the current generation.
|
||||
* Find all generations for the given profile.
|
||||
*
|
||||
* @param profile A profile specified by its name and location combined
|
||||
* into a path. E.g. if "foo" is the name of the profile, and "/bar/baz"
|
||||
* is the directory it is in, then the path "/bar/baz/foo" would be the
|
||||
* argument for this parameter.
|
||||
*
|
||||
* @return The pair of:
|
||||
*
|
||||
* - The list of currently present generations for the specified profile,
|
||||
* sorted by ascending generation number.
|
||||
*
|
||||
* - The number of the current/active generation.
|
||||
*
|
||||
* Note that the current/active generation need not be the latest one.
|
||||
*/
|
||||
std::pair<Generations, std::optional<GenerationNumber>> findGenerations(Path profile);
|
||||
|
||||
class LocalFSStore;
|
||||
|
||||
Path createGeneration(ref<LocalFSStore> store, Path profile, StorePath outPath);
|
||||
/**
|
||||
* Create a new generation of the given profile
|
||||
*
|
||||
* If the previous generation (not the currently active one!) has a
|
||||
* distinct store object, a fresh generation number is mapped to the
|
||||
* given store object, referenced by path. Otherwise, the previous
|
||||
* generation is assumed.
|
||||
*
|
||||
* The behavior of reusing existing generations like this makes this
|
||||
* procedure idempotent. It also avoids clutter.
|
||||
*/
|
||||
Path createGeneration(LocalFSStore & store, Path profile, StorePath outPath);
|
||||
|
||||
/**
|
||||
* Unconditionally delete a generation
|
||||
*
|
||||
* @param profile A profile specified by its name and location combined into a path.
|
||||
*
|
||||
* @param gen The generation number specifying exactly which generation
|
||||
* to delete.
|
||||
*
|
||||
* Because there is no check of whether the generation to delete is
|
||||
* active, this is somewhat unsafe.
|
||||
*
|
||||
* @todo Should we expose this at all?
|
||||
*/
|
||||
void deleteGeneration(const Path & profile, GenerationNumber gen);
|
||||
|
||||
/**
|
||||
* Delete the given set of generations.
|
||||
*
|
||||
* @param profile The profile, specified by its name and location combined into a path, whose generations we want to delete.
|
||||
*
|
||||
* @param gensToDelete The generations to delete, specified by a set of
|
||||
* numbers.
|
||||
*
|
||||
* @param dryRun Log what would be deleted instead of actually doing
|
||||
* so.
|
||||
*
|
||||
* Trying to delete the currently active generation will fail, and cause
|
||||
* no generations to be deleted.
|
||||
*/
|
||||
void deleteGenerations(const Path & profile, const std::set<GenerationNumber> & gensToDelete, bool dryRun);
|
||||
|
||||
/**
|
||||
* Delete generations older than `max` passed the current generation.
|
||||
*
|
||||
* @param profile The profile, specified by its name and location combined into a path, whose generations we want to delete.
|
||||
*
|
||||
* @param max How many generations to keep up to the current one. Must
|
||||
* be at least 1 so we don't delete the current one.
|
||||
*
|
||||
* @param dryRun Log what would be deleted instead of actually doing
|
||||
* so.
|
||||
*/
|
||||
void deleteGenerationsGreaterThan(const Path & profile, GenerationNumber max, bool dryRun);
|
||||
|
||||
/**
|
||||
* Delete all generations other than the current one
|
||||
*
|
||||
* @param profile The profile, specified by its name and location combined into a path, whose generations we want to delete.
|
||||
*
|
||||
* @param dryRun Log what would be deleted instead of actually doing
|
||||
* so.
|
||||
*/
|
||||
void deleteOldGenerations(const Path & profile, bool dryRun);
|
||||
|
||||
/**
|
||||
* Delete generations older than `t`, except for the most recent one
|
||||
* older than `t`.
|
||||
*
|
||||
* @param profile The profile, specified by its name and location combined into a path, whose generations we want to delete.
|
||||
*
|
||||
* @param dryRun Log what would be deleted instead of actually doing
|
||||
* so.
|
||||
*/
|
||||
void deleteGenerationsOlderThan(const Path & profile, time_t t, bool dryRun);
|
||||
|
||||
void deleteGenerationsOlderThan(const Path & profile, std::string_view timeSpec, bool dryRun);
|
||||
/**
|
||||
* Parse a temp spec intended for `deleteGenerationsOlderThan()`.
|
||||
*
|
||||
* Throws an exception if `timeSpec` fails to parse.
|
||||
*/
|
||||
time_t parseOlderThanTimeSpec(std::string_view timeSpec);
|
||||
|
||||
/**
|
||||
* Smaller wrapper around `replaceSymlink` for replacing the current
|
||||
* generation of a profile. Does not enforce proper structure.
|
||||
*
|
||||
* @todo Always use `switchGeneration()` instead, and delete this.
|
||||
*/
|
||||
void switchLink(Path link, Path target);
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,7 @@ enum class SQLiteOpenMode {
|
|||
* Use this mode if the database is on a read-only filesystem.
|
||||
* Fails with an error if the database does not exist.
|
||||
*/
|
||||
Immutable
|
||||
Immutable,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -225,15 +225,7 @@ constexpr std::array<ExperimentalFeatureDetails, 15> xpFeatureDetails = {{
|
|||
.tag = Xp::ReadOnlyLocalStore,
|
||||
.name = "read-only-local-store",
|
||||
.description = R"(
|
||||
Allow the use of the `read-only` parameter in local store URIs.
|
||||
|
||||
Set this parameter to `true` to allow stores with databases on read-only filesystems to be opened for querying; ordinarily Nix will refuse to do this.
|
||||
|
||||
This is because SQLite requires write access to the database file to perform the file locking operations necessary for safe concurrent access.
|
||||
When `read-only` is set to `true`, the database will be opened in immutable mode.
|
||||
|
||||
Under this mode, SQLite will not do any locking at all, so you should be certain that the database will not be changed.
|
||||
While the filesystem the database resides on might be read-only to this process, consider whether another user, process, or system, might have write access to it.
|
||||
Allow the use of the `read-only` parameter in [local store](@docroot@/command-ref/new-cli/nix3-help-stores.md#local-store) URIs.
|
||||
)",
|
||||
},
|
||||
}};
|
||||
|
|
|
@ -63,30 +63,19 @@ std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
|
|||
return {std::move(fd), tmpl};
|
||||
}
|
||||
|
||||
void createSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime)
|
||||
void createSymlink(const Path & target, const Path & link)
|
||||
{
|
||||
if (symlink(target.c_str(), link.c_str()))
|
||||
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
|
||||
if (mtime) {
|
||||
struct timeval times[2];
|
||||
times[0].tv_sec = *mtime;
|
||||
times[0].tv_usec = 0;
|
||||
times[1].tv_sec = *mtime;
|
||||
times[1].tv_usec = 0;
|
||||
if (lutimes(link.c_str(), times))
|
||||
throw SysError("setting time of symlink '%s'", link);
|
||||
}
|
||||
}
|
||||
|
||||
void replaceSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime)
|
||||
void replaceSymlink(const Path & target, const Path & link)
|
||||
{
|
||||
for (unsigned int n = 0; true; n++) {
|
||||
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
|
||||
|
||||
try {
|
||||
createSymlink(target, tmp, mtime);
|
||||
createSymlink(target, tmp);
|
||||
} catch (SysError & e) {
|
||||
if (e.errNo == EEXIST) continue;
|
||||
throw;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <map>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace nix {
|
||||
|
@ -66,69 +67,20 @@ void RefScanSink::operator () (std::string_view data)
|
|||
}
|
||||
|
||||
|
||||
PathRefScanSink::PathRefScanSink(StringSet && hashes, std::map<std::string, StorePath> && backMap)
|
||||
: RefScanSink(std::move(hashes))
|
||||
, backMap(std::move(backMap))
|
||||
{ }
|
||||
|
||||
PathRefScanSink PathRefScanSink::fromPaths(const StorePathSet & refs)
|
||||
{
|
||||
StringSet hashes;
|
||||
std::map<std::string, StorePath> backMap;
|
||||
|
||||
for (auto & i : refs) {
|
||||
std::string hashPart(i.hashPart());
|
||||
auto inserted = backMap.emplace(hashPart, i).second;
|
||||
assert(inserted);
|
||||
hashes.insert(hashPart);
|
||||
}
|
||||
|
||||
return PathRefScanSink(std::move(hashes), std::move(backMap));
|
||||
}
|
||||
|
||||
StorePathSet PathRefScanSink::getResultPaths()
|
||||
{
|
||||
/* Map the hashes found back to their store paths. */
|
||||
StorePathSet found;
|
||||
for (auto & i : getResult()) {
|
||||
auto j = backMap.find(i);
|
||||
assert(j != backMap.end());
|
||||
found.insert(j->second);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
std::pair<StorePathSet, HashResult> scanForReferences(
|
||||
const std::string & path,
|
||||
const StorePathSet & refs)
|
||||
{
|
||||
HashSink hashSink { htSHA256 };
|
||||
auto found = scanForReferences(hashSink, path, refs);
|
||||
auto hash = hashSink.finish();
|
||||
return std::pair<StorePathSet, HashResult>(found, hash);
|
||||
}
|
||||
|
||||
StorePathSet scanForReferences(
|
||||
Sink & toTee,
|
||||
const Path & path,
|
||||
const StorePathSet & refs)
|
||||
{
|
||||
PathRefScanSink refsSink = PathRefScanSink::fromPaths(refs);
|
||||
TeeSink sink { refsSink, toTee };
|
||||
|
||||
/* Look for the hashes in the NAR dump of the path. */
|
||||
dumpPath(path, sink);
|
||||
|
||||
return refsSink.getResultPaths();
|
||||
}
|
||||
|
||||
|
||||
RewritingSink::RewritingSink(const std::string & from, const std::string & to, Sink & nextSink)
|
||||
: from(from), to(to), nextSink(nextSink)
|
||||
: RewritingSink({{from, to}}, nextSink)
|
||||
{
|
||||
assert(from.size() == to.size());
|
||||
}
|
||||
|
||||
RewritingSink::RewritingSink(const StringMap & rewrites, Sink & nextSink)
|
||||
: rewrites(rewrites), nextSink(nextSink)
|
||||
{
|
||||
std::string::size_type maxRewriteSize = 0;
|
||||
for (auto & [from, to] : rewrites) {
|
||||
assert(from.size() == to.size());
|
||||
maxRewriteSize = std::max(maxRewriteSize, from.size());
|
||||
}
|
||||
this->maxRewriteSize = maxRewriteSize;
|
||||
}
|
||||
|
||||
void RewritingSink::operator () (std::string_view data)
|
||||
|
@ -136,13 +88,13 @@ void RewritingSink::operator () (std::string_view data)
|
|||
std::string s(prev);
|
||||
s.append(data);
|
||||
|
||||
size_t j = 0;
|
||||
while ((j = s.find(from, j)) != std::string::npos) {
|
||||
matches.push_back(pos + j);
|
||||
s.replace(j, from.size(), to);
|
||||
}
|
||||
s = rewriteStrings(s, rewrites);
|
||||
|
||||
prev = s.size() < from.size() ? s : std::string(s, s.size() - from.size() + 1, from.size() - 1);
|
||||
prev = s.size() < maxRewriteSize
|
||||
? s
|
||||
: maxRewriteSize == 0
|
||||
? ""
|
||||
: std::string(s, s.size() - maxRewriteSize + 1, maxRewriteSize - 1);
|
||||
|
||||
auto consumed = s.size() - prev.size();
|
||||
|
|
@ -2,14 +2,9 @@
|
|||
///@file
|
||||
|
||||
#include "hash.hh"
|
||||
#include "path.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::pair<StorePathSet, HashResult> scanForReferences(const Path & path, const StorePathSet & refs);
|
||||
|
||||
StorePathSet scanForReferences(Sink & toTee, const Path & path, const StorePathSet & refs);
|
||||
|
||||
class RefScanSink : public Sink
|
||||
{
|
||||
StringSet hashes;
|
||||
|
@ -28,28 +23,18 @@ public:
|
|||
void operator () (std::string_view data) override;
|
||||
};
|
||||
|
||||
class PathRefScanSink : public RefScanSink
|
||||
{
|
||||
std::map<std::string, StorePath> backMap;
|
||||
|
||||
PathRefScanSink(StringSet && hashes, std::map<std::string, StorePath> && backMap);
|
||||
|
||||
public:
|
||||
|
||||
static PathRefScanSink fromPaths(const StorePathSet & refs);
|
||||
|
||||
StorePathSet getResultPaths();
|
||||
};
|
||||
|
||||
struct RewritingSink : Sink
|
||||
{
|
||||
std::string from, to, prev;
|
||||
const StringMap rewrites;
|
||||
std::string::size_type maxRewriteSize;
|
||||
std::string prev;
|
||||
Sink & nextSink;
|
||||
uint64_t pos = 0;
|
||||
|
||||
std::vector<uint64_t> matches;
|
||||
|
||||
RewritingSink(const std::string & from, const std::string & to, Sink & nextSink);
|
||||
RewritingSink(const StringMap & rewrites, Sink & nextSink);
|
||||
|
||||
void operator () (std::string_view data) override;
|
||||
|
46
src/libutil/tests/references.cc
Normal file
46
src/libutil/tests/references.cc
Normal file
|
@ -0,0 +1,46 @@
|
|||
#include "references.hh"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
using std::string;
|
||||
|
||||
struct RewriteParams {
|
||||
string originalString, finalString;
|
||||
StringMap rewrites;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const RewriteParams& bar) {
|
||||
StringSet strRewrites;
|
||||
for (auto & [from, to] : bar.rewrites)
|
||||
strRewrites.insert(from + "->" + to);
|
||||
return os <<
|
||||
"OriginalString: " << bar.originalString << std::endl <<
|
||||
"Rewrites: " << concatStringsSep(",", strRewrites) << std::endl <<
|
||||
"Expected result: " << bar.finalString;
|
||||
}
|
||||
};
|
||||
|
||||
class RewriteTest : public ::testing::TestWithParam<RewriteParams> {
|
||||
};
|
||||
|
||||
TEST_P(RewriteTest, IdentityRewriteIsIdentity) {
|
||||
RewriteParams param = GetParam();
|
||||
StringSink rewritten;
|
||||
auto rewriter = RewritingSink(param.rewrites, rewritten);
|
||||
rewriter(param.originalString);
|
||||
rewriter.flush();
|
||||
ASSERT_EQ(rewritten.s, param.finalString);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
references,
|
||||
RewriteTest,
|
||||
::testing::Values(
|
||||
RewriteParams{ "foooo", "baroo", {{"foo", "bar"}, {"bar", "baz"}}},
|
||||
RewriteParams{ "foooo", "bazoo", {{"fou", "bar"}, {"foo", "baz"}}},
|
||||
RewriteParams{ "foooo", "foooo", {}}
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
|
@ -1853,6 +1853,7 @@ void setStackSize(size_t stackSize)
|
|||
|
||||
#if __linux__
|
||||
static AutoCloseFD fdSavedMountNamespace;
|
||||
static AutoCloseFD fdSavedRoot;
|
||||
#endif
|
||||
|
||||
void saveMountNamespace()
|
||||
|
@ -1860,10 +1861,11 @@ void saveMountNamespace()
|
|||
#if __linux__
|
||||
static std::once_flag done;
|
||||
std::call_once(done, []() {
|
||||
AutoCloseFD fd = open("/proc/self/ns/mnt", O_RDONLY);
|
||||
if (!fd)
|
||||
fdSavedMountNamespace = open("/proc/self/ns/mnt", O_RDONLY);
|
||||
if (!fdSavedMountNamespace)
|
||||
throw SysError("saving parent mount namespace");
|
||||
fdSavedMountNamespace = std::move(fd);
|
||||
|
||||
fdSavedRoot = open("/proc/self/root", O_RDONLY);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
@ -1876,9 +1878,16 @@ void restoreMountNamespace()
|
|||
|
||||
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
|
||||
throw SysError("restoring parent mount namespace");
|
||||
if (chdir(savedCwd.c_str()) == -1) {
|
||||
throw SysError("restoring cwd");
|
||||
|
||||
if (fdSavedRoot) {
|
||||
if (fchdir(fdSavedRoot.get()))
|
||||
throw SysError("chdir into saved root");
|
||||
if (chroot("."))
|
||||
throw SysError("chroot into saved root");
|
||||
}
|
||||
|
||||
if (chdir(savedCwd.c_str()) == -1)
|
||||
throw SysError("restoring cwd");
|
||||
} catch (Error & e) {
|
||||
debug(e.msg());
|
||||
}
|
||||
|
|
|
@ -256,14 +256,12 @@ inline Paths createDirs(PathView path)
|
|||
/**
|
||||
* Create a symlink.
|
||||
*/
|
||||
void createSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime = {});
|
||||
void createSymlink(const Path & target, const Path & link);
|
||||
|
||||
/**
|
||||
* Atomically create or replace a symlink.
|
||||
*/
|
||||
void replaceSymlink(const Path & target, const Path & link,
|
||||
std::optional<time_t> mtime = {});
|
||||
void replaceSymlink(const Path & target, const Path & link);
|
||||
|
||||
void renameFile(const Path & src, const Path & dst);
|
||||
|
||||
|
|
|
@ -177,6 +177,7 @@ static int main_nix_channel(int argc, char ** argv)
|
|||
cRemove,
|
||||
cList,
|
||||
cUpdate,
|
||||
cListGenerations,
|
||||
cRollback
|
||||
} cmd = cNone;
|
||||
std::vector<std::string> args;
|
||||
|
@ -193,6 +194,8 @@ static int main_nix_channel(int argc, char ** argv)
|
|||
cmd = cList;
|
||||
} else if (*arg == "--update") {
|
||||
cmd = cUpdate;
|
||||
} else if (*arg == "--list-generations") {
|
||||
cmd = cListGenerations;
|
||||
} else if (*arg == "--rollback") {
|
||||
cmd = cRollback;
|
||||
} else {
|
||||
|
@ -237,6 +240,11 @@ static int main_nix_channel(int argc, char ** argv)
|
|||
case cUpdate:
|
||||
update(StringSet(args.begin(), args.end()));
|
||||
break;
|
||||
case cListGenerations:
|
||||
if (!args.empty())
|
||||
throw UsageError("'--list-generations' expects no arguments");
|
||||
std::cout << runProgram(settings.nixBinDir + "/nix-env", false, {"--profile", profile, "--list-generations"}) << std::flush;
|
||||
break;
|
||||
case cRollback:
|
||||
if (args.size() > 1)
|
||||
throw UsageError("'--rollback' has at most one argument");
|
||||
|
|
|
@ -41,9 +41,10 @@ void removeOldGenerations(std::string dir)
|
|||
}
|
||||
if (link.find("link") != std::string::npos) {
|
||||
printInfo("removing old generations of profile %s", path);
|
||||
if (deleteOlderThan != "")
|
||||
deleteGenerationsOlderThan(path, deleteOlderThan, dryRun);
|
||||
else
|
||||
if (deleteOlderThan != "") {
|
||||
auto t = parseOlderThanTimeSpec(deleteOlderThan);
|
||||
deleteGenerationsOlderThan(path, t, dryRun);
|
||||
} else
|
||||
deleteOldGenerations(path, dryRun);
|
||||
}
|
||||
} else if (type == DT_DIR) {
|
||||
|
|
|
@ -772,7 +772,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
|
|||
|
||||
debug("switching to new user environment");
|
||||
Path generation = createGeneration(
|
||||
ref<LocalFSStore>(store2),
|
||||
*store2,
|
||||
globals.profile,
|
||||
drv.queryOutPath());
|
||||
switchLink(globals.profile, generation);
|
||||
|
@ -1356,13 +1356,14 @@ static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opAr
|
|||
if (opArgs.size() == 1 && opArgs.front() == "old") {
|
||||
deleteOldGenerations(globals.profile, globals.dryRun);
|
||||
} else if (opArgs.size() == 1 && opArgs.front().find('d') != std::string::npos) {
|
||||
deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun);
|
||||
auto t = parseOlderThanTimeSpec(opArgs.front());
|
||||
deleteGenerationsOlderThan(globals.profile, t, globals.dryRun);
|
||||
} else if (opArgs.size() == 1 && opArgs.front().find('+') != std::string::npos) {
|
||||
if (opArgs.front().size() < 2)
|
||||
throw Error("invalid number of generations '%1%'", opArgs.front());
|
||||
auto str_max = opArgs.front().substr(1);
|
||||
auto max = string2Int<GenerationNumber>(str_max);
|
||||
if (!max || *max == 0)
|
||||
if (!max)
|
||||
throw Error("invalid number of generations to keep '%1%'", opArgs.front());
|
||||
deleteGenerationsGreaterThan(globals.profile, *max, globals.dryRun);
|
||||
} else {
|
||||
|
|
|
@ -158,7 +158,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
|||
}
|
||||
|
||||
debug("switching to new user environment");
|
||||
Path generation = createGeneration(ref<LocalFSStore>(store2), profile, topLevelOut);
|
||||
Path generation = createGeneration(*store2, profile, topLevelOut);
|
||||
switchLink(profile, generation);
|
||||
}
|
||||
|
||||
|
|
|
@ -806,9 +806,10 @@ struct CmdProfileWipeHistory : virtual StoreCommand, MixDefaultProfile, MixDryRu
|
|||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
if (minAge)
|
||||
deleteGenerationsOlderThan(*profile, *minAge, dryRun);
|
||||
else
|
||||
if (minAge) {
|
||||
auto t = parseOlderThanTimeSpec(*minAge);
|
||||
deleteGenerationsOlderThan(*profile, t, dryRun);
|
||||
} else
|
||||
deleteOldGenerations(*profile, dryRun);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue