forked from lix-project/lix
dbddac0fe9
to make the Refs table more space-efficient. For instance, this reduces the size of the database on my laptop from 93 MiB to 18 MiB. (It was 72 MiB with the old schema on an ext3 disk with a 1 KiB block size.)
1296 lines
40 KiB
C++
1296 lines
40 KiB
C++
#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>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
|
|
|
|
namespace nix {
|
|
|
|
|
|
class SQLiteError : public Error
|
|
{
|
|
public:
|
|
SQLiteError(sqlite3 * db, const format & f)
|
|
: Error(format("%1%: %2%") % f.str() % sqlite3_errmsg(db))
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
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()
|
|
{
|
|
db = 0;
|
|
substitutablePathsLoaded = false;
|
|
|
|
schemaPath = nixDBPath + "/schema";
|
|
|
|
if (readOnlyMode) return;
|
|
|
|
/* Create missing state directories if they don't already exist. */
|
|
createDirs(nixStore);
|
|
Path profilesDir = nixStateDir + "/profiles";
|
|
createDirs(nixStateDir + "/profiles");
|
|
createDirs(nixStateDir + "/temproots");
|
|
Path gcRootsDir = nixStateDir + "/gcroots";
|
|
if (!pathExists(gcRootsDir)) {
|
|
createDirs(gcRootsDir);
|
|
if (symlink(profilesDir.c_str(), (gcRootsDir + "/profiles").c_str()) == -1)
|
|
throw SysError(format("creating symlink to `%1%'") % profilesDir);
|
|
}
|
|
|
|
checkStoreNotSymlink();
|
|
|
|
try {
|
|
Path globalLockPath = nixDBPath + "/big-lock";
|
|
globalLock = openLockFile(globalLockPath.c_str(), true);
|
|
} catch (SysError & e) {
|
|
if (e.errNo != EACCES) throw;
|
|
readOnlyMode = true;
|
|
return;
|
|
}
|
|
|
|
if (!lockFile(globalLock, ltRead, false)) {
|
|
printMsg(lvlError, "waiting for the big Nix store lock...");
|
|
lockFile(globalLock, ltRead, 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 < 5)
|
|
throw Error(
|
|
"Your Nix store has a database in Berkeley DB format,\n"
|
|
"which is no longer supported. To convert to the new format,\n"
|
|
"please upgrade Nix to version 0.12 first.");
|
|
if (curSchema < 6) upgradeStore6();
|
|
|
|
doFsync = queryBoolSetting("fsync-metadata", false);
|
|
}
|
|
|
|
|
|
LocalStore::~LocalStore()
|
|
{
|
|
try {
|
|
flushDelayedUpdates();
|
|
|
|
if (db && sqlite3_close(db) != SQLITE_OK)
|
|
throw SQLiteError(db, "closing database");
|
|
|
|
foreach (RunningSubstituters::iterator, i, runningSubstituters) {
|
|
i->second.to.close();
|
|
i->second.from.close();
|
|
i->second.pid.wait(true);
|
|
}
|
|
|
|
} 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;
|
|
}
|
|
|
|
|
|
#include "schema.sql.hh"
|
|
|
|
void LocalStore::initSchema()
|
|
{
|
|
if (sqlite3_open_v2((nixDBPath + "/db.sqlite").c_str(), &db,
|
|
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0) != SQLITE_OK)
|
|
throw Error("cannot open SQLite database");
|
|
|
|
if (sqlite3_busy_timeout(db, 60000) != SQLITE_OK)
|
|
throw SQLiteError(db, "sett");
|
|
|
|
if (sqlite3_exec(db, (const char *) schema, 0, 0, 0) != SQLITE_OK)
|
|
throw SQLiteError(db, "initialising database schema");
|
|
}
|
|
|
|
|
|
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 = 1; /* 1 second into the epoch */
|
|
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);
|
|
foreach (Strings::iterator, i, names)
|
|
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 failedFileFor(const Path & path)
|
|
{
|
|
string baseName = baseNameOf(path);
|
|
return (format("%1%/failed/%2%") % nixDBPath % baseName).str();
|
|
}
|
|
|
|
|
|
static Path tmpFileForAtomicUpdate(const Path & path)
|
|
{
|
|
return (format("%1%/.%2%.%3%") % dirOf(path) % getpid() % baseNameOf(path)).str();
|
|
}
|
|
|
|
|
|
void LocalStore::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());
|
|
|
|
if (doFsync) fdatasync(fd);
|
|
}
|
|
|
|
|
|
/* 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());
|
|
|
|
if (doFsync) fdatasync(fd);
|
|
|
|
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);
|
|
}
|
|
|
|
assert(info.hash.type == htSHA256);
|
|
|
|
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, doFsync);
|
|
if (rename(tmpFile.c_str(), infoFile.c_str()) == -1)
|
|
throw SysError(format("cannot rename `%1%' to `%2%'") % tmpFile % infoFile);
|
|
|
|
pathInfoCache[info.path] = info;
|
|
}
|
|
|
|
|
|
void LocalStore::registerFailedPath(const Path & path)
|
|
{
|
|
/* Write an empty file in the .../failed directory to denote the
|
|
failure of the builder for `path'. */
|
|
writeFile(failedFileFor(path), "");
|
|
}
|
|
|
|
|
|
bool LocalStore::hasPathFailed(const Path & path)
|
|
{
|
|
return pathExists(failedFileFor(path));
|
|
}
|
|
|
|
|
|
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, bool ignoreErrors)
|
|
{
|
|
ValidPathInfo res;
|
|
res.path = path;
|
|
|
|
assertStorePath(path);
|
|
|
|
if (!isValidPath(path))
|
|
throw Error(format("path `%1%' is not valid") % path);
|
|
|
|
std::map<Path, ValidPathInfo>::iterator lookup = pathInfoCache.find(path);
|
|
if (lookup != pathInfoCache.end()) return lookup->second;
|
|
|
|
/* 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");
|
|
|
|
foreach (Strings::iterator, i, lines) {
|
|
string::size_type p = i->find(':');
|
|
if (p == string::npos) {
|
|
if (!ignoreErrors)
|
|
throw Error(format("corrupt line in `%1%': %2%") % infoFile % *i);
|
|
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") {
|
|
try {
|
|
res.hash = parseHashField(path, value);
|
|
} catch (Error & e) {
|
|
if (!ignoreErrors) throw;
|
|
printMsg(lvlError, format("cannot parse hash field in `%1%': %2%") % infoFile % e.msg());
|
|
}
|
|
} else if (name == "Registered-At") {
|
|
int n = 0;
|
|
string2Int(value, n);
|
|
res.registrationTime = n;
|
|
}
|
|
}
|
|
|
|
return pathInfoCache[path] = res;
|
|
}
|
|
|
|
|
|
bool LocalStore::isValidPath(const Path & path)
|
|
{
|
|
/* Files in the info directory starting with a `.' are temporary
|
|
files. */
|
|
if (baseNameOf(path).at(0) == '.') return false;
|
|
|
|
/* A path is valid if its info file exists and has a non-zero
|
|
size. (The non-zero size restriction is to be robust to
|
|
certain kinds of filesystem corruption, particularly with
|
|
ext4.) */
|
|
Path infoFile = infoFileFor(path);
|
|
|
|
struct stat st;
|
|
if (lstat(infoFile.c_str(), &st)) {
|
|
if (errno == ENOENT) return false;
|
|
throw SysError(format("getting status of `%1%'") % infoFile);
|
|
}
|
|
|
|
if (st.st_size == 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
PathSet LocalStore::queryValidPaths()
|
|
{
|
|
PathSet paths;
|
|
Strings entries = readDirectory(nixDBPath + "/info");
|
|
foreach (Strings::iterator, i, entries)
|
|
if (i->at(0) != '.') 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), " ");
|
|
|
|
foreach (Paths::iterator, i, refs)
|
|
/* 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;
|
|
}
|
|
|
|
|
|
void LocalStore::startSubstituter(const Path & substituter, RunningSubstituter & run)
|
|
{
|
|
if (run.pid != -1) return;
|
|
|
|
debug(format("starting substituter program `%1%'") % substituter);
|
|
|
|
Pipe toPipe, fromPipe;
|
|
|
|
toPipe.create();
|
|
fromPipe.create();
|
|
|
|
run.pid = fork();
|
|
|
|
switch (run.pid) {
|
|
|
|
case -1:
|
|
throw SysError("unable to fork");
|
|
|
|
case 0: /* child */
|
|
try {
|
|
/* Hack to let "make check" succeed on Darwin. The
|
|
libtool wrapper script sets DYLD_LIBRARY_PATH to our
|
|
libutil (among others), but Perl also depends on a
|
|
library named libutil. As a result, substituters
|
|
written in Perl (i.e. all of them) fail. */
|
|
unsetenv("DYLD_LIBRARY_PATH");
|
|
|
|
fromPipe.readSide.close();
|
|
toPipe.writeSide.close();
|
|
if (dup2(toPipe.readSide, STDIN_FILENO) == -1)
|
|
throw SysError("dupping stdin");
|
|
if (dup2(fromPipe.writeSide, STDOUT_FILENO) == -1)
|
|
throw SysError("dupping stdout");
|
|
closeMostFDs(set<int>());
|
|
execl(substituter.c_str(), substituter.c_str(), "--query", NULL);
|
|
throw SysError(format("executing `%1%'") % substituter);
|
|
} catch (std::exception & e) {
|
|
std::cerr << "error: " << e.what() << std::endl;
|
|
}
|
|
quickExit(1);
|
|
}
|
|
|
|
/* Parent. */
|
|
|
|
run.to = toPipe.writeSide.borrow();
|
|
run.from = fromPipe.readSide.borrow();
|
|
}
|
|
|
|
|
|
template<class T> T getIntLine(int fd)
|
|
{
|
|
string s = readLine(fd);
|
|
T res;
|
|
if (!string2Int(s, res)) throw Error("integer expected from stream");
|
|
return res;
|
|
}
|
|
|
|
|
|
bool LocalStore::hasSubstitutes(const Path & path)
|
|
{
|
|
foreach (Paths::iterator, i, substituters) {
|
|
RunningSubstituter & run(runningSubstituters[*i]);
|
|
startSubstituter(*i, run);
|
|
writeLine(run.to, "have\n" + path);
|
|
if (getIntLine<int>(run.from)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool LocalStore::querySubstitutablePathInfo(const Path & substituter,
|
|
const Path & path, SubstitutablePathInfo & info)
|
|
{
|
|
RunningSubstituter & run(runningSubstituters[substituter]);
|
|
startSubstituter(substituter, run);
|
|
|
|
writeLine(run.to, "info\n" + path);
|
|
|
|
if (!getIntLine<int>(run.from)) return false;
|
|
|
|
info.deriver = readLine(run.from);
|
|
if (info.deriver != "") assertStorePath(info.deriver);
|
|
int nrRefs = getIntLine<int>(run.from);
|
|
while (nrRefs--) {
|
|
Path p = readLine(run.from);
|
|
assertStorePath(p);
|
|
info.references.insert(p);
|
|
}
|
|
info.downloadSize = getIntLine<long long>(run.from);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LocalStore::querySubstitutablePathInfo(const Path & path,
|
|
SubstitutablePathInfo & info)
|
|
{
|
|
foreach (Paths::iterator, i, substituters)
|
|
if (querySubstitutablePathInfo(*i, path, info)) return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
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]);
|
|
|
|
foreach (PathSet::iterator, i, info.references)
|
|
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. */
|
|
foreach (ValidPathInfos::const_iterator, i, infos)
|
|
infosMap[i->path] = *i;
|
|
|
|
PathSet visited;
|
|
Paths sorted;
|
|
foreach (ValidPathInfos::const_iterator, i, infos)
|
|
dfsVisit(infosMap, i->path, visited, sorted);
|
|
|
|
foreach (Paths::iterator, i, sorted)
|
|
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::addToStoreFromDump(const string & dump, const string & name,
|
|
bool recursive, HashType hashAlgo)
|
|
{
|
|
Hash h = hashString(hashAlgo, dump);
|
|
|
|
Path dstPath = makeFixedOutputPath(recursive, hashAlgo, h, name);
|
|
|
|
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);
|
|
|
|
if (recursive) {
|
|
StringSource source(dump);
|
|
restorePath(dstPath, source);
|
|
} else
|
|
writeFile(dstPath, dump);
|
|
|
|
canonicalisePathMetaData(dstPath);
|
|
|
|
/* Register the SHA-256 hash of the NAR serialisation of
|
|
the path in the database. We may just have computed it
|
|
above (if called with recursive == true and hashAlgo ==
|
|
sha256); otherwise, compute it here. */
|
|
registerValidPath(dstPath,
|
|
(recursive && hashAlgo == htSHA256) ? h :
|
|
(recursive ? hashString(htSHA256, dump) : hashPath(htSHA256, dstPath)),
|
|
PathSet(), "");
|
|
}
|
|
|
|
outputLock.setDeletion(true);
|
|
}
|
|
|
|
return dstPath;
|
|
}
|
|
|
|
|
|
Path LocalStore::addToStore(const Path & _srcPath,
|
|
bool recursive, HashType hashAlgo, PathFilter & filter)
|
|
{
|
|
Path srcPath(absPath(_srcPath));
|
|
debug(format("adding `%1%' to the store") % srcPath);
|
|
|
|
/* Read the whole path into memory. This is not a very scalable
|
|
method for very large paths, but `copyPath' is mainly used for
|
|
small files. */
|
|
StringSink sink;
|
|
if (recursive)
|
|
dumpPath(srcPath, sink, filter);
|
|
else
|
|
sink.s = readFile(srcPath);
|
|
|
|
return addToStoreFromDump(sink.s, baseNameOf(srcPath), recursive, hashAlgo);
|
|
}
|
|
|
|
|
|
Path LocalStore::addTextToStore(const string & name, const string & s,
|
|
const PathSet & references)
|
|
{
|
|
Path dstPath = computeStorePathForText(name, s, references);
|
|
|
|
addTempRoot(dstPath);
|
|
|
|
if (!isValidPath(dstPath)) {
|
|
|
|
PathLocks outputLock(singleton<PathSet, Path>(dstPath));
|
|
|
|
if (!isValidPath(dstPath)) {
|
|
|
|
if (pathExists(dstPath)) deletePathWrapped(dstPath);
|
|
|
|
writeFile(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";
|
|
writeFile(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); /* !!! could be GC'ed! */
|
|
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";
|
|
writeFile(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;
|
|
|
|
/* Lock the output path. But don't lock if we're being called
|
|
from a build hook (whose parent process already acquired a
|
|
lock on this path). */
|
|
Strings locksHeld = tokenizeString(getEnv("NIX_HELD_LOCKS"));
|
|
if (find(locksHeld.begin(), locksHeld.end(), dstPath) == locksHeld.end())
|
|
outputLock.lockPaths(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;
|
|
|
|
foreach (PathSet::iterator, i, validPaths2) {
|
|
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 {
|
|
Path infoFile = infoFileFor(*i);
|
|
struct stat st;
|
|
if (lstat(infoFile.c_str(), &st))
|
|
throw SysError(format("getting status of `%1%'") % infoFile);
|
|
if (st.st_size == 0) {
|
|
printMsg(lvlError, format("removing corrupt info file `%1%'") % infoFile);
|
|
if (unlink(infoFile.c_str()) == -1)
|
|
throw SysError(format("unlinking `%1%'") % infoFile);
|
|
}
|
|
else validPaths.insert(*i);
|
|
}
|
|
}
|
|
|
|
|
|
/* Check the store path meta-information. */
|
|
printMsg(lvlInfo, "checking path meta-information");
|
|
|
|
std::map<Path, PathSet> referrersCache;
|
|
|
|
foreach (PathSet::iterator, i, validPaths) {
|
|
bool update = false;
|
|
ValidPathInfo info = queryPathInfo(*i, true);
|
|
|
|
/* Check the references: each reference should be valid, and
|
|
it should have a matching referrer. */
|
|
foreach (PathSet::iterator, j, info.references) {
|
|
if (validPaths.find(*j) == validPaths.end()) {
|
|
printMsg(lvlError, format("incomplete closure: `%1%' needs missing `%2%'")
|
|
% *i % *j);
|
|
/* nothing we can do about it... */
|
|
} else {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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 (info.hash.hashSize == 0) {
|
|
printMsg(lvlError, format("re-hashing `%1%'") % *i);
|
|
info.hash = hashPath(htSHA256, *i);
|
|
update = true;
|
|
} else 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");
|
|
foreach (Strings::iterator, i, entries) {
|
|
Path from = nixStore + "/" + *i;
|
|
|
|
if (validPaths.find(from) == validPaths.end()) {
|
|
/* !!! This removes lock files as well. Need to check
|
|
whether that's okay. */
|
|
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;
|
|
foreach (PathSet::iterator, j, referrers) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
/* Upgrade from schema 5 (Nix 0.12) to schema 6 (Nix >= 0.15). */
|
|
void LocalStore::upgradeStore6()
|
|
{
|
|
if (!lockFile(globalLock, ltWrite, false)) {
|
|
printMsg(lvlError, "waiting for exclusive access to the Nix store...");
|
|
lockFile(globalLock, ltWrite, true);
|
|
}
|
|
|
|
printMsg(lvlError, "upgrading Nix store to new schema (this may take a while)...");
|
|
|
|
initSchema();
|
|
|
|
PathSet validPaths = queryValidPaths();
|
|
|
|
sqlite3_stmt * registerStmt;
|
|
if (sqlite3_prepare_v2(db, "insert into ValidPaths (path, hash, registrationTime) values (?, ?, ?);",
|
|
-1, ®isterStmt, 0) != SQLITE_OK)
|
|
throw SQLiteError(db, "creating statement");
|
|
|
|
sqlite3_stmt * addRefStmt;
|
|
if (sqlite3_prepare_v2(db, "insert into Refs (referrer, reference) values (?, ?);",
|
|
-1, &addRefStmt, 0) != SQLITE_OK)
|
|
throw SQLiteError(db, "creating statement");
|
|
|
|
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
|
|
throw SQLiteError(db, "running `begin' command");
|
|
|
|
std::map<Path, sqlite3_int64> pathToId;
|
|
|
|
foreach (PathSet::iterator, i, validPaths) {
|
|
ValidPathInfo info = queryPathInfo(*i, true);
|
|
|
|
if (sqlite3_reset(registerStmt) != SQLITE_OK)
|
|
throw SQLiteError(db, "resetting statement");
|
|
if (sqlite3_bind_text(registerStmt, 1, i->c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
|
|
throw SQLiteError(db, "binding argument 1");
|
|
string h = "sha256:" + printHash(info.hash);
|
|
if (sqlite3_bind_text(registerStmt, 2, h.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
|
|
throw SQLiteError(db, "binding argument 2");
|
|
if (sqlite3_bind_int(registerStmt, 3, info.registrationTime) != SQLITE_OK)
|
|
throw SQLiteError(db, "binding argument 3");
|
|
if (sqlite3_step(registerStmt) != SQLITE_DONE)
|
|
throw SQLiteError(db, "registering valid path in database");
|
|
|
|
pathToId[*i] = sqlite3_last_insert_rowid(db);
|
|
|
|
std::cerr << ".";
|
|
}
|
|
|
|
std::cerr << "|";
|
|
|
|
foreach (PathSet::iterator, i, validPaths) {
|
|
ValidPathInfo info = queryPathInfo(*i, true);
|
|
|
|
foreach (PathSet::iterator, j, info.references) {
|
|
if (sqlite3_reset(addRefStmt) != SQLITE_OK)
|
|
throw SQLiteError(db, "resetting statement");
|
|
if (sqlite3_bind_int(addRefStmt, 1, pathToId[*i]) != SQLITE_OK)
|
|
throw SQLiteError(db, "binding argument 1");
|
|
if (pathToId.find(*j) == pathToId.end())
|
|
throw Error(format("path `%1%' referenced by `%2%' is invalid") % *j % *i);
|
|
if (sqlite3_bind_int(addRefStmt, 2, pathToId[*j]) != SQLITE_OK)
|
|
throw SQLiteError(db, "binding argument 2");
|
|
if (sqlite3_step(addRefStmt) != SQLITE_DONE)
|
|
throw SQLiteError(db, "adding reference to database");
|
|
}
|
|
|
|
std::cerr << ".";
|
|
}
|
|
|
|
std::cerr << "\n";
|
|
|
|
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
|
|
throw SQLiteError(db, "running `commit' command");
|
|
|
|
if (sqlite3_finalize(registerStmt) != SQLITE_OK)
|
|
throw SQLiteError(db, "finalizing statement");
|
|
|
|
if (sqlite3_finalize(addRefStmt) != SQLITE_OK)
|
|
throw SQLiteError(db, "finalizing statement");
|
|
|
|
throw Error("foo");
|
|
|
|
writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str());
|
|
|
|
lockFile(globalLock, ltRead, true);
|
|
}
|
|
|
|
|
|
}
|