* 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 "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"; | ||||
|  |  | |||
|  | @ -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 | ||||
| derivations.cc store.cc: derivations-ast.hh | ||||
|  | @ -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?)"); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
|  |  | |||
|  | @ -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). */ | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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); | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue