#include "config.h"
#include "local-store.hh"
#include "globals.hh"
#include "archive.hh"
#include "pathlocks.hh"
#include "aterm.hh"
#include "derivations-ast.hh"
#include "worker-protocol.hh"
#include <iostream>
#include <algorithm>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utime.h>
#include <fcntl.h>
namespace nix {
void checkStoreNotSymlink()
{
if (getEnv("NIX_IGNORE_SYMLINK_STORE") == "1") return;
Path path = nixStore;
struct stat st;
while (path != "/") {
if (lstat(path.c_str(), &st))
throw SysError(format("getting status of `%1%'") % path);
if (S_ISLNK(st.st_mode))
throw Error(format(
"the path `%1%' is a symlink; "
"this is not allowed for the Nix store and its parent directories")
% path);
path = dirOf(path);
}
}
LocalStore::LocalStore()
{
substitutablePathsLoaded = false;
schemaPath = nixDBPath + "/schema";
if (readOnlyMode) return;
checkStoreNotSymlink();
Path globalLockPath = nixDBPath + "/big-lock";
globalLock = openLockFile(globalLockPath.c_str(), true);
if (!lockFile(globalLock, ltRead, false)) {
printMsg(lvlError, "waiting for the big Nix store lock...");
lockFile(globalLock, ltRead, true);
}
createDirs(nixDBPath + "/info");
createDirs(nixDBPath + "/referrer");
//printMsg(lvlTalkative, "cannot access Nix database; continuing anyway");
//readOnlyMode = true;
int curSchema = getSchema();
if (curSchema > nixSchemaVersion)
throw Error(format("current Nix store schema is version %1%, but I only support %2%")
% curSchema % nixSchemaVersion);
if (curSchema == 0) { /* new store */
curSchema = nixSchemaVersion;
writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str());
}
if (curSchema == 1) throw Error("your Nix store is no longer supported");
if (curSchema < nixSchemaVersion) upgradeStore12();
}
LocalStore::~LocalStore()
{
try {
flushDelayedUpdates();
} catch (...) {
ignoreException();
}
}
int LocalStore::getSchema()
{
int curSchema = 0;
if (pathExists(schemaPath)) {
string s = readFile(schemaPath);
if (!string2Int(s, curSchema))
throw Error(format("`%1%' is corrupt") % schemaPath);
}
return curSchema;
}
void copyPath(const Path & src, const Path & dst, PathFilter & filter)
{
debug(format("copying `%1%' to `%2%'") % src % dst);
/* Dump an archive of the path `src' into a string buffer, then
restore the archive to `dst'. This is not a very good method
for very large paths, but `copyPath' is mainly used for small
files. */
StringSink sink;
dumpPath(src, sink, filter);
StringSource source(sink.s);
restorePath(dst, source);
}
void canonicalisePathMetaData(const Path & path, bool recurse)
{
checkInterrupt();
struct stat st;
if (lstat(path.c_str(), &st))
throw SysError(format("getting attributes of path `%1%'") % path);
/* Change ownership to the current uid. If it's a symlink, use
lchown if available, otherwise don't bother. Wrong ownership
of a symlink doesn't matter, since the owning user can't change
the symlink and can't delete it because the directory is not
writable. The only exception is top-level paths in the Nix
store (since that directory is group-writable for the Nix build
users group); we check for this case below. */
if (st.st_uid != geteuid()) {
#if HAVE_LCHOWN
if (lchown(path.c_str(), geteuid(), (gid_t) -1) == -1)
#else
if (!S_ISLNK(st.st_mode) &&
chown(path.c_str(), geteuid(), (gid_t) -1) == -1)
#endif
throw SysError(format("changing owner of `%1%' to %2%")
% path % geteuid());
}
if (!S_ISLNK(st.st_mode)) {
/* Mask out all type related bits. */
mode_t mode = st.st_mode & ~S_IFMT;
if (mode != 0444 && mode != 0555) {
mode = (st.st_mode & S_IFMT)
| 0444
| (st.st_mode & S_IXUSR ? 0111 : 0);
if (chmod(path.c_str(), mode) == -1)
throw SysError(format("changing mode of `%1%' to %2$o") % path % mode);
}
if (st.st_mtime != 0) {
struct utimbuf utimbuf;
utimbuf.actime = st.st_atime;
utimbuf.modtime = 0;
if (utime(path.c_str(), &utimbuf) == -1)
throw SysError(format("changing modification time of `%1%'") % path);
}
}
if (recurse && S_ISDIR(st.st_mode)) {
Strings names = readDirectory(path);
for (Strings::iterator i = names.begin(); i != names.end(); ++i)
canonicalisePathMetaData(path + "/" + *i, true);
}
}
void canonicalisePathMetaData(const Path & path)
{
canonicalisePathMetaData(path, true);
/* On platforms that don't have lchown(), the top-level path can't
be a symlink, since we can't change its ownership. */
struct stat st;
if (lstat(path.c_str(), &st))
throw SysError(format("getting attributes of path `%1%'") % path);
if (st.st_uid != geteuid()) {
assert(S_ISLNK(st.st_mode));
throw Error(format("wrong ownership of top-level store path `%1%'") % path);
}
}
static Path infoFileFor(const Path & path)
{
string baseName = baseNameOf(path);
return (format("%1%/info/%2%") % nixDBPath % baseName).str();
}
static Path referrersFileFor(const Path & path)
{
string baseName = baseNameOf(path);
return (format("%1%/referrer/%2%") % nixDBPath % baseName).str();
}
static Path tmpFileForAtomicUpdate(const Path & path)
{
return (format("%1%/.%2%.%3%") % dirOf(path) % getpid() % baseNameOf(path)).str();
}
static void appendReferrer(const Path & from, const Path & to, bool lock)
{
Path referrersFile = referrersFileFor(from);
PathLocks referrersLock;
if (lock) {
referrersLock.lockPaths(singleton<PathSet, Path>(referrersFile));
referrersLock.setDeletion(true);
}
AutoCloseFD fd = open(referrersFile.c_str(), O_WRONLY | O_APPEND | O_CREAT, 0666);
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
string s = " " + to;
writeFull(fd, (const unsigned char *) s.c_str(), s.size());
}
/* Atomically update the referrers file. If `purge' is true, the set
of referrers is set to `referrers'. Otherwise, the current set of
referrers is purged of invalid paths. */
void LocalStore::rewriteReferrers(const Path & path, bool purge, PathSet referrers)
{
Path referrersFile = referrersFileFor(path);
PathLocks referrersLock(singleton<PathSet, Path>(referrersFile));
referrersLock.setDeletion(true);
if (purge)
/* queryReferrers() purges invalid paths, so that's all we
need. */
queryReferrers(path, referrers);
Path tmpFile = tmpFileForAtomicUpdate(referrersFile);
AutoCloseFD fd = open(tmpFile.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
string s;
foreach (PathSet::const_iterator, i, referrers) {
s += " "; s += *i;
}
writeFull(fd, (const unsigned char *) s.c_str(), s.size());
fd.close(); /* for Windows; can't rename open file */
if (rename(tmpFile.c_str(), referrersFile.c_str()) == -1)
throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % referrersFile);
}
void LocalStore::flushDelayedUpdates()
{
foreach (PathSet::iterator, i, delayedUpdates) {
rewriteReferrers(*i, true, PathSet());
}
delayedUpdates.clear();
}
void LocalStore::registerValidPath(const Path & path,
const Hash & hash, const PathSet & references,
const Path & deriver)
{
ValidPathInfo info;
info.path = path;
info.hash = hash;
info.references = references;
info.deriver = deriver;
registerValidPath(info);
}
void LocalStore::registerValidPath(const ValidPathInfo & info, bool ignoreValidity)
{
Path infoFile = infoFileFor(info.path);
ValidPathInfo oldInfo;
if (pathExists(infoFile)) oldInfo = queryPathInfo(info.path);
/* Note that it's possible for infoFile to already exist. */
/* Acquire a lock on each referrer file. This prevents those
paths from being invalidated. (It would be a violation of the
store invariants if we registered info.path as valid while some
of its references are invalid.) NB: there can be no deadlock
here since we're acquiring the locks in sorted order. */
PathSet lockNames;
foreach (PathSet::const_iterator, i, info.references)
if (*i != info.path) lockNames.insert(referrersFileFor(*i));
PathLocks referrerLocks(lockNames);
referrerLocks.setDeletion(true);
string refs;
foreach (PathSet::const_iterator, i, info.references) {
if (!refs.empty()) refs += " ";
refs += *i;
if (!ignoreValidity && *i != info.path && !isValidPath(*i))
throw Error(format("cannot register `%1%' as valid, because its reference `%2%' isn't valid")
% info.path % *i);
/* Update the referrer mapping for *i. This must be done
before the info file is written to maintain the invariant
that if `path' is a valid path, then all its references
have referrer mappings back to `path'. A " " is prefixed
to separate it from the previous entry. It's not suffixed
to deal with interrupted partial writes to this file. */
if (oldInfo.references.find(*i) == oldInfo.references.end())
appendReferrer(*i, info.path, false);
}
string s = (format(
"Hash: sha256:%1%\n"
"References: %2%\n"
"Deriver: %3%\n"
"Registered-At: %4%\n")
% printHash(info.hash) % refs % info.deriver %
(oldInfo.registrationTime ? oldInfo.registrationTime : time(0))).str();
/* Atomically rewrite the info file. */
Path tmpFile = tmpFileForAtomicUpdate(infoFile);
writeFile(tmpFile, s);
if (rename(tmpFile.c_str(), infoFile.c_str()) == -1)
throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % infoFile);
pathInfoCache[info.path] = info;
}
Hash parseHashField(const Path & path, const string & s)
{
string::size_type colon = s.find(':');
if (colon == string::npos)
throw Error(format("corrupt hash `%1%' in valid-path entry for `%2%'")
% s % path);
HashType ht = parseHashType(string(s, 0, colon));
if (ht == htUnknown)
throw Error(format("unknown hash type `%1%' in valid-path entry for `%2%'")
% string(s, 0, colon) % path);
return parseHash(ht, string(s, colon + 1));
}
ValidPathInfo LocalStore::queryPathInfo(const Path & path)
{
ValidPathInfo res;
res.path = path;
assertStorePath(path);
std::map<Path, ValidPathInfo>::iterator lookup = pathInfoCache.find(path);
if (lookup != pathInfoCache.end()) return lookup->second;
//printMsg(lvlError, "queryPathInfo: " + path);
/* Read the info file. */
Path infoFile = infoFileFor(path);
if (!pathExists(infoFile))
throw Error(format("path `%1%' is not valid") % path);
string info = readFile(infoFile);
/* Parse it. */
Strings lines = tokenizeString(info, "\n");
for (Strings::iterator i = lines.begin(); i != lines.end(); ++i) {
unsigned int p = i->find(':');
if (p == string::npos) continue; /* bad line */
string name(*i, 0, p);
string value(*i, p + 2);
if (name == "References") {
Strings refs = tokenizeString(value, " ");
res.references = PathSet(refs.begin(), refs.end());
} else if (name == "Deriver") {
res.deriver = value;
} else if (name == "Hash") {
res.hash = parseHashField(path, value);
} else if (name == "Registered-At") {
int n = 0;
string2Int(value, n);
res.registrationTime = n;
}
}
return pathInfoCache[path] = res;
}
bool LocalStore::isValidPath(const Path & path)
{
return pathExists(infoFileFor(path));
}
PathSet LocalStore::queryValidPaths()
{
PathSet paths;
Strings entries = readDirectory(nixDBPath + "/info");
for (Strings::iterator i = entries.begin(); i != entries.end(); ++i)
paths.insert(nixStore + "/" + *i);
return paths;
}
void LocalStore::queryReferences(const Path & path,
PathSet & references)
{
ValidPathInfo info = queryPathInfo(path);
references.insert(info.references.begin(), info.references.end());
}
bool LocalStore::queryReferrersInternal(const Path & path, PathSet & referrers)
{
bool allValid = true;
if (!isValidPath(path))
throw Error(format("path `%1%' is not valid") % path);
/* No locking is necessary here: updates are only done by
appending or by atomically replacing the file. When appending,
there is a possibility that we see a partial entry, but it will
just be filtered out below (the partially written path will not
be valid, so it will be ignored). */
Path referrersFile = referrersFileFor(path);
if (!pathExists(referrersFile)) return true;
AutoCloseFD fd = open(referrersFile.c_str(), O_RDONLY);
if (fd == -1) throw SysError(format("opening file `%1%'") % referrersFile);
Paths refs = tokenizeString(readFile(fd), " ");
for (Paths::iterator i = refs.begin(); i != refs.end(); ++i)
/* Referrers can be invalid (see registerValidPath() for the
invariant), so we only return one if it is valid. */
if (isStorePath(*i) && isValidPath(*i)) referrers.insert(*i); else allValid = false;
return allValid;
}
void LocalStore::queryReferrers(const Path & path, PathSet & referrers)
{
queryReferrersInternal(path, referrers);
}
Path LocalStore::queryDeriver(const Path & path)
{
return queryPathInfo(path).deriver;
}
PathSet LocalStore::querySubstitutablePaths()
{
if (!substitutablePathsLoaded) {
for (Paths::iterator i = substituters.begin(); i != substituters.end(); ++i) {
debug(format("running `%1%' to find out substitutable paths") % *i);
Strings args;
args.push_back("--query-paths");
Strings ss = tokenizeString(runProgram(*i, false, args), "\n");
for (Strings::iterator j = ss.begin(); j != ss.end(); ++j) {
if (!isStorePath(*j))
throw Error(format("`%1%' returned a bad substitutable path `%2%'")
% *i % *j);
substitutablePaths.insert(*j);
}
}
substitutablePathsLoaded = true;
}
return substitutablePaths;
}
bool LocalStore::hasSubstitutes(const Path & path)
{
if (!substitutablePathsLoaded)
querySubstitutablePaths();
return substitutablePaths.find(path) != substitutablePaths.end();
}
Hash LocalStore::queryPathHash(const Path & path)
{
return queryPathInfo(path).hash;
}
static void dfsVisit(std::map<Path, ValidPathInfo> & infos,
const Path & path, PathSet & visited, Paths & sorted)
{
if (visited.find(path) != visited.end()) return;
visited.insert(path);
ValidPathInfo & info(infos[path]);
for (PathSet::iterator i = info.references.begin();
i != info.references.end(); ++i)
if (infos.find(*i) != infos.end())
dfsVisit(infos, *i, visited, sorted);
sorted.push_back(path);
}
void LocalStore::registerValidPaths(const ValidPathInfos & infos)
{
std::map<Path, ValidPathInfo> infosMap;
/* Sort the paths topologically under the references relation, so
that if path A is referenced by B, then A is registered before
B. */
for (ValidPathInfos::const_iterator i = infos.begin(); i != infos.end(); ++i)
infosMap[i->path] = *i;
PathSet visited;
Paths sorted;
for (ValidPathInfos::const_iterator i = infos.begin(); i != infos.end(); ++i)
dfsVisit(infosMap, i->path, visited, sorted);
for (Paths::iterator i = sorted.begin(); i != sorted.end(); ++i)
registerValidPath(infosMap[*i]);
}
/* Invalidate a path. The caller is responsible for checking that
there are no referrers. */
void LocalStore::invalidatePath(const Path & path)
{
debug(format("invalidating path `%1%'") % path);
ValidPathInfo info;
if (pathExists(infoFileFor(path))) {
info = queryPathInfo(path);
/* Remove the info file. */
Path p = infoFileFor(path);
if (unlink(p.c_str()) == -1)
throw SysError(format("unlinking `%1%'") % p);
}
/* Remove the referrers file for `path'. */
Path p = referrersFileFor(path);
if (pathExists(p) && unlink(p.c_str()) == -1)
throw SysError(format("unlinking `%1%'") % p);
/* Clear `path' from the info cache. */
pathInfoCache.erase(path);
delayedUpdates.erase(path);
/* Cause the referrer files for each path referenced by this one
to be updated. This has to happen after removing the info file
to preserve the invariant (see registerValidPath()).
The referrer files are updated lazily in flushDelayedUpdates()
to prevent quadratic performance in the garbage collector
(i.e., when N referrers to some path X are deleted, we have to
rewrite the referrers file for X N times, causing O(N^2) I/O).
What happens if we die before the referrer file can be updated?
That's not a problem, because stale (invalid) entries in the
referrer file are ignored by queryReferrers(). Thus a referrer
file is allowed to have stale entries; removing them is just an
optimisation. verifyStore() gets rid of them eventually.
*/
foreach (PathSet::iterator, i, info.references)
if (*i != path) delayedUpdates.insert(*i);
}
Path LocalStore::addToStore(const Path & _srcPath, bool fixed,
bool recursive, string hashAlgo, PathFilter & filter)
{
Path srcPath(absPath(_srcPath));
debug(format("adding `%1%' to the store") % srcPath);
std::pair<Path, Hash> pr =
computeStorePathForPath(srcPath, fixed, recursive, hashAlgo, filter);
Path & dstPath(pr.first);
Hash & h(pr.second);
addTempRoot(dstPath);
if (!isValidPath(dstPath)) {
/* The first check above is an optimisation to prevent
unnecessary lock acquisition. */
PathLocks outputLock(singleton<PathSet, Path>(dstPath));
if (!isValidPath(dstPath)) {
if (pathExists(dstPath)) deletePathWrapped(dstPath);
copyPath(srcPath, dstPath, filter);
Hash h2 = hashPath(htSHA256, dstPath, filter);
if (h != h2)
throw Error(format("contents of `%1%' changed while copying it to `%2%' (%3% -> %4%)")
% srcPath % dstPath % printHash(h) % printHash(h2));
canonicalisePathMetaData(dstPath);
registerValidPath(dstPath, h, PathSet(), "");
}
outputLock.setDeletion(true);
}
return dstPath;
}
Path LocalStore::addTextToStore(const string & suffix, const string & s,
const PathSet & references)
{
Path dstPath = computeStorePathForText(suffix, s, references);
addTempRoot(dstPath);
if (!isValidPath(dstPath)) {
PathLocks outputLock(singleton<PathSet, Path>(dstPath));
if (!isValidPath(dstPath)) {
if (pathExists(dstPath)) deletePathWrapped(dstPath);
writeStringToFile(dstPath, s);
canonicalisePathMetaData(dstPath);
registerValidPath(dstPath,
hashPath(htSHA256, dstPath), references, "");
}
outputLock.setDeletion(true);
}
return dstPath;
}
struct HashAndWriteSink : Sink
{
Sink & writeSink;
HashSink hashSink;
bool hashing;
HashAndWriteSink(Sink & writeSink) : writeSink(writeSink), hashSink(htSHA256)
{
hashing = true;
}
virtual void operator ()
(const unsigned char * data, unsigned int len)
{
writeSink(data, len);
if (hashing) hashSink(data, len);
}
};
#define EXPORT_MAGIC 0x4558494e
static void checkSecrecy(const Path & path)
{
struct stat st;
if (stat(path.c_str(), &st))
throw SysError(format("getting status of `%1%'") % path);
if ((st.st_mode & (S_IRWXG | S_IRWXO)) != 0)
throw Error(format("file `%1%' should be secret (inaccessible to everybody else)!") % path);
}
void LocalStore::exportPath(const Path & path, bool sign,
Sink & sink)
{
assertStorePath(path);
addTempRoot(path);
if (!isValidPath(path))
throw Error(format("path `%1%' is not valid") % path);
HashAndWriteSink hashAndWriteSink(sink);
dumpPath(path, hashAndWriteSink);
writeInt(EXPORT_MAGIC, hashAndWriteSink);
writeString(path, hashAndWriteSink);
PathSet references;
queryReferences(path, references);
writeStringSet(references, hashAndWriteSink);
Path deriver = queryDeriver(path);
writeString(deriver, hashAndWriteSink);
if (sign) {
Hash hash = hashAndWriteSink.hashSink.finish();
hashAndWriteSink.hashing = false;
writeInt(1, hashAndWriteSink);
Path tmpDir = createTempDir();
AutoDelete delTmp(tmpDir);
Path hashFile = tmpDir + "/hash";
writeStringToFile(hashFile, printHash(hash));
Path secretKey = nixConfDir + "/signing-key.sec";
checkSecrecy(secretKey);
Strings args;
args.push_back("rsautl");
args.push_back("-sign");
args.push_back("-inkey");
args.push_back(secretKey);
args.push_back("-in");
args.push_back(hashFile);
string signature = runProgram(OPENSSL_PATH, true, args);
writeString(signature, hashAndWriteSink);
} else
writeInt(0, hashAndWriteSink);
}
struct HashAndReadSource : Source
{
Source & readSource;
HashSink hashSink;
bool hashing;
HashAndReadSource(Source & readSource) : readSource(readSource), hashSink(htSHA256)
{
hashing = true;
}
virtual void operator ()
(unsigned char * data, unsigned int len)
{
readSource(data, len);
if (hashing) hashSink(data, len);
}
};
Path LocalStore::importPath(bool requireSignature, Source & source)
{
HashAndReadSource hashAndReadSource(source);
/* We don't yet know what store path this archive contains (the
store path follows the archive data proper), and besides, we
don't know yet whether the signature is valid. */
Path tmpDir = createTempDir(nixStore);
AutoDelete delTmp(tmpDir);
Path unpacked = tmpDir + "/unpacked";
restorePath(unpacked, hashAndReadSource);
unsigned int magic = readInt(hashAndReadSource);
if (magic != EXPORT_MAGIC)
throw Error("Nix archive cannot be imported; wrong format");
Path dstPath = readStorePath(hashAndReadSource);
PathSet references = readStorePaths(hashAndReadSource);
Path deriver = readString(hashAndReadSource);
if (deriver != "") assertStorePath(deriver);
Hash hash = hashAndReadSource.hashSink.finish();
hashAndReadSource.hashing = false;
bool haveSignature = readInt(hashAndReadSource) == 1;
if (requireSignature && !haveSignature)
throw Error("imported archive lacks a signature");
if (haveSignature) {
string signature = readString(hashAndReadSource);
if (requireSignature) {
Path sigFile = tmpDir + "/sig";
writeStringToFile(sigFile, signature);
Strings args;
args.push_back("rsautl");
args.push_back("-verify");
args.push_back("-inkey");
args.push_back(nixConfDir + "/signing-key.pub");
args.push_back("-pubin");
args.push_back("-in");
args.push_back(sigFile);
string hash2 = runProgram(OPENSSL_PATH, true, args);
/* Note: runProgram() throws an exception if the signature
is invalid. */
if (printHash(hash) != hash2)
throw Error(
"signed hash doesn't match actual contents of imported "
"archive; archive could be corrupt, or someone is trying "
"to import a Trojan horse");
}
}
/* Do the actual import. */
/* !!! way too much code duplication with addTextToStore() etc. */
addTempRoot(dstPath);
if (!isValidPath(dstPath)) {
PathLocks outputLock(singleton<PathSet, Path>(dstPath));
if (!isValidPath(dstPath)) {
if (pathExists(dstPath)) deletePathWrapped(dstPath);
if (rename(unpacked.c_str(), dstPath.c_str()) == -1)
throw SysError(format("cannot move `%1%' to `%2%'")
% unpacked % dstPath);
canonicalisePathMetaData(dstPath);
/* !!! if we were clever, we could prevent the hashPath()
here. */
if (deriver != "" && !isValidPath(deriver)) deriver = "";
registerValidPath(dstPath,
hashPath(htSHA256, dstPath), references, deriver);
}
outputLock.setDeletion(true);
}
return dstPath;
}
void LocalStore::deleteFromStore(const Path & path, unsigned long long & bytesFreed,
unsigned long long & blocksFreed)
{
bytesFreed = 0;
assertStorePath(path);
if (isValidPath(path)) {
/* Acquire a lock on the referrers file to prevent new
referrers to this path from appearing while we're deleting
it. */
PathLocks referrersLock(singleton<PathSet, Path>(referrersFileFor(path)));
referrersLock.setDeletion(true);
PathSet referrers; queryReferrers(path, referrers);
referrers.erase(path); /* ignore self-references */
if (!referrers.empty())
throw PathInUse(format("cannot delete path `%1%' because it is in use by `%2%'")
% path % showPaths(referrers));
invalidatePath(path);
}
deletePathWrapped(path, bytesFreed, blocksFreed);
}
void LocalStore::verifyStore(bool checkContents)
{
/* Check whether all valid paths actually exist. */
printMsg(lvlInfo, "checking path existence");
PathSet validPaths2 = queryValidPaths(), validPaths;
for (PathSet::iterator i = validPaths2.begin(); i != validPaths2.end(); ++i) {
checkInterrupt();
if (!isStorePath(*i)) {
printMsg(lvlError, format("path `%1%' is not in the Nix store") % *i);
invalidatePath(*i);
} else if (!pathExists(*i)) {
printMsg(lvlError, format("path `%1%' disappeared") % *i);
invalidatePath(*i);
} else
validPaths.insert(*i);
}
/* Check the store path meta-information. */
printMsg(lvlInfo, "checking path meta-information");
std::map<Path, PathSet> referrersCache;
for (PathSet::iterator i = validPaths.begin(); i != validPaths.end(); ++i) {
bool update = false;
ValidPathInfo info = queryPathInfo(*i);
/* Check the references: each reference should be valid, and
it should have a matching referrer. */
for (PathSet::iterator j = info.references.begin();
j != info.references.end(); ++j)
{
if (referrersCache.find(*j) == referrersCache.end())
queryReferrers(*j, referrersCache[*j]);
if (referrersCache[*j].find(*i) == referrersCache[*j].end()) {
printMsg(lvlError, format("adding missing referrer mapping from `%1%' to `%2%'")
% *j % *i);
appendReferrer(*j, *i, true);
}
if (validPaths.find(*j) == validPaths.end()) {
printMsg(lvlError, format("incomplete closure: `%1%' needs missing `%2%'")
% *i % *j);
/* nothing we can do about it... */
}
}
/* Check the deriver. (Note that the deriver doesn't have to
be a valid path.) */
if (!info.deriver.empty() && !isStorePath(info.deriver)) {
info.deriver = "";
update = true;
}
/* Check the content hash (optionally - slow). */
if (checkContents) {
debug(format("checking contents of `%1%'") % *i);
Hash current = hashPath(info.hash.type, *i);
if (current != info.hash) {
printMsg(lvlError, format("path `%1%' was modified! "
"expected hash `%2%', got `%3%'")
% *i % printHash(info.hash) % printHash(current));
}
}
if (update) registerValidPath(info);
}
referrersCache.clear();
/* Check the referrers. */
printMsg(lvlInfo, "checking referrers");
std::map<Path, PathSet> referencesCache;
Strings entries = readDirectory(nixDBPath + "/referrer");
for (Strings::iterator i = entries.begin(); i != entries.end(); ++i) {
Path from = nixStore + "/" + *i;
if (validPaths.find(from) == validPaths.end()) {
printMsg(lvlError, format("removing referrers file for invalid `%1%'") % from);
Path p = referrersFileFor(from);
if (unlink(p.c_str()) == -1)
throw SysError(format("unlinking `%1%'") % p);
continue;
}
PathSet referrers;
bool allValid = queryReferrersInternal(from, referrers);
bool update = false;
if (!allValid) {
printMsg(lvlError, format("removing some stale referrers for `%1%'") % from);
update = true;
}
/* Each referrer should have a matching reference. */
PathSet referrersNew;
for (PathSet::iterator j = referrers.begin(); j != referrers.end(); ++j) {
if (referencesCache.find(*j) == referencesCache.end())
queryReferences(*j, referencesCache[*j]);
if (referencesCache[*j].find(from) == referencesCache[*j].end()) {
printMsg(lvlError, format("removing unexpected referrer mapping from `%1%' to `%2%'")
% from % *j);
update = true;
} else referrersNew.insert(*j);
}
if (update) rewriteReferrers(from, false, referrersNew);
}
}
}