about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2005-02-09T09·50+0000
committerEelco Dolstra <e.dolstra@tudelft.nl>2005-02-09T09·50+0000
commit582e01c06f9ecee25a31c34562926b41dc2856eb (patch)
treeddb6236f949485e3b5b960d66862c45fbdf32207
parentc5474398433225e40c8868b0952aebe36da2c849 (diff)
* Automatically upgrade <= 0.7 Nix stores to the new schema (so that
  existing user environments continue to work).
* `nix-store --verify': detect incomplete closures.  

-rwxr-xr-xsrc/aterm-helper.pl2
-rw-r--r--src/libstore/Makefile.am2
-rw-r--r--src/libstore/db.cc2
-rw-r--r--src/libstore/derivations-ast.def3
-rw-r--r--src/libstore/store.cc153
-rw-r--r--src/libstore/store.hh3
-rw-r--r--src/libutil/util.cc9
-rw-r--r--src/libutil/util.hh3
8 files changed, 154 insertions, 23 deletions
diff --git a/src/aterm-helper.pl b/src/aterm-helper.pl
index 5666244ae2b6..1feff0ccd098 100755
--- a/src/aterm-helper.pl
+++ b/src/aterm-helper.pl
@@ -111,7 +111,7 @@ while (<STDIN>) {
 
         print HEADER "#ifdef __cplusplus\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 "    return true;\n";
         print HEADER "}\n";
diff --git a/src/libstore/Makefile.am b/src/libstore/Makefile.am
index 37d2f82b4833..100fd0be285d 100644
--- a/src/libstore/Makefile.am
+++ b/src/libstore/Makefile.am
@@ -15,4 +15,4 @@ AM_CXXFLAGS = -Wall \
 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
 
-derivations.cc: derivations-ast.hh
\ No newline at end of file
+derivations.cc store.cc: derivations-ast.hh
\ No newline at end of file
diff --git a/src/libstore/db.cc b/src/libstore/db.cc
index 3b7bddaa26a0..4a815a5f90a8 100644
--- a/src/libstore/db.cc
+++ b/src/libstore/db.cc
@@ -82,7 +82,7 @@ void Transaction::moveTo(Transaction & t)
 void Database::requireEnv()
 {
     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?)");
 }
 
diff --git a/src/libstore/derivations-ast.def b/src/libstore/derivations-ast.def
index 38689845883b..574529ae76c5 100644
--- a/src/libstore/derivations-ast.def
+++ b/src/libstore/derivations-ast.def
@@ -5,3 +5,6 @@ Derive | ATermList ATermList ATermList string string ATermList ATermList | ATerm
 | string string | ATerm | EnvBinding |
 | string ATermList | ATerm | DerivationInput |
 | string string string string | ATerm | DerivationOutput |
+
+Closure | ATermList ATermList | ATerm | OldClosure |
+| string ATermList | ATerm | OldClosureElem |
diff --git a/src/libstore/store.cc b/src/libstore/store.cc
index da60a62b0457..83577e4fb744 100644
--- a/src/libstore/store.cc
+++ b/src/libstore/store.cc
@@ -71,9 +71,13 @@ bool Substitute::operator == (const Substitute & sub)
 }
 
 
+static void upgradeStore();
+
+
 void openDB()
 {
     if (readOnlyMode) return;
+
     try {
         nixDB.open(nixDBPath);
     } catch (DbNoPermission & e) {
@@ -86,6 +90,23 @@ void openDB()
     dbReferers = nixDB.openTable("referers");
     dbSubstitutes = nixDB.openTable("substitutes");
     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,
     const Path & _path, const Hash & hash, const PathSet & references,
     const Path & deriver)
@@ -464,10 +509,8 @@ void registerValidPath(const Transaction & txn,
     Path path(canonPath(_path));
     assertStorePath(path);
 
-    assert(hash.type == htSHA256);
-    
     debug(format("registering path `%1%'") % path);
-    nixDB.setString(txn, dbValidPaths, path, "sha256:" + printHash(hash));
+    setHash(txn, path, hash);
 
     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)
 {
     Transaction txn(nixDB);
@@ -648,7 +675,6 @@ void verifyStore(bool checkContents)
     nixDB.enumTable(txn, dbValidPaths, paths);
 
     for (Paths::iterator i = paths.begin(); i != paths.end(); ++i) {
-        Path path = *i;
         if (!pathExists(*i)) {
             printMsg(lvlError, format("path `%1%' disappeared") % *i);
             invalidatePath(*i, txn);
@@ -725,6 +751,7 @@ void verifyStore(bool checkContents)
             nixDB.delPair(txn, dbReferences, *i);
         }
         else {
+            bool isValid = validPaths.find(*i) != validPaths.end();
             PathSet references;
             queryReferences(txn, *i, references);
             for (PathSet::iterator j = references.begin();
@@ -735,6 +762,10 @@ void verifyStore(bool checkContents)
                     printMsg(lvlError, format("missing referer mapping from `%1%' to `%2%'")
                         % *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();
 }
+
+
+#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();
+}
diff --git a/src/libstore/store.hh b/src/libstore/store.hh
index 8e59679a7d3e..082fc9de498d 100644
--- a/src/libstore/store.hh
+++ b/src/libstore/store.hh
@@ -9,6 +9,9 @@
 using namespace std;
 
 
+const int nixSchemaVersion = 2;
+
+
 /* A substitute is a program invocation that constructs some store
    path (typically by fetching it from somewhere, e.g., from the
    network). */
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 2fb3e9ee6853..27df7a1aaa52 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -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)
 {
     checkInterrupt();
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index c7f117129f17..b67913862408 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -94,6 +94,9 @@ Strings readDirectory(const Path & path);
 string readFile(int fd);
 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
    recursively.  Don't use this at home, kids. */
 void deletePath(const Path & path);