Introduce FSInputAccessor and use it

Backported from the lazy-trees branch. Note that this doesn't yet use
the access control features of FSInputAccessor.
This commit is contained in:
Eelco Dolstra 2023-10-18 15:32:31 +02:00
parent e92cac789f
commit ea38605d11
18 changed files with 502 additions and 126 deletions

View file

@ -132,7 +132,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
if (colon == std::string::npos) fail(); if (colon == std::string::npos) fail();
std::string filename(fn, 0, colon); std::string filename(fn, 0, colon);
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos)); auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {CanonPath(fn.substr(0, colon)), lineno}; return {SourcePath{path.accessor, CanonPath(fn.substr(0, colon))}, lineno};
} catch (std::invalid_argument & e) { } catch (std::invalid_argument & e) {
fail(); fail();
abort(); abort();

View file

@ -12,6 +12,7 @@
#include "function-trace.hh" #include "function-trace.hh"
#include "profiles.hh" #include "profiles.hh"
#include "print.hh" #include "print.hh"
#include "fs-input-accessor.hh"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@ -503,6 +504,18 @@ EvalState::EvalState(
, sOutputSpecified(symbols.create("outputSpecified")) , sOutputSpecified(symbols.create("outputSpecified"))
, repair(NoRepair) , repair(NoRepair)
, emptyBindings(0) , emptyBindings(0)
, rootFS(
makeFSInputAccessor(
CanonPath::root,
evalSettings.restrictEval || evalSettings.pureEval
? std::optional<std::set<CanonPath>>(std::set<CanonPath>())
: std::nullopt,
[](const CanonPath & path) -> RestrictedPathError {
auto modeInformation = evalSettings.pureEval
? "in pure evaluation mode (use '--impure' to override)"
: "in restricted mode";
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
}))
, derivationInternal(rootPath(CanonPath("/builtin/derivation.nix"))) , derivationInternal(rootPath(CanonPath("/builtin/derivation.nix")))
, store(store) , store(store)
, buildStore(buildStore ? buildStore : store) , buildStore(buildStore ? buildStore : store)
@ -518,6 +531,8 @@ EvalState::EvalState(
, baseEnv(allocEnv(128)) , baseEnv(allocEnv(128))
, staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)} , staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)}
{ {
rootFS->allowPath(CanonPath::root); // FIXME
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
assert(gcInitialised); assert(gcInitialised);
@ -599,7 +614,7 @@ SourcePath EvalState::checkSourcePath(const SourcePath & path_)
*/ */
Path abspath = canonPath(path_.path.abs()); Path abspath = canonPath(path_.path.abs());
if (hasPrefix(abspath, corepkgsPrefix)) return CanonPath(abspath); if (hasPrefix(abspath, corepkgsPrefix)) return rootPath(CanonPath(abspath));
for (auto & i : *allowedPaths) { for (auto & i : *allowedPaths) {
if (isDirOrInDir(abspath, i)) { if (isDirOrInDir(abspath, i)) {
@ -617,7 +632,7 @@ SourcePath EvalState::checkSourcePath(const SourcePath & path_)
/* Resolve symlinks. */ /* Resolve symlinks. */
debug("checking access to '%s'", abspath); debug("checking access to '%s'", abspath);
SourcePath path = CanonPath(canonPath(abspath, true)); SourcePath path = rootPath(CanonPath(canonPath(abspath, true)));
for (auto & i : *allowedPaths) { for (auto & i : *allowedPaths) {
if (isDirOrInDir(path.path.abs(), i)) { if (isDirOrInDir(path.path.abs(), i)) {
@ -649,12 +664,12 @@ void EvalState::checkURI(const std::string & uri)
/* If the URI is a path, then check it against allowedPaths as /* If the URI is a path, then check it against allowedPaths as
well. */ well. */
if (hasPrefix(uri, "/")) { if (hasPrefix(uri, "/")) {
checkSourcePath(CanonPath(uri)); checkSourcePath(rootPath(CanonPath(uri)));
return; return;
} }
if (hasPrefix(uri, "file://")) { if (hasPrefix(uri, "file://")) {
checkSourcePath(CanonPath(std::string(uri, 7))); checkSourcePath(rootPath(CanonPath(std::string(uri, 7))));
return; return;
} }
@ -950,7 +965,7 @@ void Value::mkStringMove(const char * s, const NixStringContext & context)
void Value::mkPath(const SourcePath & path) void Value::mkPath(const SourcePath & path)
{ {
mkPath(makeImmutableString(path.path.abs())); mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
} }
@ -2037,7 +2052,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
else if (firstType == nPath) { else if (firstType == nPath) {
if (!context.empty()) if (!context.empty())
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>(); state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(CanonPath(canonPath(str()))); v.mkPath(state.rootPath(CanonPath(canonPath(str()))));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
} }
@ -2236,7 +2251,7 @@ BackedStringView EvalState::coerceToString(
!canonicalizePath && !copyToStore !canonicalizePath && !copyToStore
? // FIXME: hack to preserve path literals that end in a ? // FIXME: hack to preserve path literals that end in a
// slash, as in /foo/${x}. // slash, as in /foo/${x}.
v._path v._path.path
: copyToStore : copyToStore
? store->printStorePath(copyPathToStore(context, v.path())) ? store->printStorePath(copyPathToStore(context, v.path()))
: std::string(v.path().path.abs()); : std::string(v.path().path.abs());
@ -2329,7 +2344,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/') if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>(); error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return CanonPath(path); return rootPath(CanonPath(path));
} }
@ -2429,7 +2444,9 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
return v1.string_view().compare(v2.string_view()) == 0; return v1.string_view().compare(v2.string_view()) == 0;
case nPath: case nPath:
return strcmp(v1._path, v2._path) == 0; return
v1._path.accessor == v2._path.accessor
&& strcmp(v1._path.path, v2._path.path) == 0;
case nNull: case nNull:
return true; return true;

View file

@ -24,6 +24,7 @@ class EvalState;
class StorePath; class StorePath;
struct SingleDerivedPath; struct SingleDerivedPath;
enum RepairFlag : bool; enum RepairFlag : bool;
struct FSInputAccessor;
/** /**
@ -211,6 +212,8 @@ public:
Bindings emptyBindings; Bindings emptyBindings;
const ref<FSInputAccessor> rootFS;
const SourcePath derivationInternal; const SourcePath derivationInternal;
/** /**

View file

@ -223,9 +223,9 @@ static Flake getFlake(
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir); throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
Value vInfo; Value vInfo;
state.evalFile(CanonPath(flakeFile), vInfo, true); // FIXME: symlink attack state.evalFile(state.rootPath(CanonPath(flakeFile)), vInfo, true); // FIXME: symlink attack
expectType(state, nAttrs, vInfo, state.positions.add({CanonPath(flakeFile)}, 1, 1)); expectType(state, nAttrs, vInfo, state.positions.add({state.rootPath(CanonPath(flakeFile))}, 1, 1));
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);
@ -741,7 +741,7 @@ void callFlake(EvalState & state,
state.vCallFlake = allocRootValue(state.allocValue()); state.vCallFlake = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "call-flake.nix.gen.hh" #include "call-flake.nix.gen.hh"
, CanonPath::root), **state.vCallFlake); , state.rootPath(CanonPath::root)), **state.vCallFlake);
} }
state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos); state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);

View file

@ -20,7 +20,6 @@ MakeError(Abort, EvalError);
MakeError(TypeError, EvalError); MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, Error); MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError); MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error);
/** /**
* Position objects. * Position objects.
@ -200,9 +199,13 @@ struct ExprString : Expr
struct ExprPath : Expr struct ExprPath : Expr
{ {
ref<InputAccessor> accessor;
std::string s; std::string s;
Value v; Value v;
ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); }; ExprPath(ref<InputAccessor> accessor, std::string s) : accessor(accessor), s(std::move(s))
{
v.mkPath(&*accessor, this->s.c_str());
}
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS COMMON_METHODS
}; };

View file

@ -64,6 +64,7 @@ struct StringToken {
#include "parser-tab.hh" #include "parser-tab.hh"
#include "lexer-tab.hh" #include "lexer-tab.hh"
#include "fs-input-accessor.hh"
YY_DECL; YY_DECL;
@ -520,7 +521,7 @@ path_start
/* add back in the trailing '/' to the first segment */ /* add back in the trailing '/' to the first segment */
if ($1.p[$1.l-1] == '/' && $1.l > 1) if ($1.p[$1.l-1] == '/' && $1.l > 1)
path += "/"; path += "/";
$$ = new ExprPath(std::move(path)); $$ = new ExprPath(ref<InputAccessor>(data->state.rootFS), std::move(path));
} }
| HPATH { | HPATH {
if (evalSettings.pureEval) { if (evalSettings.pureEval) {
@ -530,7 +531,7 @@ path_start
); );
} }
Path path(getHome() + std::string($1.p + 1, $1.l - 1)); Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(std::move(path)); $$ = new ExprPath(ref<InputAccessor>(data->state.rootFS), std::move(path));
} }
; ;
@ -756,11 +757,11 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
auto r = *rOpt; auto r = *rOpt;
Path res = suffix == "" ? r : concatStrings(r, "/", suffix); Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
if (pathExists(res)) return CanonPath(canonPath(res)); if (pathExists(res)) return rootPath(CanonPath(canonPath(res)));
} }
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return CanonPath(concatStrings(corepkgsPrefix, path.substr(4))); return rootPath(CanonPath(concatStrings(corepkgsPrefix, path.substr(4))));
debugThrow(ThrownError({ debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval .msg = hintfmt(evalSettings.pureEval

View file

@ -1,10 +1,11 @@
#include "eval.hh" #include "eval.hh"
#include "fs-input-accessor.hh"
namespace nix { namespace nix {
SourcePath EvalState::rootPath(CanonPath path) SourcePath EvalState::rootPath(CanonPath path)
{ {
return path; return {rootFS, std::move(path)};
} }
} }

View file

@ -202,7 +202,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
state.vImportedDrvToDerivation = allocRootValue(state.allocValue()); state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "imported-drv-to-derivation.nix.gen.hh" #include "imported-drv-to-derivation.nix.gen.hh"
, CanonPath::root), **state.vImportedDrvToDerivation); , state.rootPath(CanonPath::root)), **state.vImportedDrvToDerivation);
} }
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh"); state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
@ -213,7 +213,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
else if (path2 == corepkgsPrefix + "fetchurl.nix") { else if (path2 == corepkgsPrefix + "fetchurl.nix") {
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "fetchurl.nix.gen.hh" #include "fetchurl.nix.gen.hh"
, CanonPath::root), v); , state.rootPath(CanonPath::root)), v);
} }
else { else {
@ -599,7 +599,8 @@ struct CompareValues
case nString: case nString:
return v1->string_view().compare(v2->string_view()) < 0; return v1->string_view().compare(v2->string_view()) < 0;
case nPath: case nPath:
return strcmp(v1->_path, v2->_path) < 0; // FIXME: handle accessor?
return strcmp(v1->_path.path, v2->_path.path) < 0;
case nList: case nList:
// Lexicographic comparison // Lexicographic comparison
for (size_t i = 0;; i++) { for (size_t i = 0;; i++) {
@ -2203,7 +2204,7 @@ static void addPath(
path = evalSettings.pureEval && expectedHash path = evalSettings.pureEval && expectedHash
? path ? path
: state.checkSourcePath(CanonPath(path)).path.abs(); : state.checkSourcePath(state.rootPath(CanonPath(path))).path.abs();
PathFilter filter = filterFun ? ([&](const Path & path) { PathFilter filter = filterFun ? ([&](const Path & path) {
auto st = lstat(path); auto st = lstat(path);
@ -2236,9 +2237,10 @@ static void addPath(
}); });
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) { if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
StorePath dstPath = settings.readOnlyMode // FIXME
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first if (method != FileIngestionMethod::Recursive)
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs); throw Error("'recursive = false' is not implemented");
auto dstPath = state.rootPath(CanonPath(path)).fetchToStore(state.store, name, &filter, state.repair);
if (expectedHash && expectedStorePath != dstPath) if (expectedHash && expectedStorePath != dstPath)
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path)); state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
state.allowAndSetStorePathString(dstPath, v); state.allowAndSetStorePathString(dstPath, v);
@ -4447,7 +4449,7 @@ void EvalState::createBaseEnv()
// the parser needs two NUL bytes as terminators; one of them // the parser needs two NUL bytes as terminators; one of them
// is implied by being a C string. // is implied by being a C string.
"\0"; "\0";
eval(parse(code, sizeof(code), derivationInternal, {CanonPath::root}, staticBaseEnv), *vDerivation); eval(parse(code, sizeof(code), derivationInternal, rootPath(CanonPath::root), staticBaseEnv), *vDerivation);
} }

View file

@ -62,7 +62,7 @@ namespace nix {
// not supported by store 'dummy'" thrown in the test body. // not supported by store 'dummy'" thrown in the test body.
TEST_F(JSONValueTest, DISABLED_Path) { TEST_F(JSONValueTest, DISABLED_Path) {
Value v; Value v;
v.mkPath("test"); v.mkPath(state.rootPath(CanonPath("/test")));
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\""); ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
} }
} /* namespace nix */ } /* namespace nix */

