Fix flakes

This commit is contained in:
Eelco Dolstra 2022-02-24 21:03:41 +01:00
parent 4b313ceb9e
commit 1d36d16086
8 changed files with 83 additions and 65 deletions

View file

@ -89,12 +89,19 @@ static void expectType(EvalState & state, ValueType type,
} }
static std::map<FlakeId, FlakeInput> parseFlakeInputs( static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const Pos & pos, EvalState & state,
const std::optional<Path> & baseDir, InputPath lockRootPath); Value * value,
const Pos & pos,
const std::optional<Path> & baseDir,
const InputPath & lockRootPath);
static FlakeInput parseFlakeInput(EvalState & state, static FlakeInput parseFlakeInput(
const std::string & inputName, Value * value, const Pos & pos, EvalState & state,
const std::optional<Path> & baseDir, InputPath lockRootPath) const std::string & inputName,
Value * value,
const Pos & pos,
const std::optional<Path> & baseDir,
const InputPath & lockRootPath)
{ {
expectType(state, nAttrs, *value, pos); expectType(state, nAttrs, *value, pos);
@ -168,8 +175,11 @@ static FlakeInput parseFlakeInput(EvalState & state,
} }
static std::map<FlakeId, FlakeInput> parseFlakeInputs( static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const Pos & pos, EvalState & state,
const std::optional<Path> & baseDir, InputPath lockRootPath) Value * value,
const Pos & pos,
const std::optional<Path> & baseDir,
const InputPath & lockRootPath)
{ {
std::map<FlakeId, FlakeInput> inputs; std::map<FlakeId, FlakeInput> inputs;
@ -188,38 +198,31 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
return inputs; return inputs;
} }
static Flake getFlake( static Flake readFlake(
EvalState & state, EvalState & state,
const FlakeRef & originalRef, const FlakeRef & lockedRef,
bool allowLookup, nix::ref<InputAccessor> accessor,
FlakeCache & flakeCache, const InputPath & lockRootPath)
InputPath lockRootPath)
{ {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree( auto flakeDir = canonPath("/" + lockedRef.subdir);
state, originalRef, allowLookup, flakeCache); SourcePath flakePath{accessor, canonPath(flakeDir + "/flake.nix")};
// Guard against symlink attacks. if (!flakePath.pathExists())
auto flakeDir = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir, true); throw Error("source tree referenced by '%s' does not contain a file named '%s'", lockedRef, flakePath.path);
auto flakeFile = canonPath(flakeDir + "/flake.nix", true);
if (!isInDir(flakeFile, sourceInfo.actualPath))
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
lockedRef, state.store->printStorePath(sourceInfo.storePath));
Flake flake {
.originalRef = originalRef,
.resolvedRef = resolvedRef,
.lockedRef = lockedRef,
//.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo))
};
if (!pathExists(flakeFile))
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
Value vInfo; Value vInfo;
// FIXME: use accessor state.evalFile(flakePath, vInfo, true);
state.evalFile(state.rootPath(flakeFile), vInfo, true); // FIXME: symlink attack
expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0)); expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(state.packPath(flakePath)), 0, 0));
Flake flake {
// FIXME
.originalRef = lockedRef,
.resolvedRef = lockedRef,
.lockedRef = lockedRef,
.accessor = accessor,
.flakePath = dirOf(flakePath.path),
};
if (auto description = vInfo.attrs->get(state.sDescription)) { if (auto description = vInfo.attrs->get(state.sDescription)) {
expectType(state, nString, *description->value, *description->pos); expectType(state, nString, *description->value, *description->pos);
@ -295,6 +298,19 @@ static Flake getFlake(
return flake; return flake;
} }
static Flake getFlake(
EvalState & state,
const FlakeRef & originalRef,
bool allowLookup,
FlakeCache & flakeCache,
const InputPath & lockRootPath)
{
// FIXME: resolve
auto [accessor, input] = originalRef.input.lazyFetch(state.store);
return readFlake(state, originalRef, accessor, lockRootPath);
}
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup, FlakeCache & flakeCache) Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup, FlakeCache & flakeCache)
{ {
return getFlake(state, originalRef, allowLookup, flakeCache, {}); return getFlake(state, originalRef, allowLookup, flakeCache, {});
@ -306,6 +322,14 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
return getFlake(state, originalRef, allowLookup, flakeCache); return getFlake(state, originalRef, allowLookup, flakeCache);
} }
static LockFile readLockFile(const Flake & flake)
{
SourcePath lockFilePath{flake.accessor, canonPath(flake.flakePath + "/flake.lock")};
return lockFilePath.pathExists()
? LockFile(lockFilePath.readFile(), fmt("%s", lockFilePath))
: LockFile();
}
/* Compute an in-memory lock file for the specified top-level flake, /* Compute an in-memory lock file for the specified top-level flake,
and optionally write it to file, if the flake is writable. */ and optionally write it to file, if the flake is writable. */
LockedFlake lockFlake( LockedFlake lockFlake(
@ -319,19 +343,16 @@ LockedFlake lockFlake(
auto useRegistries = lockFlags.useRegistries.value_or(fetchSettings.useRegistries); auto useRegistries = lockFlags.useRegistries.value_or(fetchSettings.useRegistries);
auto flake = getFlake(state, topRef, useRegistries, flakeCache); auto flake = getFlake(state, topRef, useRegistries, flakeCache, {});
if (lockFlags.applyNixConfig) { if (lockFlags.applyNixConfig) {
flake.config.apply(); flake.config.apply();
state.store->setOptions(); state.store->setOptions();
} }
#if 0
try { try {
// FIXME: symlink attack auto oldLockFile = readLockFile(flake);
auto oldLockFile = LockFile::read(
flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock");
debug("old lock file: %s", oldLockFile); debug("old lock file: %s", oldLockFile);
@ -545,8 +566,7 @@ LockedFlake lockFlake(
inputFlake.inputs, childNode, inputPath, inputFlake.inputs, childNode, inputPath,
oldLock oldLock
? std::dynamic_pointer_cast<const Node>(oldLock) ? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read( : readLockFile(inputFlake).root,
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root,
oldLock ? lockRootPath : inputPath, localPath, false); oldLock ? lockRootPath : inputPath, localPath, false);
} }
@ -565,12 +585,9 @@ LockedFlake lockFlake(
} }
}; };
// Bring in the current ref for relative path resolution if we have it
auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true);
computeLocks( computeLocks(
flake.inputs, newLockFile.root, {}, flake.inputs, newLockFile.root, {},
lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false); lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, flake.flakePath, false);
for (auto & i : lockFlags.inputOverrides) for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first)) if (!overridesUsed.count(i.first))
@ -670,9 +687,6 @@ LockedFlake lockFlake(
e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string()); e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string());
throw; throw;
} }
#endif
throw UnimplementedError("lockFlake");
} }
void callFlake(EvalState & state, void callFlake(EvalState & state,
@ -687,17 +701,13 @@ void callFlake(EvalState & state,
vLocks->mkString(lockedFlake.lockFile.to_string()); vLocks->mkString(lockedFlake.lockFile.to_string());
#if 0
emitTreeAttrs( emitTreeAttrs(
state, state,
//*lockedFlake.flake.sourceInfo, {lockedFlake.flake.accessor, lockedFlake.flake.flakePath},
lockedFlake.flake.lockedRef.input, lockedFlake.flake.lockedRef.input,
*vRootSrc, *vRootSrc,
false, false,
lockedFlake.flake.forceDirty); lockedFlake.flake.forceDirty);
#endif
throw UnimplementedError("callFlake");
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir); vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);

View file

@ -61,6 +61,8 @@ struct Flake
FlakeRef originalRef; // the original flake specification (by the user) FlakeRef originalRef; // the original flake specification (by the user)
FlakeRef resolvedRef; // registry references and caching resolved to the specific underlying flake FlakeRef resolvedRef; // registry references and caching resolved to the specific underlying flake
FlakeRef lockedRef; // the specific local store result of invoking the fetcher FlakeRef lockedRef; // the specific local store result of invoking the fetcher
ref<InputAccessor> accessor;
Path flakePath;
bool forceDirty = false; // pretend that 'lockedRef' is dirty bool forceDirty = false; // pretend that 'lockedRef' is dirty
std::optional<std::string> description; std::optional<std::string> description;
FlakeInputs inputs; FlakeInputs inputs;

View file

@ -34,7 +34,7 @@ typedef std::string FlakeId;
struct FlakeRef struct FlakeRef
{ {
/* fetcher-specific representation of the input, sufficient to /* Fetcher-specific representation of the input, sufficient to
perform the fetch operation. */ perform the fetch operation. */
fetchers::Input input; fetchers::Input input;

View file

@ -66,7 +66,7 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
return pos; return pos;
} }
LockFile::LockFile(const nlohmann::json & json, const Path & path) LockFile::LockFile(const nlohmann::json & json, std::string_view path)
{ {
auto version = json.value("version", 0); auto version = json.value("version", 0);
if (version < 5 || version > 7) if (version < 5 || version > 7)
@ -116,6 +116,11 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
// a bit since we don't need to worry about cycles. // a bit since we don't need to worry about cycles.
} }
LockFile::LockFile(std::string_view contents, std::string_view path)
: LockFile(nlohmann::json::parse(contents), path)
{
}
nlohmann::json LockFile::toJSON() const nlohmann::json LockFile::toJSON() const
{ {
nlohmann::json nodes; nlohmann::json nodes;
@ -183,12 +188,6 @@ std::string LockFile::to_string() const
return toJSON().dump(2); return toJSON().dump(2);
} }
LockFile LockFile::read(const Path & path)
{
if (!pathExists(path)) return LockFile();
return LockFile(nlohmann::json::parse(readFile(path)), path);
}
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile) std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
{ {
stream << lockFile.toJSON().dump(2); stream << lockFile.toJSON().dump(2);

View file

@ -50,7 +50,8 @@ struct LockFile
std::shared_ptr<Node> root = std::make_shared<Node>(); std::shared_ptr<Node> root = std::make_shared<Node>();
LockFile() {}; LockFile() {};
LockFile(const nlohmann::json & json, const Path & path); LockFile(const nlohmann::json & json, std::string_view path);
LockFile(std::string_view contents, std::string_view path);
nlohmann::json toJSON() const; nlohmann::json toJSON() const;

View file

@ -716,7 +716,7 @@ Expr * EvalState::parseExprFromFile(const SourcePath & path)
Expr * EvalState::parseExprFromFile(const SourcePath & path, StaticEnv & staticEnv) Expr * EvalState::parseExprFromFile(const SourcePath & path, StaticEnv & staticEnv)
{ {
auto packed = packPath(path); auto packed = packPath(path);
auto buffer = path.accessor->readFile(path.path); auto buffer = path.readFile();
// readFile hopefully have left some extra space for terminators // readFile hopefully have left some extra space for terminators
buffer.append("\0\0", 2); buffer.append("\0\0", 2);
return parse(buffer.data(), buffer.size(), foFile, packed, dirOf(packed), staticEnv); return parse(buffer.data(), buffer.size(), foFile, packed, dirOf(packed), staticEnv);

View file

@ -1394,7 +1394,7 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args,
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false }); auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
try { try {
v.mkBool(path.accessor->pathExists(path.path)); v.mkBool(path.pathExists());
} catch (SysError & e) { } catch (SysError & e) {
/* Don't give away info from errors while canonicalising /* Don't give away info from errors while canonicalising
path in restricted mode. */ path in restricted mode. */
@ -1459,7 +1459,7 @@ static RegisterPrimOp primop_dirOf({
static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
auto path = realisePath(state, pos, *args[0]); auto path = realisePath(state, pos, *args[0]);
auto s = path.accessor->readFile(path.path); auto s = path.readFile();
if (s.find((char) 0) != std::string::npos) if (s.find((char) 0) != std::string::npos)
throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path); throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path);
auto refs = auto refs =
@ -1547,7 +1547,7 @@ static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Va
auto path = realisePath(state, pos, *args[1]); auto path = realisePath(state, pos, *args[1]);
// FIXME: state.toRealPath(path, context) // FIXME: state.toRealPath(path, context)
v.mkString(hashString(*ht, path.accessor->readFile(path.path)).to_string(Base16, false)); v.mkString(hashString(*ht, path.readFile()).to_string(Base16, false));
} }
static RegisterPrimOp primop_hashFile({ static RegisterPrimOp primop_hashFile({

View file

@ -67,6 +67,12 @@ struct SourcePath
Path path; Path path;
std::string_view baseName() const; std::string_view baseName() const;
std::string readFile() const
{ return accessor->readFile(path); }
bool pathExists() const
{ return accessor->pathExists(path); }
}; };
std::ostream & operator << (std::ostream & str, const SourcePath & path); std::ostream & operator << (std::ostream & str, const SourcePath & path);