* Automatically upgrade <= 0.7 Nix stores to the new schema (so that
existing user environments continue to work). * `nix-store --verify': detect incomplete closures.
This commit is contained in:
parent
c547439843
commit
582e01c06f
8 changed files with 154 additions and 23 deletions
|
@ -111,7 +111,7 @@ while (<STDIN>) {
|
||||||
|
|
||||||
print HEADER "#ifdef __cplusplus\n";
|
print HEADER "#ifdef __cplusplus\n";
|
||||||
print HEADER "static inline bool match$funname(ATerm e$formals2) {\n";
|
print HEADER "static inline bool match$funname(ATerm e$formals2) {\n";
|
||||||
print HEADER " if (ATgetType(e) != AT_APPL || ATgetAFun(e) != sym$funname) return false;\n";
|
print HEADER " if (ATgetType(e) != AT_APPL || (AFun) ATgetAFun(e) != sym$funname) return false;\n";
|
||||||
print HEADER "$unpack";
|
print HEADER "$unpack";
|
||||||
print HEADER " return true;\n";
|
print HEADER " return true;\n";
|
||||||
print HEADER "}\n";
|
print HEADER "}\n";
|
||||||
|
|
|
@ -15,4 +15,4 @@ AM_CXXFLAGS = -Wall \
|
||||||
derivations-ast.cc derivations-ast.hh: ../aterm-helper.pl derivations-ast.def
|
derivations-ast.cc derivations-ast.hh: ../aterm-helper.pl derivations-ast.def
|
||||||
$(perl) ../aterm-helper.pl derivations-ast.hh derivations-ast.cc < derivations-ast.def
|
$(perl) ../aterm-helper.pl derivations-ast.hh derivations-ast.cc < derivations-ast.def
|
||||||
|
|
||||||
derivations.cc: derivations-ast.hh
|
derivations.cc store.cc: derivations-ast.hh
|
|
@ -82,7 +82,7 @@ void Transaction::moveTo(Transaction & t)
|
||||||
void Database::requireEnv()
|
void Database::requireEnv()
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
if (!env)throw Error("database environment is not open "
|
if (!env) throw Error("database environment is not open "
|
||||||
"(maybe you don't have sufficient permission?)");
|
"(maybe you don't have sufficient permission?)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,3 +5,6 @@ Derive | ATermList ATermList ATermList string string ATermList ATermList | ATerm
|
||||||
| string string | ATerm | EnvBinding |
|
| string string | ATerm | EnvBinding |
|
||||||
| string ATermList | ATerm | DerivationInput |
|
| string ATermList | ATerm | DerivationInput |
|
||||||
| string string string string | ATerm | DerivationOutput |
|
| string string string string | ATerm | DerivationOutput |
|
||||||
|
|
||||||
|
Closure | ATermList ATermList | ATerm | OldClosure |
|
||||||
|
| string ATermList | ATerm | OldClosureElem |
|
||||||
|
|
|
@ -71,9 +71,13 @@ bool Substitute::operator == (const Substitute & sub)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void upgradeStore();
|
||||||
|
|
||||||
|
|
||||||
void openDB()
|
void openDB()
|
||||||
{
|
{
|
||||||
if (readOnlyMode) return;
|
if (readOnlyMode) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
nixDB.open(nixDBPath);
|
nixDB.open(nixDBPath);
|
||||||
} catch (DbNoPermission & e) {
|
} catch (DbNoPermission & e) {
|
||||||
|
@ -86,6 +90,23 @@ void openDB()
|
||||||
dbReferers = nixDB.openTable("referers");
|
dbReferers = nixDB.openTable("referers");
|
||||||
dbSubstitutes = nixDB.openTable("substitutes");
|
dbSubstitutes = nixDB.openTable("substitutes");
|
||||||
dbDerivers = nixDB.openTable("derivers");
|
dbDerivers = nixDB.openTable("derivers");
|
||||||
|
|
||||||
|
int curSchema = 0;
|
||||||
|
Path schemaFN = nixDBPath + "/schema";
|
||||||
|
if (pathExists(schemaFN)) {
|
||||||
|
string s = readFile(schemaFN);
|
||||||
|
if (!string2Int(s, curSchema))
|
||||||
|
throw Error(format("`%1%' is corrupt") % schemaFN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curSchema > nixSchemaVersion)
|
||||||
|
throw Error(format("current Nix store schema is version %1%, but I only support %2%")
|
||||||
|
% curSchema % nixSchemaVersion);
|
||||||
|
|
||||||
|
if (curSchema < nixSchemaVersion) {
|
||||||
|
upgradeStore();
|
||||||
|
writeFile(schemaFN, (format("%1%") % nixSchemaVersion).str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -457,6 +478,30 @@ void clearSubstitutes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void setHash(const Transaction & txn, const Path & storePath,
|
||||||
|
const Hash & hash)
|
||||||
|
{
|
||||||
|
assert(hash.type == htSHA256);
|
||||||
|
nixDB.setString(txn, dbValidPaths, storePath, "sha256:" + printHash(hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static Hash queryHash(const Transaction & txn, const Path & storePath)
|
||||||
|
{
|
||||||
|
string s;
|
||||||
|
nixDB.queryString(txn, dbValidPaths, storePath, s);
|
||||||
|
unsigned int colon = s.find(':');
|
||||||
|
if (colon == string::npos)
|
||||||
|
throw Error(format("corrupt hash `%1%' in valid-path entry for `%2%'")
|
||||||
|
% s % storePath);
|
||||||
|
HashType ht = parseHashType(string(s, 0, colon));
|
||||||
|
if (ht == htUnknown)
|
||||||
|
throw Error(format("unknown hash type `%1%' in valid-path entry for `%2%'")
|
||||||
|
% string(0, colon) % storePath);
|
||||||
|
return parseHash(ht, string(s, colon + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void registerValidPath(const Transaction & txn,
|
void registerValidPath(const Transaction & txn,
|
||||||
const Path & _path, const Hash & hash, const PathSet & references,
|
const Path & _path, const Hash & hash, const PathSet & references,
|
||||||
const Path & deriver)
|
const Path & deriver)
|
||||||
|
@ -464,10 +509,8 @@ void registerValidPath(const Transaction & txn,
|
||||||
Path path(canonPath(_path));
|
Path path(canonPath(_path));
|
||||||
assertStorePath(path);
|
assertStorePath(path);
|
||||||
|
|
||||||
assert(hash.type == htSHA256);
|
|
||||||
|
|
||||||
debug(format("registering path `%1%'") % path);
|
debug(format("registering path `%1%'") % path);
|
||||||
nixDB.setString(txn, dbValidPaths, path, "sha256:" + printHash(hash));
|
setHash(txn, path, hash);
|
||||||
|
|
||||||
setReferences(txn, path, references);
|
setReferences(txn, path, references);
|
||||||
|
|
||||||
|
@ -623,22 +666,6 @@ void deleteFromStore(const Path & _path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Hash queryHash(const Transaction & txn, const Path & storePath)
|
|
||||||
{
|
|
||||||
string s;
|
|
||||||
nixDB.queryString(txn, dbValidPaths, storePath, s);
|
|
||||||
unsigned int colon = s.find(':');
|
|
||||||
if (colon == string::npos)
|
|
||||||
throw Error(format("corrupt hash `%1%' in valid-path entry for `%2%'")
|
|
||||||
% s % storePath);
|
|
||||||
HashType ht = parseHashType(string(s, 0, colon));
|
|
||||||
if (ht == htUnknown)
|
|
||||||
throw Error(format("unknown hash type `%1%' in valid-path entry for `%2%'")
|
|
||||||
% string(0, colon) % storePath);
|
|
||||||
return parseHash(ht, string(s, colon + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void verifyStore(bool checkContents)
|
void verifyStore(bool checkContents)
|
||||||
{
|
{
|
||||||
Transaction txn(nixDB);
|
Transaction txn(nixDB);
|
||||||
|
@ -648,7 +675,6 @@ void verifyStore(bool checkContents)
|
||||||
nixDB.enumTable(txn, dbValidPaths, paths);
|
nixDB.enumTable(txn, dbValidPaths, paths);
|
||||||
|
|
||||||
for (Paths::iterator i = paths.begin(); i != paths.end(); ++i) {
|
for (Paths::iterator i = paths.begin(); i != paths.end(); ++i) {
|
||||||
Path path = *i;
|
|
||||||
if (!pathExists(*i)) {
|
if (!pathExists(*i)) {
|
||||||
printMsg(lvlError, format("path `%1%' disappeared") % *i);
|
printMsg(lvlError, format("path `%1%' disappeared") % *i);
|
||||||
invalidatePath(*i, txn);
|
invalidatePath(*i, txn);
|
||||||
|
@ -725,6 +751,7 @@ void verifyStore(bool checkContents)
|
||||||
nixDB.delPair(txn, dbReferences, *i);
|
nixDB.delPair(txn, dbReferences, *i);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
bool isValid = validPaths.find(*i) != validPaths.end();
|
||||||
PathSet references;
|
PathSet references;
|
||||||
queryReferences(txn, *i, references);
|
queryReferences(txn, *i, references);
|
||||||
for (PathSet::iterator j = references.begin();
|
for (PathSet::iterator j = references.begin();
|
||||||
|
@ -735,6 +762,10 @@ void verifyStore(bool checkContents)
|
||||||
printMsg(lvlError, format("missing referer mapping from `%1%' to `%2%'")
|
printMsg(lvlError, format("missing referer mapping from `%1%' to `%2%'")
|
||||||
% *j % *i);
|
% *j % *i);
|
||||||
}
|
}
|
||||||
|
if (isValid && validPaths.find(*j) == validPaths.end()) {
|
||||||
|
printMsg(lvlError, format("incomplete closure: `%1%' needs missing `%2%'")
|
||||||
|
% *i % *j);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -768,3 +799,85 @@ void verifyStore(bool checkContents)
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#include "aterm.hh"
|
||||||
|
#include "derivations-ast.hh"
|
||||||
|
|
||||||
|
|
||||||
|
/* Upgrade from schema 1 (Nix <= 0.7) to schema 2 (Nix >= 0.8). */
|
||||||
|
static void upgradeStore()
|
||||||
|
{
|
||||||
|
printMsg(lvlError, "upgrading Nix store to new schema (this may take a while)...");
|
||||||
|
|
||||||
|
Transaction txn(nixDB);
|
||||||
|
|
||||||
|
Paths validPaths2;
|
||||||
|
nixDB.enumTable(txn, dbValidPaths, validPaths2);
|
||||||
|
PathSet validPaths(validPaths2.begin(), validPaths2.end());
|
||||||
|
|
||||||
|
cerr << "hashing paths...";
|
||||||
|
for (PathSet::iterator i = validPaths.begin(); i != validPaths.end(); ++i) {
|
||||||
|
checkInterrupt();
|
||||||
|
string s;
|
||||||
|
nixDB.queryString(txn, dbValidPaths, *i, s);
|
||||||
|
if (s == "") {
|
||||||
|
Hash hash = hashPath(htSHA256, *i);
|
||||||
|
setHash(txn, *i, hash);
|
||||||
|
cerr << ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cerr << "\n";
|
||||||
|
|
||||||
|
cerr << "processing closures...";
|
||||||
|
for (PathSet::iterator i = validPaths.begin(); i != validPaths.end(); ++i) {
|
||||||
|
checkInterrupt();
|
||||||
|
if (i->size() > 6 && string(*i, i->size() - 6) == ".store") {
|
||||||
|
ATerm t = ATreadFromNamedFile(i->c_str());
|
||||||
|
if (!t) throw Error(format("cannot read aterm from `%1%'") % *i);
|
||||||
|
|
||||||
|
ATermList roots, elems;
|
||||||
|
if (!matchOldClosure(t, roots, elems)) continue;
|
||||||
|
|
||||||
|
for (ATermIterator j(elems); j; ++j) {
|
||||||
|
|
||||||
|
ATerm path2;
|
||||||
|
ATermList references2;
|
||||||
|
if (!matchOldClosureElem(*j, path2, references2)) continue;
|
||||||
|
|
||||||
|
Path path = aterm2String(path2);
|
||||||
|
if (validPaths.find(path) == validPaths.end())
|
||||||
|
/* Skip this path; it's invalid. This is a normal
|
||||||
|
condition (Nix <= 0.7 did not enforce closure
|
||||||
|
on closure store expressions). */
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PathSet references;
|
||||||
|
for (ATermIterator k(references2); k; ++k) {
|
||||||
|
Path reference = aterm2String(*k);
|
||||||
|
if (validPaths.find(reference) == validPaths.end())
|
||||||
|
/* Bad reference. Set it anyway and let the
|
||||||
|
user fix it. */
|
||||||
|
printMsg(lvlError, format("closure `%1%' contains reference from `%2%' "
|
||||||
|
"to invalid path `%3%' (run `nix-store --verify')")
|
||||||
|
% *i % path % reference);
|
||||||
|
references.insert(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathSet prevReferences;
|
||||||
|
queryReferences(txn, path, prevReferences);
|
||||||
|
if (prevReferences.size() > 0 && references != prevReferences)
|
||||||
|
printMsg(lvlError, format("warning: conflicting references for `%1%'") % path);
|
||||||
|
|
||||||
|
if (references != prevReferences)
|
||||||
|
setReferences(txn, path, references);
|
||||||
|
}
|
||||||
|
|
||||||
|
cerr << ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cerr << "\n";
|
||||||
|
|
||||||
|
/* !!! maybe this transaction is way too big */
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
|
||||||
|
const int nixSchemaVersion = 2;
|
||||||
|
|
||||||
|
|
||||||
/* A substitute is a program invocation that constructs some store
|
/* A substitute is a program invocation that constructs some store
|
||||||
path (typically by fetching it from somewhere, e.g., from the
|
path (typically by fetching it from somewhere, e.g., from the
|
||||||
network). */
|
network). */
|
||||||
|
|
|
@ -185,6 +185,15 @@ string readFile(const Path & path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void writeFile(const Path & path, const string & s)
|
||||||
|
{
|
||||||
|
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0666);
|
||||||
|
if (fd == -1)
|
||||||
|
throw SysError(format("opening file `%1%'") % path);
|
||||||
|
writeFull(fd, (unsigned char *) s.c_str(), s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void _deletePath(const Path & path)
|
static void _deletePath(const Path & path)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
|
@ -94,6 +94,9 @@ Strings readDirectory(const Path & path);
|
||||||
string readFile(int fd);
|
string readFile(int fd);
|
||||||
string readFile(const Path & path);
|
string readFile(const Path & path);
|
||||||
|
|
||||||
|
/* Write a string to a file. */
|
||||||
|
void writeFile(const Path & path, const string & s);
|
||||||
|
|
||||||
/* Delete a path; i.e., in the case of a directory, it is deleted
|
/* Delete a path; i.e., in the case of a directory, it is deleted
|
||||||
recursively. Don't use this at home, kids. */
|
recursively. Don't use this at home, kids. */
|
||||||
void deletePath(const Path & path);
|
void deletePath(const Path & path);
|
||||||
|
|
Loading…
Reference in a new issue