View file

@ -103,14 +103,17 @@ namespace nix {
} }
MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) { MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) {
if (arg.type() != nPath) { if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type(); *result_listener << "Expected a path got " << arg.type();
return false; return false;
} else if (std::string_view(arg._path) != p) { } else {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << arg.c_str(); auto path = arg.path();
if (path.path != CanonPath(p)) {
*result_listener << "Expected a path that equals \"" << p << "\" but got: " << path.path;
return false; return false;
} }
return true; }
return true;
} }

View file

@ -189,7 +189,12 @@ public:
const char * c_str; const char * c_str;
const char * * context; // must be in sorted order const char * * context; // must be in sorted order
} string; } string;
const char * _path;
struct {
InputAccessor * accessor;
const char * path;
} _path;
Bindings * attrs; Bindings * attrs;
struct { struct {
size_t size; size_t size;
@ -286,11 +291,12 @@ public:
void mkPath(const SourcePath & path); void mkPath(const SourcePath & path);
inline void mkPath(const char * path) inline void mkPath(InputAccessor * accessor, const char * path)
{ {
clearValue(); clearValue();
internalType = tPath; internalType = tPath;
_path = path; _path.accessor = accessor;
_path.path = path;
} }
inline void mkNull() inline void mkNull()
@ -437,7 +443,10 @@ public:
SourcePath path() const SourcePath path() const
{ {
assert(internalType == tPath); assert(internalType == tPath);
return SourcePath{CanonPath(_path)}; return SourcePath {
.accessor = ref(_path.accessor->shared_from_this()),
.path = CanonPath(CanonPath::unchecked_t(), _path.path)
};
} }
std::string_view string_view() const std::string_view string_view() const

View file

@ -0,0 +1,141 @@
#include "fs-input-accessor.hh"
#include "store-api.hh"
namespace nix {
struct FSInputAccessorImpl : FSInputAccessor
{
CanonPath root;
std::optional<std::set<CanonPath>> allowedPaths;
MakeNotAllowedError makeNotAllowedError;
FSInputAccessorImpl(
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths,
MakeNotAllowedError && makeNotAllowedError)
: root(root)
, allowedPaths(std::move(allowedPaths))
, makeNotAllowedError(std::move(makeNotAllowedError))
{
}
std::string readFile(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
return nix::readFile(absPath.abs());
}
bool pathExists(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
return isAllowed(absPath) && nix::pathExists(absPath.abs());
}
Stat lstat(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
auto st = nix::lstat(absPath.abs());
return Stat {
.type =
S_ISREG(st.st_mode) ? tRegular :
S_ISDIR(st.st_mode) ? tDirectory :
S_ISLNK(st.st_mode) ? tSymlink :
tMisc,
.isExecutable = S_ISREG(st.st_mode) && st.st_mode & S_IXUSR
};
}
DirEntries readDirectory(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
DirEntries res;
for (auto & entry : nix::readDirectory(absPath.abs())) {
std::optional<Type> type;
switch (entry.type) {
case DT_REG: type = Type::tRegular; break;
case DT_LNK: type = Type::tSymlink; break;
case DT_DIR: type = Type::tDirectory; break;
}
if (isAllowed(absPath + entry.name))
res.emplace(entry.name, type);
}
return res;
}
std::string readLink(const CanonPath & path) override
{
auto absPath = makeAbsPath(path);
checkAllowed(absPath);
return nix::readLink(absPath.abs());
}
CanonPath makeAbsPath(const CanonPath & path)
{
return root + path;
}
void checkAllowed(const CanonPath & absPath) override
{
if (!isAllowed(absPath))
throw makeNotAllowedError
? makeNotAllowedError(absPath)
: RestrictedPathError("access to path '%s' is forbidden", absPath);
}
bool isAllowed(const CanonPath & absPath)
{
if (!absPath.isWithin(root))
return false;
if (allowedPaths) {
auto p = absPath.removePrefix(root);
if (!p.isAllowed(*allowedPaths))
return false;
}
return true;
}
void allowPath(CanonPath path) override
{
if (allowedPaths)
allowedPaths->insert(std::move(path));
}
bool hasAccessControl() override
{
return (bool) allowedPaths;
}
std::optional<CanonPath> getPhysicalPath(const CanonPath & path) override
{
return makeAbsPath(path);
}
};
ref<FSInputAccessor> makeFSInputAccessor(
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths,
MakeNotAllowedError && makeNotAllowedError)
{
return make_ref<FSInputAccessorImpl>(root, std::move(allowedPaths), std::move(makeNotAllowedError));
}
ref<FSInputAccessor> makeStorePathAccessor(
ref<Store> store,
const StorePath & storePath,
MakeNotAllowedError && makeNotAllowedError)
{
return makeFSInputAccessor(CanonPath(store->toRealPath(storePath)), {}, std::move(makeNotAllowedError));
}
SourcePath getUnfilteredRootPath(CanonPath path)
{
static auto rootFS = makeFSInputAccessor(CanonPath::root);
return {rootFS, path};
}
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "input-accessor.hh"
namespace nix {
class StorePath;
class Store;
struct FSInputAccessor : InputAccessor
{
virtual void checkAllowed(const CanonPath & absPath) = 0;
virtual void allowPath(CanonPath path) = 0;
virtual bool hasAccessControl() = 0;
};
typedef std::function<RestrictedPathError(const CanonPath & path)> MakeNotAllowedError;
ref<FSInputAccessor> makeFSInputAccessor(
const CanonPath & root,
std::optional<std::set<CanonPath>> && allowedPaths = {},
MakeNotAllowedError && makeNotAllowedError = {});
ref<FSInputAccessor> makeStorePathAccessor(
ref<Store> store,
const StorePath & storePath,
MakeNotAllowedError && makeNotAllowedError = {});
SourcePath getUnfilteredRootPath(CanonPath path);
}

View file

@ -3,12 +3,149 @@
namespace nix { namespace nix {
static std::atomic<size_t> nextNumber{0};
InputAccessor::InputAccessor()
: number(++nextNumber)
{
}
// FIXME: merge with archive.cc.
void InputAccessor::dumpPath(
const CanonPath & path,
Sink & sink,
PathFilter & filter)
{
auto dumpContents = [&](const CanonPath & path)
{
// FIXME: pipe
auto s = readFile(path);
sink << "contents" << s.size();
sink(s);
writePadding(s.size(), sink);
};
std::function<void(const CanonPath & path)> dump;
dump = [&](const CanonPath & path) {
checkInterrupt();
auto st = lstat(path);
sink << "(";
if (st.type == tRegular) {
sink << "type" << "regular";
if (st.isExecutable)
sink << "executable" << "";
dumpContents(path);
}
else if (st.type == tDirectory) {
sink << "type" << "directory";
/* If we're on a case-insensitive system like macOS, undo
the case hack applied by restorePath(). */
std::map<std::string, std::string> unhacked;
for (auto & i : readDirectory(path))
if (/* archiveSettings.useCaseHack */ false) { // FIXME
std::string name(i.first);
size_t pos = i.first.find(caseHackSuffix);
if (pos != std::string::npos) {
debug("removing case hack suffix from '%s'", path + i.first);
name.erase(pos);
}
if (!unhacked.emplace(name, i.first).second)
throw Error("file name collision in between '%s' and '%s'",
(path + unhacked[name]),
(path + i.first));
} else
unhacked.emplace(i.first, i.first);
for (auto & i : unhacked)
if (filter((path + i.first).abs())) {
sink << "entry" << "(" << "name" << i.first << "node";
dump(path + i.second);
sink << ")";
}
}
else if (st.type == tSymlink)
sink << "type" << "symlink" << "target" << readLink(path);
else throw Error("file '%s' has an unsupported type", path);
sink << ")";
};
sink << narVersionMagic1;
dump(path);
}
Hash InputAccessor::hashPath(
const CanonPath & path,
PathFilter & filter,
HashType ht)
{
HashSink sink(ht);
dumpPath(path, sink, filter);
return sink.finish().first;
}
StorePath InputAccessor::fetchToStore(
ref<Store> store,
const CanonPath & path,
std::string_view name,
PathFilter * filter,
RepairFlag repair)
{
Activity act(*logger, lvlChatty, actUnknown, fmt("copying '%s' to the store", showPath(path)));
auto source = sinkToSource([&](Sink & sink) {
dumpPath(path, sink, filter ? *filter : defaultPathFilter);
});
auto storePath =
settings.readOnlyMode
? store->computeStorePathFromDump(*source, name).first
: store->addToStoreFromDump(*source, name, FileIngestionMethod::Recursive, htSHA256, repair);
return storePath;
}
std::optional<InputAccessor::Stat> InputAccessor::maybeLstat(const CanonPath & path)
{
// FIXME: merge these into one operation.
if (!pathExists(path))
return {};
return lstat(path);
}
std::string InputAccessor::showPath(const CanonPath & path)
{
return path.abs();
}
SourcePath InputAccessor::root()
{
return {ref(shared_from_this()), CanonPath::root};
}
std::ostream & operator << (std::ostream & str, const SourcePath & path) std::ostream & operator << (std::ostream & str, const SourcePath & path)
{ {
str << path.to_string(); str << path.to_string();
return str; return str;
} }
StorePath SourcePath::fetchToStore(
ref<Store> store,
std::string_view name,
PathFilter * filter,
RepairFlag repair) const
{
return accessor->fetchToStore(store, path, name, filter, repair);
}
std::string_view SourcePath::baseName() const std::string_view SourcePath::baseName() const
{ {
return path.baseName().value_or("source"); return path.baseName().value_or("source");
@ -18,60 +155,12 @@ SourcePath SourcePath::parent() const
{ {
auto p = path.parent(); auto p = path.parent();
assert(p); assert(p);
return std::move(*p); return {accessor, std::move(*p)};
}
InputAccessor::Stat SourcePath::lstat() const
{
auto st = nix::lstat(path.abs());
return InputAccessor::Stat {
.type =
S_ISREG(st.st_mode) ? InputAccessor::tRegular :
S_ISDIR(st.st_mode) ? InputAccessor::tDirectory :
S_ISLNK(st.st_mode) ? InputAccessor::tSymlink :
InputAccessor::tMisc,
.isExecutable = S_ISREG(st.st_mode) && st.st_mode & S_IXUSR
};
}
std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
{
// FIXME: merge these into one operation.
if (!pathExists())
return {};
return lstat();
}
InputAccessor::DirEntries SourcePath::readDirectory() const
{
InputAccessor::DirEntries res;
for (auto & entry : nix::readDirectory(path.abs())) {
std::optional<InputAccessor::Type> type;
switch (entry.type) {
case DT_REG: type = InputAccessor::Type::tRegular; break;
case DT_LNK: type = InputAccessor::Type::tSymlink; break;
case DT_DIR: type = InputAccessor::Type::tDirectory; break;
}
res.emplace(entry.name, type);
}
return res;
}
StorePath SourcePath::fetchToStore(
ref<Store> store,
std::string_view name,
PathFilter * filter,
RepairFlag repair) const
{
return
settings.readOnlyMode
? store->computeStorePathForPath(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter).first
: store->addToStore(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter, repair);
} }
SourcePath SourcePath::resolveSymlinks() const SourcePath SourcePath::resolveSymlinks() const
{ {
SourcePath res(CanonPath::root); auto res = accessor->root();
int linksAllowed = 1024; int linksAllowed = 1024;

View file

@ -5,14 +5,29 @@
#include "archive.hh" #include "archive.hh"
#include "canon-path.hh" #include "canon-path.hh"
#include "repair-flag.hh" #include "repair-flag.hh"
#include "hash.hh"
namespace nix { namespace nix {
MakeError(RestrictedPathError, Error);
struct SourcePath;
class StorePath; class StorePath;
class Store; class Store;
struct InputAccessor struct InputAccessor : public std::enable_shared_from_this<InputAccessor>
{ {
const size_t number;
InputAccessor();
virtual ~InputAccessor()
{ }
virtual std::string readFile(const CanonPath & path) = 0;
virtual bool pathExists(const CanonPath & path) = 0;
enum Type { enum Type {
tRegular, tSymlink, tDirectory, tRegular, tSymlink, tDirectory,
/** /**
@ -32,9 +47,63 @@ struct InputAccessor
bool isExecutable = false; // regular files only bool isExecutable = false; // regular files only
}; };
virtual Stat lstat(const CanonPath & path) = 0;
std::optional<Stat> maybeLstat(const CanonPath & path);
typedef std::optional<Type> DirEntry; typedef std::optional<Type> DirEntry;
typedef std::map<std::string, DirEntry> DirEntries; typedef std::map<std::string, DirEntry> DirEntries;
virtual DirEntries readDirectory(const CanonPath & path) = 0;
virtual std::string readLink(const CanonPath & path) = 0;
virtual void dumpPath(
const CanonPath & path,
Sink & sink,
PathFilter & filter = defaultPathFilter);
Hash hashPath(
const CanonPath & path,
PathFilter & filter = defaultPathFilter,
HashType ht = htSHA256);
StorePath fetchToStore(
ref<Store> store,
const CanonPath & path,
std::string_view name = "source",
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair);
/* Return a corresponding path in the root filesystem, if
possible. This is only possible for inputs that are
materialized in the root filesystem. */
virtual std::optional<CanonPath> getPhysicalPath(const CanonPath & path)
{ return std::nullopt; }
bool operator == (const InputAccessor & x) const
{
return number == x.number;
}
bool operator < (const InputAccessor & x) const
{
return number < x.number;
}
void setPathDisplay(std::string displayPrefix, std::string displaySuffix = "");
virtual std::string showPath(const CanonPath & path);
SourcePath root();
/* Return the maximum last-modified time of the files in this
tree, if available. */
virtual std::optional<time_t> getLastModified()
{
return std::nullopt;
}
}; };
/** /**
@ -45,12 +114,9 @@ struct InputAccessor
*/ */
struct SourcePath struct SourcePath
{ {
ref<InputAccessor> accessor;
CanonPath path; CanonPath path;
SourcePath(CanonPath path)
: path(std::move(path))
{ }
std::string_view baseName() const; std::string_view baseName() const;
/** /**
@ -64,39 +130,42 @@ struct SourcePath
* return its contents; otherwise throw an error. * return its contents; otherwise throw an error.
*/ */
std::string readFile() const std::string readFile() const
{ return nix::readFile(path.abs()); } { return accessor->readFile(path); }
/** /**
* Return whether this `SourcePath` denotes a file (of any type) * Return whether this `SourcePath` denotes a file (of any type)
* that exists * that exists
*/ */
bool pathExists() const bool pathExists() const
{ return nix::pathExists(path.abs()); } { return accessor->pathExists(path); }
/** /**
* Return stats about this `SourcePath`, or throw an exception if * Return stats about this `SourcePath`, or throw an exception if
* it doesn't exist. * it doesn't exist.
*/ */
InputAccessor::Stat lstat() const; InputAccessor::Stat lstat() const
{ return accessor->lstat(path); }
/** /**
* Return stats about this `SourcePath`, or std::nullopt if it * Return stats about this `SourcePath`, or std::nullopt if it
* doesn't exist. * doesn't exist.
*/ */
std::optional<InputAccessor::Stat> maybeLstat() const; std::optional<InputAccessor::Stat> maybeLstat() const
{ return accessor->maybeLstat(path); }
/** /**
* If this `SourcePath` denotes a directory (not a symlink), * If this `SourcePath` denotes a directory (not a symlink),
* return its directory entries; otherwise throw an error. * return its directory entries; otherwise throw an error.
*/ */
InputAccessor::DirEntries readDirectory() const; InputAccessor::DirEntries readDirectory() const
{ return accessor->readDirectory(path); }
/** /**
* If this `SourcePath` denotes a symlink, return its target; * If this `SourcePath` denotes a symlink, return its target;
* otherwise throw an error. * otherwise throw an error.
*/ */
std::string readLink() const std::string readLink() const
{ return nix::readLink(path.abs()); } { return accessor->readLink(path); }
/** /**
* Dump this `SourcePath` to `sink` as a NAR archive. * Dump this `SourcePath` to `sink` as a NAR archive.
@ -104,7 +173,7 @@ struct SourcePath
void dumpPath( void dumpPath(
Sink & sink, Sink & sink,
PathFilter & filter = defaultPathFilter) const PathFilter & filter = defaultPathFilter) const
{ return nix::dumpPath(path.abs(), sink, filter); } { return accessor->dumpPath(path, sink, filter); }
/** /**
* Copy this `SourcePath` to the Nix store. * Copy this `SourcePath` to the Nix store.
@ -120,7 +189,7 @@ struct SourcePath
* it has a physical location. * it has a physical location.
*/ */
std::optional<CanonPath> getPhysicalPath() const std::optional<CanonPath> getPhysicalPath() const
{ return path; } { return accessor->getPhysicalPath(path); }
std::string to_string() const std::string to_string() const
{ return path.abs(); } { return path.abs(); }
@ -129,7 +198,7 @@ struct SourcePath
* Append a `CanonPath` to this path. * Append a `CanonPath` to this path.
*/ */
SourcePath operator + (const CanonPath & x) const SourcePath operator + (const CanonPath & x) const
{ return {path + x}; } { return {accessor, path + x}; }
/** /**
* Append a single component `c` to this path. `c` must not * Append a single component `c` to this path. `c` must not
@ -137,21 +206,21 @@ struct SourcePath
* and `c`. * and `c`.
*/ */
SourcePath operator + (std::string_view c) const SourcePath operator + (std::string_view c) const
{ return {path + c}; } { return {accessor, path + c}; }
bool operator == (const SourcePath & x) const bool operator == (const SourcePath & x) const
{ {
return path == x.path; return std::tie(accessor, path) == std::tie(x.accessor, x.path);
} }
bool operator != (const SourcePath & x) const bool operator != (const SourcePath & x) const
{ {
return path != x.path; return std::tie(accessor, path) != std::tie(x.accessor, x.path);
} }
bool operator < (const SourcePath & x) const bool operator < (const SourcePath & x) const
{ {
return path < x.path; return std::tie(accessor, path) < std::tie(x.accessor, x.path);
} }
/** /**

View file

@ -225,12 +225,16 @@ StorePath Store::makeFixedOutputPathFromCA(std::string_view name, const ContentA
} }
std::pair<StorePath, Hash> Store::computeStorePathForPath(std::string_view name, std::pair<StorePath, Hash> Store::computeStorePathFromDump(
const Path & srcPath, FileIngestionMethod method, HashType hashAlgo, PathFilter & filter) const Source & dump,
std::string_view name,
FileIngestionMethod method,
HashType hashAlgo,
const StorePathSet & references) const
{ {
Hash h = method == FileIngestionMethod::Recursive HashSink sink(hashAlgo);
? hashPath(hashAlgo, srcPath, filter).first dump.drainInto(sink);
: hashFile(hashAlgo, srcPath); auto h = sink.finish().first;
FixedOutputInfo caInfo { FixedOutputInfo caInfo {
.method = method, .method = method,
.hash = h, .hash = h,

View file

@ -292,14 +292,15 @@ public:
StorePath makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const; StorePath makeFixedOutputPathFromCA(std::string_view name, const ContentAddressWithReferences & ca) const;
/** /**
* Preparatory part of addToStore(). * Read-only variant of addToStoreFromDump(). It returns the store
* * path to which a NAR or flat file would be written.
* @return the store path to which srcPath is to be copied
* and the cryptographic hash of the contents of srcPath.
*/ */
std::pair<StorePath, Hash> computeStorePathForPath(std::string_view name, std::pair<StorePath, Hash> computeStorePathFromDump(
const Path & srcPath, FileIngestionMethod method = FileIngestionMethod::Recursive, Source & dump,
HashType hashAlgo = htSHA256, PathFilter & filter = defaultPathFilter) const; std::string_view name,
FileIngestionMethod method = FileIngestionMethod::Recursive,
HashType hashAlgo = htSHA256,
const StorePathSet & references = {}) const;
/** /**
* Preparatory part of addTextToStore(). * Preparatory part of addTextToStore().

View file

@ -204,30 +204,30 @@ static void showHelp(std::vector<std::string> subcommand, NixArgs & toplevel)
auto vGenerateManpage = state.allocValue(); auto vGenerateManpage = state.allocValue();
state.eval(state.parseExprFromString( state.eval(state.parseExprFromString(
#include "generate-manpage.nix.gen.hh" #include "generate-manpage.nix.gen.hh"
, CanonPath::root), *vGenerateManpage); , state.rootPath(CanonPath::root)), *vGenerateManpage);
auto vUtils = state.allocValue(); auto vUtils = state.allocValue();
state.cacheFile( state.cacheFile(
CanonPath("/utils.nix"), CanonPath("/utils.nix"), state.rootPath(CanonPath("/utils.nix")), state.rootPath(CanonPath("/utils.nix")),
state.parseExprFromString( state.parseExprFromString(
#include "utils.nix.gen.hh" #include "utils.nix.gen.hh"
, CanonPath::root), , state.rootPath(CanonPath::root)),
*vUtils); *vUtils);
auto vSettingsInfo = state.allocValue(); auto vSettingsInfo = state.allocValue();
state.cacheFile( state.cacheFile(
CanonPath("/generate-settings.nix"), CanonPath("/generate-settings.nix"), state.rootPath(CanonPath("/generate-settings.nix")), state.rootPath(CanonPath("/generate-settings.nix")),
state.parseExprFromString( state.parseExprFromString(
#include "generate-settings.nix.gen.hh" #include "generate-settings.nix.gen.hh"
, CanonPath::root), , state.rootPath(CanonPath::root)),
*vSettingsInfo); *vSettingsInfo);
auto vStoreInfo = state.allocValue(); auto vStoreInfo = state.allocValue();
state.cacheFile( state.cacheFile(
CanonPath("/generate-store-info.nix"), CanonPath("/generate-store-info.nix"), state.rootPath(CanonPath("/generate-store-info.nix")), state.rootPath(CanonPath("/generate-store-info.nix")),
state.parseExprFromString( state.parseExprFromString(
#include "generate-store-info.nix.gen.hh" #include "generate-store-info.nix.gen.hh"
, CanonPath::root), , state.rootPath(CanonPath::root)),
*vStoreInfo); *vStoreInfo);
auto vDump = state.allocValue(); auto vDump = state.allocValue();