mirror of
https://github.com/NixOS/nix
synced 2025-07-06 21:41:48 +02:00
* libnix -> libstore.
This commit is contained in:
parent
8798fae304
commit
9f0f020929
26 changed files with 12 additions and 12 deletions
17
src/libstore/Makefile.am
Normal file
17
src/libstore/Makefile.am
Normal file
|
@ -0,0 +1,17 @@
|
|||
noinst_LIBRARIES = libstore.a
|
||||
|
||||
libstore_a_SOURCES = \
|
||||
store.cc expr.cc normalise.cc exec.cc \
|
||||
globals.cc db.cc references.cc pathlocks.cc
|
||||
|
||||
AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall \
|
||||
-I.. -I../../externals/inst/include -I../libutil
|
||||
|
||||
EXTRA_DIST = *.hh *.h test-builder-*.sh
|
||||
|
||||
check_PROGRAMS = test-aterm
|
||||
|
||||
test_aterm_SOURCES = test-aterm.cc
|
||||
test_aterm_LDADD = libstore.a $(LDADD) ../boost/format/libformat.a \
|
||||
-L../../externals/inst/lib -ldb_cxx -lATerm
|
||||
|
425
src/libstore/db.cc
Normal file
425
src/libstore/db.cc
Normal file
|
@ -0,0 +1,425 @@
|
|||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "db.hh"
|
||||
#include "util.hh"
|
||||
#include "pathlocks.hh"
|
||||
|
||||
|
||||
/* Wrapper class to ensure proper destruction. */
|
||||
class DestroyDbc
|
||||
{
|
||||
Dbc * dbc;
|
||||
public:
|
||||
DestroyDbc(Dbc * _dbc) : dbc(_dbc) { }
|
||||
~DestroyDbc() { dbc->close(); /* close() frees dbc */ }
|
||||
};
|
||||
|
||||
|
||||
static void rethrow(DbException & e)
|
||||
{
|
||||
throw Error(e.what());
|
||||
}
|
||||
|
||||
|
||||
Transaction::Transaction()
|
||||
: txn(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Transaction::Transaction(Database & db)
|
||||
{
|
||||
db.requireEnv();
|
||||
try {
|
||||
db.env->txn_begin(0, &txn, 0);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
Transaction::~Transaction()
|
||||
{
|
||||
if (txn) abort();
|
||||
}
|
||||
|
||||
|
||||
void Transaction::commit()
|
||||
{
|
||||
if (!txn) throw Error("commit called on null transaction");
|
||||
debug(format("committing transaction %1%") % (void *) txn);
|
||||
DbTxn * txn2 = txn;
|
||||
txn = 0;
|
||||
try {
|
||||
txn2->commit(0);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Transaction::abort()
|
||||
{
|
||||
if (!txn) throw Error("abort called on null transaction");
|
||||
debug(format("aborting transaction %1%") % (void *) txn);
|
||||
DbTxn * txn2 = txn;
|
||||
txn = 0;
|
||||
try {
|
||||
txn2->abort();
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Transaction::moveTo(Transaction & t)
|
||||
{
|
||||
if (t.txn) throw Error("target txn already exists");
|
||||
t.txn = txn;
|
||||
txn = 0;
|
||||
}
|
||||
|
||||
|
||||
void Database::requireEnv()
|
||||
{
|
||||
if (!env) throw Error("database environment not open");
|
||||
}
|
||||
|
||||
|
||||
Db * Database::getDb(TableId table)
|
||||
{
|
||||
map<TableId, Db *>::iterator i = tables.find(table);
|
||||
if (i == tables.end())
|
||||
throw Error("unknown table id");
|
||||
return i->second;
|
||||
}
|
||||
|
||||
|
||||
Database::Database()
|
||||
: env(0)
|
||||
, nextId(1)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Database::~Database()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
int getAccessorCount(int fd)
|
||||
{
|
||||
if (lseek(fd, 0, SEEK_SET) == -1)
|
||||
throw SysError("seeking accessor count");
|
||||
char buf[128];
|
||||
int len;
|
||||
if ((len = read(fd, buf, sizeof(buf) - 1)) == -1)
|
||||
throw SysError("reading accessor count");
|
||||
buf[len] = 0;
|
||||
int count;
|
||||
if (sscanf(buf, "%d", &count) != 1) {
|
||||
debug(format("accessor count is invalid: `%1%'") % buf);
|
||||
return -1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
void setAccessorCount(int fd, int n)
|
||||
{
|
||||
if (lseek(fd, 0, SEEK_SET) == -1)
|
||||
throw SysError("seeking accessor count");
|
||||
string s = (format("%1%") % n).str();
|
||||
const char * s2 = s.c_str();
|
||||
if (write(fd, s2, strlen(s2)) != (ssize_t) strlen(s2) ||
|
||||
ftruncate(fd, strlen(s2)) != 0)
|
||||
throw SysError("writing accessor count");
|
||||
}
|
||||
|
||||
|
||||
void openEnv(DbEnv * env, const string & path, u_int32_t flags)
|
||||
{
|
||||
env->open(path.c_str(),
|
||||
DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN |
|
||||
DB_CREATE | flags,
|
||||
0666);
|
||||
}
|
||||
|
||||
|
||||
void Database::open(const string & path)
|
||||
{
|
||||
if (env) throw Error(format("environment already open"));
|
||||
|
||||
try {
|
||||
|
||||
debug(format("opening database environment"));
|
||||
|
||||
|
||||
/* Create the database environment object. */
|
||||
env = new DbEnv(0);
|
||||
|
||||
env->set_lg_bsize(32 * 1024); /* default */
|
||||
env->set_lg_max(256 * 1024); /* must be > 4 * lg_bsize */
|
||||
env->set_lk_detect(DB_LOCK_DEFAULT);
|
||||
env->set_flags(DB_TXN_WRITE_NOSYNC, 1);
|
||||
|
||||
|
||||
/* The following code provides automatic recovery of the
|
||||
database environment. Recovery is necessary when a process
|
||||
dies while it has the database open. To detect this,
|
||||
processes atomically increment a counter when the open the
|
||||
database, and decrement it when they close it. If we see
|
||||
that counter is > 0 but no processes are accessing the
|
||||
database---determined by attempting to obtain a write lock
|
||||
on a lock file on which all accessors have a read lock---we
|
||||
must run recovery. Note that this also ensures that we
|
||||
only run recovery when there are no other accessors (which
|
||||
could cause database corruption). */
|
||||
|
||||
/* !!! close fdAccessors / fdLock on exception */
|
||||
|
||||
/* Open the accessor count file. */
|
||||
string accessorsPath = path + "/accessor_count";
|
||||
fdAccessors = ::open(accessorsPath.c_str(), O_RDWR | O_CREAT, 0666);
|
||||
if (fdAccessors == -1)
|
||||
throw SysError(format("opening file `%1%'") % accessorsPath);
|
||||
|
||||
/* Open the lock file. */
|
||||
string lockPath = path + "/access_lock";
|
||||
fdLock = ::open(lockPath.c_str(), O_RDWR | O_CREAT, 0666);
|
||||
if (fdLock == -1)
|
||||
throw SysError(format("opening lock file `%1%'") % lockPath);
|
||||
|
||||
/* Try to acquire a write lock. */
|
||||
debug(format("attempting write lock on `%1%'") % lockPath);
|
||||
if (lockFile(fdLock, ltWrite, false)) { /* don't wait */
|
||||
|
||||
debug(format("write lock granted"));
|
||||
|
||||
/* We have a write lock, which means that there are no
|
||||
other readers or writers. */
|
||||
|
||||
int n = getAccessorCount(fdAccessors);
|
||||
setAccessorCount(fdAccessors, 1);
|
||||
|
||||
if (n != 0) {
|
||||
printMsg(lvlTalkative,
|
||||
format("accessor count is %1%, running recovery") % n);
|
||||
|
||||
/* Open the environment after running recovery. */
|
||||
openEnv(env, path, DB_RECOVER);
|
||||
}
|
||||
|
||||
else
|
||||
/* Open the environment normally. */
|
||||
openEnv(env, path, 0);
|
||||
|
||||
/* Downgrade to a read lock. */
|
||||
debug(format("downgrading to read lock on `%1%'") % lockPath);
|
||||
lockFile(fdLock, ltRead, true);
|
||||
|
||||
} else {
|
||||
/* There are other accessors. */
|
||||
debug(format("write lock refused"));
|
||||
|
||||
/* Acquire a read lock. */
|
||||
debug(format("acquiring read lock on `%1%'") % lockPath);
|
||||
lockFile(fdLock, ltRead, true); /* wait indefinitely */
|
||||
|
||||
/* Increment the accessor count. */
|
||||
lockFile(fdAccessors, ltWrite, true);
|
||||
int n = getAccessorCount(fdAccessors) + 1;
|
||||
setAccessorCount(fdAccessors, n);
|
||||
debug(format("incremented accessor count to %1%") % n);
|
||||
lockFile(fdAccessors, ltNone, true);
|
||||
|
||||
/* Open the environment normally. */
|
||||
openEnv(env, path, 0);
|
||||
}
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Database::close()
|
||||
{
|
||||
if (!env) return;
|
||||
|
||||
/* Close the database environment. */
|
||||
debug(format("closing database environment"));
|
||||
|
||||
try {
|
||||
|
||||
for (map<TableId, Db *>::iterator i = tables.begin();
|
||||
i != tables.end(); i++)
|
||||
{
|
||||
Db * db = i->second;
|
||||
db->close(DB_NOSYNC);
|
||||
delete db;
|
||||
}
|
||||
|
||||
// env->txn_checkpoint(0, 0, 0);
|
||||
env->close(0);
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
|
||||
delete env;
|
||||
|
||||
/* Decrement the accessor count. */
|
||||
lockFile(fdAccessors, ltWrite, true);
|
||||
int n = getAccessorCount(fdAccessors) - 1;
|
||||
setAccessorCount(fdAccessors, n);
|
||||
debug(format("decremented accessor count to %1%") % n);
|
||||
lockFile(fdAccessors, ltNone, true);
|
||||
|
||||
::close(fdAccessors);
|
||||
::close(fdLock);
|
||||
}
|
||||
|
||||
|
||||
TableId Database::openTable(const string & tableName)
|
||||
{
|
||||
requireEnv();
|
||||
TableId table = nextId++;
|
||||
|
||||
try {
|
||||
|
||||
Db * db = new Db(env, 0);
|
||||
|
||||
try {
|
||||
db->open(0, tableName.c_str(), 0,
|
||||
DB_HASH, DB_CREATE | DB_AUTO_COMMIT, 0666);
|
||||
} catch (...) {
|
||||
delete db;
|
||||
throw;
|
||||
}
|
||||
|
||||
tables[table] = db;
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
|
||||
bool Database::queryString(const Transaction & txn, TableId table,
|
||||
const string & key, string & data)
|
||||
{
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
|
||||
Dbt kt((void *) key.c_str(), key.length());
|
||||
Dbt dt;
|
||||
|
||||
int err = db->get(txn.txn, &kt, &dt, 0);
|
||||
if (err) return false;
|
||||
|
||||
if (!dt.get_data())
|
||||
data = "";
|
||||
else
|
||||
data = string((char *) dt.get_data(), dt.get_size());
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool Database::queryStrings(const Transaction & txn, TableId table,
|
||||
const string & key, Strings & data)
|
||||
{
|
||||
string d;
|
||||
|
||||
if (!queryString(txn, table, key, d))
|
||||
return false;
|
||||
|
||||
string::iterator it = d.begin();
|
||||
|
||||
while (it != d.end()) {
|
||||
|
||||
if (it + 4 > d.end())
|
||||
throw Error(format("short db entry: `%1%'") % d);
|
||||
|
||||
unsigned int len;
|
||||
len = (unsigned char) *it++;
|
||||
len |= ((unsigned char) *it++) << 8;
|
||||
len |= ((unsigned char) *it++) << 16;
|
||||
len |= ((unsigned char) *it++) << 24;
|
||||
|
||||
if (it + len > d.end())
|
||||
throw Error(format("short db entry: `%1%'") % d);
|
||||
|
||||
string s;
|
||||
while (len--) s += *it++;
|
||||
|
||||
data.push_back(s);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void Database::setString(const Transaction & txn, TableId table,
|
||||
const string & key, const string & data)
|
||||
{
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
Dbt kt((void *) key.c_str(), key.length());
|
||||
Dbt dt((void *) data.c_str(), data.length());
|
||||
db->put(txn.txn, &kt, &dt, 0);
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Database::setStrings(const Transaction & txn, TableId table,
|
||||
const string & key, const Strings & data)
|
||||
{
|
||||
string d;
|
||||
|
||||
for (Strings::const_iterator it = data.begin();
|
||||
it != data.end(); it++)
|
||||
{
|
||||
string s = *it;
|
||||
unsigned int len = s.size();
|
||||
|
||||
d += len & 0xff;
|
||||
d += (len >> 8) & 0xff;
|
||||
d += (len >> 16) & 0xff;
|
||||
d += (len >> 24) & 0xff;
|
||||
|
||||
d += s;
|
||||
}
|
||||
|
||||
setString(txn, table, key, d);
|
||||
}
|
||||
|
||||
|
||||
void Database::delPair(const Transaction & txn, TableId table,
|
||||
const string & key)
|
||||
{
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
Dbt kt((void *) key.c_str(), key.length());
|
||||
db->del(txn.txn, &kt, 0);
|
||||
/* Non-existence of a pair with the given key is not an
|
||||
error. */
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
||||
|
||||
|
||||
void Database::enumTable(const Transaction & txn, TableId table,
|
||||
Strings & keys)
|
||||
{
|
||||
try {
|
||||
Db * db = getDb(table);
|
||||
|
||||
Dbc * dbc;
|
||||
db->cursor(txn.txn, &dbc, 0);
|
||||
DestroyDbc destroyDbc(dbc);
|
||||
|
||||
Dbt kt, dt;
|
||||
while (dbc->get(&kt, &dt, DB_NEXT) != DB_NOTFOUND)
|
||||
keys.push_back(
|
||||
string((char *) kt.get_data(), kt.get_size()));
|
||||
|
||||
} catch (DbException e) { rethrow(e); }
|
||||
}
|
89
src/libstore/db.hh
Normal file
89
src/libstore/db.hh
Normal file
|
@ -0,0 +1,89 @@
|
|||
#ifndef __DB_H
|
||||
#define __DB_H
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
#include <db_cxx.h>
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
class Database;
|
||||
|
||||
|
||||
class Transaction
|
||||
{
|
||||
friend class Database;
|
||||
|
||||
private:
|
||||
DbTxn * txn;
|
||||
|
||||
public:
|
||||
Transaction();
|
||||
Transaction(Database & _db);
|
||||
~Transaction();
|
||||
|
||||
void abort();
|
||||
void commit();
|
||||
|
||||
void moveTo(Transaction & t);
|
||||
};
|
||||
|
||||
|
||||
#define noTxn Transaction()
|
||||
|
||||
|
||||
typedef unsigned int TableId; /* table handles */
|
||||
|
||||
|
||||
class Database
|
||||
{
|
||||
friend class Transaction;
|
||||
|
||||
private:
|
||||
DbEnv * env;
|
||||
|
||||
int fdLock;
|
||||
int fdAccessors;
|
||||
|
||||
TableId nextId;
|
||||
map<TableId, Db *> tables;
|
||||
|
||||
void requireEnv();
|
||||
|
||||
Db * getDb(TableId table);
|
||||
|
||||
public:
|
||||
Database();
|
||||
~Database();
|
||||
|
||||
void open(const string & path);
|
||||
void close();
|
||||
|
||||
TableId openTable(const string & table);
|
||||
|
||||
bool queryString(const Transaction & txn, TableId table,
|
||||
const string & key, string & data);
|
||||
|
||||
bool queryStrings(const Transaction & txn, TableId table,
|
||||
const string & key, Strings & data);
|
||||
|
||||
void setString(const Transaction & txn, TableId table,
|
||||
const string & key, const string & data);
|
||||
|
||||
void setStrings(const Transaction & txn, TableId table,
|
||||
const string & key, const Strings & data);
|
||||
|
||||
void delPair(const Transaction & txn, TableId table,
|
||||
const string & key);
|
||||
|
||||
void enumTable(const Transaction & txn, TableId table,
|
||||
Strings & keys);
|
||||
};
|
||||
|
||||
|
||||
#endif /* !__DB_H */
|
127
src/libstore/exec.cc
Normal file
127
src/libstore/exec.cc
Normal file
|
@ -0,0 +1,127 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "exec.hh"
|
||||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
|
||||
static string pathNullDevice = "/dev/null";
|
||||
|
||||
|
||||
/* Run a program. */
|
||||
void runProgram(const string & program,
|
||||
const Strings & args, const Environment & env,
|
||||
const string & logFileName)
|
||||
{
|
||||
/* Create a log file. */
|
||||
string logCommand =
|
||||
verbosity >= lvlDebug
|
||||
? "tee " + logFileName + " >&2"
|
||||
: "cat > " + logFileName;
|
||||
/* !!! auto-pclose on exit */
|
||||
FILE * logFile = popen(logCommand.c_str(), "w"); /* !!! escaping */
|
||||
if (!logFile)
|
||||
throw SysError(format("creating log file `%1%'") % logFileName);
|
||||
|
||||
/* Create a temporary directory where the build will take
|
||||
place. */
|
||||
string tmpDir = createTempDir();
|
||||
|
||||
AutoDelete delTmpDir(tmpDir);
|
||||
|
||||
/* Fork a child to build the package. */
|
||||
pid_t pid;
|
||||
switch (pid = fork()) {
|
||||
|
||||
case -1:
|
||||
throw SysError("unable to fork");
|
||||
|
||||
case 0:
|
||||
|
||||
try { /* child */
|
||||
|
||||
if (chdir(tmpDir.c_str()) == -1)
|
||||
throw SysError(format("changing into to `%1%'") % tmpDir);
|
||||
|
||||
/* Fill in the arguments. */
|
||||
const char * argArr[args.size() + 2];
|
||||
const char * * p = argArr;
|
||||
string progName = baseNameOf(program);
|
||||
*p++ = progName.c_str();
|
||||
for (Strings::const_iterator i = args.begin();
|
||||
i != args.end(); i++)
|
||||
*p++ = i->c_str();
|
||||
*p = 0;
|
||||
|
||||
/* Fill in the environment. */
|
||||
Strings envStrs;
|
||||
const char * envArr[env.size() + 1];
|
||||
p = envArr;
|
||||
for (Environment::const_iterator i = env.begin();
|
||||
i != env.end(); i++)
|
||||
*p++ = envStrs.insert(envStrs.end(),
|
||||
i->first + "=" + i->second)->c_str();
|
||||
*p = 0;
|
||||
|
||||
/* Dup the log handle into stderr. */
|
||||
if (dup2(fileno(logFile), STDERR_FILENO) == -1)
|
||||
throw SysError("cannot pipe standard error into log file");
|
||||
|
||||
/* Dup stderr to stdin. */
|
||||
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
|
||||
throw SysError("cannot dup stderr into stdout");
|
||||
|
||||
/* Reroute stdin to /dev/null. */
|
||||
int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
|
||||
if (fdDevNull == -1)
|
||||
throw SysError(format("cannot open `%1%'") % pathNullDevice);
|
||||
if (dup2(fdDevNull, STDIN_FILENO) == -1)
|
||||
throw SysError("cannot dup null device into stdin");
|
||||
|
||||
/* Execute the program. This should not return. */
|
||||
execve(program.c_str(), (char * *) argArr, (char * *) envArr);
|
||||
|
||||
throw SysError(format("unable to execute %1%") % program);
|
||||
|
||||
} catch (exception & e) {
|
||||
cerr << format("build error: %1%\n") % e.what();
|
||||
}
|
||||
_exit(1);
|
||||
|
||||
}
|
||||
|
||||
/* parent */
|
||||
|
||||
/* Close the logging pipe. Note that this should not cause
|
||||
the logger to exit until builder exits (because the latter
|
||||
has an open file handle to the former). */
|
||||
pclose(logFile);
|
||||
|
||||
/* Wait for the child to finish. */
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) != pid)
|
||||
throw Error("unable to wait for child");
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
|
||||
if (keepFailed) {
|
||||
printMsg(lvlTalkative,
|
||||
format("program `%1%' failed; keeping build directory `%2%'")
|
||||
% program % tmpDir);
|
||||
delTmpDir.cancel();
|
||||
}
|
||||
if (WIFEXITED(status))
|
||||
throw Error(format("program `%1%' failed with exit code %2%")
|
||||
% program % WEXITSTATUS(status));
|
||||
else if (WIFSIGNALED(status))
|
||||
throw Error(format("program `%1%' failed due to signal %2%")
|
||||
% program % WTERMSIG(status));
|
||||
else
|
||||
throw Error(format("program `%1%' died abnormally") % program);
|
||||
}
|
||||
}
|
22
src/libstore/exec.hh
Normal file
22
src/libstore/exec.hh
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef __EXEC_H
|
||||
#define __EXEC_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/* A Unix environment is a mapping from strings to strings. */
|
||||
typedef map<string, string> Environment;
|
||||
|
||||
|
||||
/* Run a program. */
|
||||
void runProgram(const string & program,
|
||||
const Strings & args, const Environment & env,
|
||||
const string & logFileName);
|
||||
|
||||
|
||||
#endif /* !__EXEC_H */
|
210
src/libstore/expr.cc
Normal file
210
src/libstore/expr.cc
Normal file
|
@ -0,0 +1,210 @@
|
|||
#include "expr.hh"
|
||||
#include "globals.hh"
|
||||
#include "store.hh"
|
||||
|
||||
|
||||
Error badTerm(const format & f, ATerm t)
|
||||
{
|
||||
char * s = ATwriteToString(t);
|
||||
if (!s) throw Error("cannot print term");
|
||||
if (strlen(s) > 1000) {
|
||||
int len;
|
||||
s = ATwriteToSharedString(t, &len);
|
||||
if (!s) throw Error("cannot print term");
|
||||
}
|
||||
return Error(format("%1%, in `%2%'") % f.str() % (string) s);
|
||||
}
|
||||
|
||||
|
||||
Hash hashTerm(ATerm t)
|
||||
{
|
||||
return hashString(atPrint(t));
|
||||
}
|
||||
|
||||
|
||||
Path writeTerm(ATerm t, const string & suffix)
|
||||
{
|
||||
/* The id of a term is its hash. */
|
||||
Hash h = hashTerm(t);
|
||||
|
||||
Path path = canonPath(nixStore + "/" +
|
||||
(string) h + suffix + ".nix");
|
||||
|
||||
if (!isValidPath(path)) {
|
||||
char * s = ATwriteToString(t);
|
||||
if (!s) throw Error(format("cannot write aterm to `%1%'") % path);
|
||||
addTextToStore(path, string(s));
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
static void parsePaths(ATermList paths, PathSet & out)
|
||||
{
|
||||
ATMatcher m;
|
||||
for (ATermIterator i(paths); i; ++i) {
|
||||
string s;
|
||||
if (!(atMatch(m, *i) >> s))
|
||||
throw badTerm("not a path", *i);
|
||||
out.insert(s);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void checkClosure(const Closure & closure)
|
||||
{
|
||||
if (closure.elems.size() == 0)
|
||||
throw Error("empty closure");
|
||||
|
||||
PathSet decl;
|
||||
for (ClosureElems::const_iterator i = closure.elems.begin();
|
||||
i != closure.elems.end(); i++)
|
||||
decl.insert(i->first);
|
||||
|
||||
for (PathSet::const_iterator i = closure.roots.begin();
|
||||
i != closure.roots.end(); i++)
|
||||
if (decl.find(*i) == decl.end())
|
||||
throw Error(format("undefined root path `%1%'") % *i);
|
||||
|
||||
for (ClosureElems::const_iterator i = closure.elems.begin();
|
||||
i != closure.elems.end(); i++)
|
||||
for (PathSet::const_iterator j = i->second.refs.begin();
|
||||
j != i->second.refs.end(); j++)
|
||||
if (decl.find(*j) == decl.end())
|
||||
throw Error(
|
||||
format("undefined path `%1%' referenced by `%2%'")
|
||||
% *j % i->first);
|
||||
}
|
||||
|
||||
|
||||
/* Parse a closure. */
|
||||
static bool parseClosure(ATerm t, Closure & closure)
|
||||
{
|
||||
ATermList roots, elems;
|
||||
ATMatcher m;
|
||||
|
||||
if (!(atMatch(m, t) >> "Closure" >> roots >> elems))
|
||||
return false;
|
||||
|
||||
parsePaths(roots, closure.roots);
|
||||
|
||||
for (ATermIterator i(elems); i; ++i) {
|
||||
string path;
|
||||
ATermList refs;
|
||||
if (!(atMatch(m, *i) >> "" >> path >> refs))
|
||||
throw badTerm("not a closure element", *i);
|
||||
ClosureElem elem;
|
||||
parsePaths(refs, elem.refs);
|
||||
closure.elems[path] = elem;
|
||||
}
|
||||
|
||||
checkClosure(closure);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool parseDerivation(ATerm t, Derivation & derivation)
|
||||
{
|
||||
ATMatcher m;
|
||||
ATermList outs, ins, args, bnds;
|
||||
string builder, platform;
|
||||
|
||||
if (!(atMatch(m, t) >> "Derive" >> outs >> ins >> platform
|
||||
>> builder >> args >> bnds))
|
||||
return false;
|
||||
|
||||
parsePaths(outs, derivation.outputs);
|
||||
parsePaths(ins, derivation.inputs);
|
||||
|
||||
derivation.builder = builder;
|
||||
derivation.platform = platform;
|
||||
|
||||
for (ATermIterator i(args); i; ++i) {
|
||||
string s;
|
||||
if (!(atMatch(m, *i) >> s))
|
||||
throw badTerm("string expected", *i);
|
||||
derivation.args.push_back(s);
|
||||
}
|
||||
|
||||
for (ATermIterator i(bnds); i; ++i) {
|
||||
string s1, s2;
|
||||
if (!(atMatch(m, *i) >> "" >> s1 >> s2))
|
||||
throw badTerm("tuple of strings expected", *i);
|
||||
derivation.env[s1] = s2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
NixExpr parseNixExpr(ATerm t)
|
||||
{
|
||||
NixExpr ne;
|
||||
if (parseClosure(t, ne.closure))
|
||||
ne.type = NixExpr::neClosure;
|
||||
else if (parseDerivation(t, ne.derivation))
|
||||
ne.type = NixExpr::neDerivation;
|
||||
else throw badTerm("not a Nix expression", t);
|
||||
return ne;
|
||||
}
|
||||
|
||||
|
||||
static ATermList unparsePaths(const PathSet & paths)
|
||||
{
|
||||
ATermList l = ATempty;
|
||||
for (PathSet::const_iterator i = paths.begin();
|
||||
i != paths.end(); i++)
|
||||
l = ATinsert(l, ATmake("<str>", i->c_str()));
|
||||
return ATreverse(l);
|
||||
}
|
||||
|
||||
|
||||
static ATerm unparseClosure(const Closure & closure)
|
||||
{
|
||||
ATermList roots = unparsePaths(closure.roots);
|
||||
|
||||
ATermList elems = ATempty;
|
||||
for (ClosureElems::const_iterator i = closure.elems.begin();
|
||||
i != closure.elems.end(); i++)
|
||||
elems = ATinsert(elems,
|
||||
ATmake("(<str>, <term>)",
|
||||
i->first.c_str(),
|
||||
unparsePaths(i->second.refs)));
|
||||
|
||||
return ATmake("Closure(<term>, <term>)", roots, elems);
|
||||
}
|
||||
|
||||
|
||||
static ATerm unparseDerivation(const Derivation & derivation)
|
||||
{
|
||||
ATermList args = ATempty;
|
||||
for (Strings::const_iterator i = derivation.args.begin();
|
||||
i != derivation.args.end(); i++)
|
||||
args = ATinsert(args, ATmake("<str>", i->c_str()));
|
||||
|
||||
ATermList env = ATempty;
|
||||
for (StringPairs::const_iterator i = derivation.env.begin();
|
||||
i != derivation.env.end(); i++)
|
||||
env = ATinsert(env,
|
||||
ATmake("(<str>, <str>)",
|
||||
i->first.c_str(), i->second.c_str()));
|
||||
|
||||
return ATmake("Derive(<term>, <term>, <str>, <str>, <term>, <term>)",
|
||||
unparsePaths(derivation.outputs),
|
||||
unparsePaths(derivation.inputs),
|
||||
derivation.platform.c_str(),
|
||||
derivation.builder.c_str(),
|
||||
ATreverse(args),
|
||||
ATreverse(env));
|
||||
}
|
||||
|
||||
|
||||
ATerm unparseNixExpr(const NixExpr & ne)
|
||||
{
|
||||
if (ne.type == NixExpr::neClosure)
|
||||
return unparseClosure(ne.closure);
|
||||
else if (ne.type == NixExpr::neDerivation)
|
||||
return unparseDerivation(ne.derivation);
|
||||
else abort();
|
||||
}
|
60
src/libstore/expr.hh
Normal file
60
src/libstore/expr.hh
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef __FSTATE_H
|
||||
#define __FSTATE_H
|
||||
|
||||
#include "aterm.hh"
|
||||
#include "store.hh"
|
||||
|
||||
|
||||
/* Abstract syntax of Nix expressions. */
|
||||
|
||||
struct ClosureElem
|
||||
{
|
||||
PathSet refs;
|
||||
};
|
||||
|
||||
typedef map<Path, ClosureElem> ClosureElems;
|
||||
|
||||
struct Closure
|
||||
{
|
||||
PathSet roots;
|
||||
ClosureElems elems;
|
||||
};
|
||||
|
||||
typedef map<string, string> StringPairs;
|
||||
|
||||
struct Derivation
|
||||
{
|
||||
PathSet outputs;
|
||||
PathSet inputs; /* Nix expressions, not actual inputs */
|
||||
string platform;
|
||||
Path builder;
|
||||
Strings args;
|
||||
StringPairs env;
|
||||
};
|
||||
|
||||
struct NixExpr
|
||||
{
|
||||
enum { neClosure, neDerivation } type;
|
||||
Closure closure;
|
||||
Derivation derivation;
|
||||
};
|
||||
|
||||
|
||||
/* Throw an exception with an error message containing the given
|
||||
aterm. */
|
||||
Error badTerm(const format & f, ATerm t);
|
||||
|
||||
/* Hash an aterm. */
|
||||
Hash hashTerm(ATerm t);
|
||||
|
||||
/* Write an aterm to the Nix store directory, and return its path. */
|
||||
Path writeTerm(ATerm t, const string & suffix);
|
||||
|
||||
/* Parse a Nix expression. */
|
||||
NixExpr parseNixExpr(ATerm t);
|
||||
|
||||
/* Parse a Nix expression. */
|
||||
ATerm unparseNixExpr(const NixExpr & ne);
|
||||
|
||||
|
||||
#endif /* !__FSTATE_H */
|
8
src/libstore/globals.cc
Normal file
8
src/libstore/globals.cc
Normal file
|
@ -0,0 +1,8 @@
|
|||
#include "globals.hh"
|
||||
|
||||
string nixStore = "/UNINIT";
|
||||
string nixDataDir = "/UNINIT";
|
||||
string nixLogDir = "/UNINIT";
|
||||
string nixDBPath = "/UNINIT";
|
||||
|
||||
bool keepFailed = false;
|
29
src/libstore/globals.hh
Normal file
29
src/libstore/globals.hh
Normal file
|
@ -0,0 +1,29 @@
|
|||
#ifndef __GLOBALS_H
|
||||
#define __GLOBALS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
/* Path names. */
|
||||
|
||||
/* nixStore is the directory where we generally store atomic and
|
||||
derived files. */
|
||||
extern string nixStore;
|
||||
|
||||
extern string nixDataDir; /* !!! fix */
|
||||
|
||||
/* nixLogDir is the directory where we log various operations. */
|
||||
extern string nixLogDir;
|
||||
|
||||
/* nixDBPath is the path name of our Berkeley DB environment. */
|
||||
extern string nixDBPath;
|
||||
|
||||
|
||||
/* Misc. global flags. */
|
||||
|
||||
/* Whether to keep temporary directories of failed builds. */
|
||||
extern bool keepFailed;
|
||||
|
||||
|
||||
#endif /* !__GLOBALS_H */
|
369
src/libstore/normalise.cc
Normal file
369
src/libstore/normalise.cc
Normal file
|
@ -0,0 +1,369 @@
|
|||
#include <map>
|
||||
|
||||
#include "normalise.hh"
|
||||
#include "references.hh"
|
||||
#include "exec.hh"
|
||||
#include "pathlocks.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
|
||||
static Path useSuccessor(const Path & path)
|
||||
{
|
||||
string pathSucc;
|
||||
if (querySuccessor(path, pathSucc)) {
|
||||
debug(format("successor %1% -> %2%") % (string) path % pathSucc);
|
||||
return pathSucc;
|
||||
} else
|
||||
return path;
|
||||
}
|
||||
|
||||
|
||||
Path normaliseNixExpr(const Path & _nePath, PathSet pending)
|
||||
{
|
||||
startNest(nest, lvlTalkative,
|
||||
format("normalising expression in `%1%'") % (string) _nePath);
|
||||
|
||||
/* Try to substitute the expression by any known successors in
|
||||
order to speed up the rewrite process. */
|
||||
Path nePath = useSuccessor(_nePath);
|
||||
|
||||
/* Get the Nix expression. */
|
||||
NixExpr ne = exprFromPath(nePath, pending);
|
||||
|
||||
/* If this is a normal form (i.e., a closure) we are done. */
|
||||
if (ne.type == NixExpr::neClosure) return nePath;
|
||||
if (ne.type != NixExpr::neDerivation) abort();
|
||||
|
||||
|
||||
/* Otherwise, it's a derivation expression, and we have to build it to
|
||||
determine its normal form. */
|
||||
|
||||
|
||||
/* Some variables. */
|
||||
|
||||
/* Input paths, with their closure elements. */
|
||||
ClosureElems inClosures;
|
||||
|
||||
/* Referenceable paths (i.e., input and output paths). */
|
||||
PathSet allPaths;
|
||||
|
||||
/* The environment to be passed to the builder. */
|
||||
Environment env;
|
||||
|
||||
/* The result. */
|
||||
NixExpr nf;
|
||||
nf.type = NixExpr::neClosure;
|
||||
|
||||
|
||||
/* The outputs are referenceable paths. */
|
||||
for (PathSet::iterator i = ne.derivation.outputs.begin();
|
||||
i != ne.derivation.outputs.end(); i++)
|
||||
{
|
||||
debug(format("building path `%1%'") % *i);
|
||||
allPaths.insert(*i);
|
||||
}
|
||||
|
||||
/* Obtain locks on all output paths. The locks are automatically
|
||||
released when we exit this function or Nix crashes. */
|
||||
PathLocks outputLocks(ne.derivation.outputs);
|
||||
|
||||
/* Now check again whether there is a successor. This is because
|
||||
another process may have started building in parallel. After
|
||||
it has finished and released the locks, we can (and should)
|
||||
reuse its results. (Strictly speaking the first successor
|
||||
check above can be omitted, but that would be less efficient.)
|
||||
Note that since we now hold the locks on the output paths, no
|
||||
other process can build this expression, so no further checks
|
||||
are necessary. */
|
||||
{
|
||||
Path nePath2 = useSuccessor(nePath);
|
||||
if (nePath != nePath2) {
|
||||
NixExpr ne = exprFromPath(nePath2, pending);
|
||||
debug(format("skipping build of expression `%1%', someone beat us to it")
|
||||
% (string) nePath);
|
||||
if (ne.type != NixExpr::neClosure) abort();
|
||||
return nePath2;
|
||||
}
|
||||
}
|
||||
|
||||
/* Right platform? */
|
||||
if (ne.derivation.platform != thisSystem)
|
||||
throw Error(format("a `%1%' is required, but I am a `%2%'")
|
||||
% ne.derivation.platform % thisSystem);
|
||||
|
||||
/* Realise inputs (and remember all input paths). */
|
||||
for (PathSet::iterator i = ne.derivation.inputs.begin();
|
||||
i != ne.derivation.inputs.end(); i++)
|
||||
{
|
||||
Path nfPath = normaliseNixExpr(*i, pending);
|
||||
realiseClosure(nfPath, pending);
|
||||
/* !!! nfPath should be a root of the garbage collector while
|
||||
we are building */
|
||||
NixExpr ne = exprFromPath(nfPath, pending);
|
||||
if (ne.type != NixExpr::neClosure) abort();
|
||||
for (ClosureElems::iterator j = ne.closure.elems.begin();
|
||||
j != ne.closure.elems.end(); j++)
|
||||
{
|
||||
inClosures[j->first] = j->second;
|
||||
allPaths.insert(j->first);
|
||||
}
|
||||
}
|
||||
|
||||
/* Most shells initialise PATH to some default (/bin:/usr/bin:...) when
|
||||
PATH is not set. We don't want this, so we fill it in with some dummy
|
||||
value. */
|
||||
env["PATH"] = "/path-not-set";
|
||||
|
||||
/* Set HOME to a non-existing path to prevent certain programs from using
|
||||
/etc/passwd (or NIS, or whatever) to locate the home directory (for
|
||||
example, wget looks for ~/.wgetrc). I.e., these tools use /etc/passwd
|
||||
if HOME is not set, but they will just assume that the settings file
|
||||
they are looking for does not exist if HOME is set but points to some
|
||||
non-existing path. */
|
||||
env["HOME"] = "/homeless-shelter";
|
||||
|
||||
/* Build the environment. */
|
||||
for (StringPairs::iterator i = ne.derivation.env.begin();
|
||||
i != ne.derivation.env.end(); i++)
|
||||
env[i->first] = i->second;
|
||||
|
||||
/* We can skip running the builder if all output paths are already
|
||||
valid. */
|
||||
bool fastBuild = true;
|
||||
for (PathSet::iterator i = ne.derivation.outputs.begin();
|
||||
i != ne.derivation.outputs.end(); i++)
|
||||
{
|
||||
if (!isValidPath(*i)) {
|
||||
fastBuild = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fastBuild) {
|
||||
|
||||
/* If any of the outputs already exist but are not registered,
|
||||
delete them. */
|
||||
for (PathSet::iterator i = ne.derivation.outputs.begin();
|
||||
i != ne.derivation.outputs.end(); i++)
|
||||
{
|
||||
Path path = *i;
|
||||
if (isValidPath(path))
|
||||
throw Error(format("obstructed build: path `%1%' exists") % path);
|
||||
if (pathExists(path)) {
|
||||
debug(format("removing unregistered path `%1%'") % path);
|
||||
deletePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run the builder. */
|
||||
printMsg(lvlChatty, format("building..."));
|
||||
runProgram(ne.derivation.builder, ne.derivation.args, env,
|
||||
nixLogDir + "/" + baseNameOf(nePath));
|
||||
printMsg(lvlChatty, format("build completed"));
|
||||
|
||||
} else
|
||||
printMsg(lvlChatty, format("fast build succesful"));
|
||||
|
||||
/* Check whether the output paths were created, and grep each
|
||||
output path to determine what other paths it references. Also make all
|
||||
output paths read-only. */
|
||||
PathSet usedPaths;
|
||||
for (PathSet::iterator i = ne.derivation.outputs.begin();
|
||||
i != ne.derivation.outputs.end(); i++)
|
||||
{
|
||||
Path path = *i;
|
||||
if (!pathExists(path))
|
||||
throw Error(format("path `%1%' does not exist") % path);
|
||||
nf.closure.roots.insert(path);
|
||||
|
||||
makePathReadOnly(path);
|
||||
|
||||
/* For this output path, find the references to other paths contained
|
||||
in it. */
|
||||
Strings refPaths = filterReferences(path,
|
||||
Strings(allPaths.begin(), allPaths.end()));
|
||||
|
||||
/* Construct a closure element for this output path. */
|
||||
ClosureElem elem;
|
||||
|
||||
/* For each path referenced by this output path, add its id to the
|
||||
closure element and add the id to the `usedPaths' set (so that the
|
||||
elements referenced by *its* closure are added below). */
|
||||
for (Paths::iterator j = refPaths.begin();
|
||||
j != refPaths.end(); j++)
|
||||
{
|
||||
Path path = *j;
|
||||
elem.refs.insert(path);
|
||||
if (inClosures.find(path) != inClosures.end())
|
||||
usedPaths.insert(path);
|
||||
else if (ne.derivation.outputs.find(path) == ne.derivation.outputs.end())
|
||||
abort();
|
||||
}
|
||||
|
||||
nf.closure.elems[path] = elem;
|
||||
}
|
||||
|
||||
/* Close the closure. That is, for any referenced path, add the paths
|
||||
referenced by it. */
|
||||
PathSet donePaths;
|
||||
|
||||
while (!usedPaths.empty()) {
|
||||
PathSet::iterator i = usedPaths.begin();
|
||||
Path path = *i;
|
||||
usedPaths.erase(i);
|
||||
|
||||
if (donePaths.find(path) != donePaths.end()) continue;
|
||||
donePaths.insert(path);
|
||||
|
||||
ClosureElems::iterator j = inClosures.find(path);
|
||||
if (j == inClosures.end()) abort();
|
||||
|
||||
nf.closure.elems[path] = j->second;
|
||||
|
||||
for (PathSet::iterator k = j->second.refs.begin();
|
||||
k != j->second.refs.end(); k++)
|
||||
usedPaths.insert(*k);
|
||||
}
|
||||
|
||||
/* For debugging, print out the referenced and unreferenced paths. */
|
||||
for (ClosureElems::iterator i = inClosures.begin();
|
||||
i != inClosures.end(); i++)
|
||||
{
|
||||
PathSet::iterator j = donePaths.find(i->first);
|
||||
if (j == donePaths.end())
|
||||
debug(format("NOT referenced: `%1%'") % i->first);
|
||||
else
|
||||
debug(format("referenced: `%1%'") % i->first);
|
||||
}
|
||||
|
||||
/* Write the normal form. This does not have to occur in the
|
||||
transaction below because writing terms is idem-potent. */
|
||||
ATerm nfTerm = unparseNixExpr(nf);
|
||||
printMsg(lvlVomit, format("normal form: %1%") % atPrint(nfTerm));
|
||||
Path nfPath = writeTerm(nfTerm, "-s");
|
||||
|
||||
/* Register each outpat path, and register the normal form. This
|
||||
is wrapped in one database transaction to ensure that if we
|
||||
crash, either everything is registered or nothing is. This is
|
||||
for recoverability: unregistered paths in the store can be
|
||||
deleted arbitrarily, while registered paths can only be deleted
|
||||
by running the garbage collector. */
|
||||
Transaction txn;
|
||||
createStoreTransaction(txn);
|
||||
for (PathSet::iterator i = ne.derivation.outputs.begin();
|
||||
i != ne.derivation.outputs.end(); i++)
|
||||
registerValidPath(txn, *i);
|
||||
registerSuccessor(txn, nePath, nfPath);
|
||||
txn.commit();
|
||||
|
||||
return nfPath;
|
||||
}
|
||||
|
||||
|
||||
void realiseClosure(const Path & nePath, PathSet pending)
|
||||
{
|
||||
startNest(nest, lvlDebug, format("realising closure `%1%'") % nePath);
|
||||
|
||||
NixExpr ne = exprFromPath(nePath, pending);
|
||||
if (ne.type != NixExpr::neClosure)
|
||||
throw Error(format("expected closure in `%1%'") % nePath);
|
||||
|
||||
for (ClosureElems::const_iterator i = ne.closure.elems.begin();
|
||||
i != ne.closure.elems.end(); i++)
|
||||
ensurePath(i->first, pending);
|
||||
}
|
||||
|
||||
|
||||
void ensurePath(const Path & path, PathSet pending)
|
||||
{
|
||||
/* If the path is already valid, we're done. */
|
||||
if (isValidPath(path)) return;
|
||||
|
||||
/* Otherwise, try the substitutes. */
|
||||
Paths subPaths = querySubstitutes(path);
|
||||
|
||||
for (Paths::iterator i = subPaths.begin();
|
||||
i != subPaths.end(); i++)
|
||||
{
|
||||
try {
|
||||
normaliseNixExpr(*i, pending);
|
||||
if (isValidPath(path)) return;
|
||||
throw Error(format("substitute failed to produce expected output path"));
|
||||
} catch (Error & e) {
|
||||
printMsg(lvlTalkative,
|
||||
format("building of substitute `%1%' for `%2%' failed: %3%")
|
||||
% *i % path % e.what());
|
||||
}
|
||||
}
|
||||
|
||||
throw Error(format("path `%1%' is required, "
|
||||
"but there are no (successful) substitutes") % path);
|
||||
}
|
||||
|
||||
|
||||
NixExpr exprFromPath(const Path & path, PathSet pending)
|
||||
{
|
||||
ensurePath(path, pending);
|
||||
ATerm t = ATreadFromNamedFile(path.c_str());
|
||||
if (!t) throw Error(format("cannot read aterm from `%1%'") % path);
|
||||
return parseNixExpr(t);
|
||||
}
|
||||
|
||||
|
||||
PathSet nixExprRoots(const Path & nePath)
|
||||
{
|
||||
PathSet paths;
|
||||
|
||||
NixExpr ne = exprFromPath(nePath);
|
||||
|
||||
if (ne.type == NixExpr::neClosure)
|
||||
paths.insert(ne.closure.roots.begin(), ne.closure.roots.end());
|
||||
else if (ne.type == NixExpr::neDerivation)
|
||||
paths.insert(ne.derivation.outputs.begin(),
|
||||
ne.derivation.outputs.end());
|
||||
else abort();
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
|
||||
static void requisitesWorker(const Path & nePath,
|
||||
bool includeExprs, bool includeSuccessors,
|
||||
PathSet & paths, PathSet & doneSet)
|
||||
{
|
||||
if (doneSet.find(nePath) != doneSet.end()) return;
|
||||
doneSet.insert(nePath);
|
||||
|
||||
NixExpr ne = exprFromPath(nePath);
|
||||
|
||||
if (ne.type == NixExpr::neClosure)
|
||||
for (ClosureElems::iterator i = ne.closure.elems.begin();
|
||||
i != ne.closure.elems.end(); i++)
|
||||
paths.insert(i->first);
|
||||
|
||||
else if (ne.type == NixExpr::neDerivation)
|
||||
for (PathSet::iterator i = ne.derivation.inputs.begin();
|
||||
i != ne.derivation.inputs.end(); i++)
|
||||
requisitesWorker(*i,
|
||||
includeExprs, includeSuccessors, paths, doneSet);
|
||||
|
||||
else abort();
|
||||
|
||||
if (includeExprs) paths.insert(nePath);
|
||||
|
||||
string nfPath;
|
||||
if (includeSuccessors && (nfPath = useSuccessor(nePath)) != nePath)
|
||||
requisitesWorker(nfPath, includeExprs, includeSuccessors,
|
||||
paths, doneSet);
|
||||
}
|
||||
|
||||
|
||||
PathSet nixExprRequisites(const Path & nePath,
|
||||
bool includeExprs, bool includeSuccessors)
|
||||
{
|
||||
PathSet paths;
|
||||
PathSet doneSet;
|
||||
requisitesWorker(nePath, includeExprs, includeSuccessors,
|
||||
paths, doneSet);
|
||||
return paths;
|
||||
}
|
46
src/libstore/normalise.hh
Normal file
46
src/libstore/normalise.hh
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef __NORMALISE_H
|
||||
#define __NORMALISE_H
|
||||
|
||||
#include "expr.hh"
|
||||
|
||||
|
||||
/* Normalise a Nix expression. That is, if the expression is a
|
||||
derivation, a path containing an equivalent closure expression is
|
||||
returned. This requires that the derivation is performed, unless a
|
||||
successor is known. */
|
||||
Path normaliseNixExpr(const Path & nePath, PathSet pending = PathSet());
|
||||
|
||||
/* Realise a closure expression in the file system.
|
||||
|
||||
The pending paths are those that are already being realised. This
|
||||
prevents infinite recursion for paths realised through a substitute
|
||||
(since when we build the substitute, we would first try to realise
|
||||
its output paths through substitutes... kaboom!). */
|
||||
void realiseClosure(const Path & nePath, PathSet pending = PathSet());
|
||||
|
||||
/* Ensure that a path exists, possibly by instantiating it by
|
||||
realising a substitute. */
|
||||
void ensurePath(const Path & path, PathSet pending = PathSet());
|
||||
|
||||
/* Read a Nix expression, after ensuring its existence through
|
||||
ensurePath(). */
|
||||
NixExpr exprFromPath(const Path & path, PathSet pending = PathSet());
|
||||
|
||||
/* Get the list of root (output) paths of the given Nix expression. */
|
||||
PathSet nixExprRoots(const Path & nePath);
|
||||
|
||||
/* Get the list of paths that are required to realise the given
|
||||
expression. For a derive expression, this is the union of
|
||||
requisites of the inputs; for a closure expression, it is the path of
|
||||
each element in the closure. If `includeExprs' is true, include the
|
||||
paths of the Nix expressions themselves. If `includeSuccessors' is
|
||||
true, include the requisites of successors. */
|
||||
PathSet nixExprRequisites(const Path & nePath,
|
||||
bool includeExprs, bool includeSuccessors);
|
||||
|
||||
/* Return the list of the paths of all known Nix expressions whose
|
||||
output paths are completely contained in the set `outputs'. */
|
||||
PathSet findGenerators(const PathSet & outputs);
|
||||
|
||||
|
||||
#endif /* !__NORMALISE_H */
|
90
src/libstore/pathlocks.cc
Normal file
90
src/libstore/pathlocks.cc
Normal file
|
@ -0,0 +1,90 @@
|
|||
#include <cerrno>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "pathlocks.hh"
|
||||
|
||||
|
||||
bool lockFile(int fd, LockType lockType, bool wait)
|
||||
{
|
||||
struct flock lock;
|
||||
if (lockType == ltRead) lock.l_type = F_RDLCK;
|
||||
else if (lockType == ltWrite) lock.l_type = F_WRLCK;
|
||||
else if (lockType == ltNone) lock.l_type = F_UNLCK;
|
||||
else abort();
|
||||
lock.l_whence = SEEK_SET;
|
||||
lock.l_start = 0;
|
||||
lock.l_len = 0; /* entire file */
|
||||
|
||||
if (wait) {
|
||||
while (fcntl(fd, F_SETLKW, &lock) != 0)
|
||||
if (errno != EINTR)
|
||||
throw SysError(format("acquiring/releasing lock"));
|
||||
} else {
|
||||
while (fcntl(fd, F_SETLK, &lock) != 0) {
|
||||
if (errno == EACCES || errno == EAGAIN) return false;
|
||||
if (errno != EINTR)
|
||||
throw SysError(format("acquiring/releasing lock"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* This enables us to check whether are not already holding a lock on
|
||||
a file ourselves. POSIX locks (fcntl) suck in this respect: if we
|
||||
close a descriptor, the previous lock will be closed as well. And
|
||||
there is no way to query whether we already have a lock (F_GETLK
|
||||
only works on locks held by other processes). */
|
||||
static StringSet lockedPaths; /* !!! not thread-safe */
|
||||
|
||||
|
||||
PathLocks::PathLocks(const PathSet & _paths)
|
||||
{
|
||||
/* Note that `fds' is built incrementally so that the destructor
|
||||
will only release those locks that we have already acquired. */
|
||||
|
||||
/* Sort the paths. This assures that locks are always acquired in
|
||||
the same order, thus preventing deadlocks. */
|
||||
Paths paths(_paths.begin(), _paths.end());
|
||||
paths.sort();
|
||||
|
||||
/* Acquire the lock for each path. */
|
||||
for (Paths::iterator i = paths.begin(); i != paths.end(); i++) {
|
||||
Path path = *i;
|
||||
Path lockPath = path + ".lock";
|
||||
|
||||
debug(format("locking path `%1%'") % path);
|
||||
|
||||
if (lockedPaths.find(lockPath) != lockedPaths.end()) {
|
||||
debug(format("already holding lock on `%1%'") % lockPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Open/create the lock file. */
|
||||
int fd = open(lockPath.c_str(), O_WRONLY | O_CREAT, 0666);
|
||||
if (fd == -1)
|
||||
throw SysError(format("opening lock file `%1%'") % lockPath);
|
||||
|
||||
fds.push_back(fd);
|
||||
this->paths.push_back(lockPath);
|
||||
|
||||
/* Acquire an exclusive lock. */
|
||||
lockFile(fd, ltWrite, true);
|
||||
|
||||
lockedPaths.insert(lockPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PathLocks::~PathLocks()
|
||||
{
|
||||
for (list<int>::iterator i = fds.begin(); i != fds.end(); i++)
|
||||
close(*i);
|
||||
|
||||
for (Paths::iterator i = paths.begin(); i != paths.end(); i++)
|
||||
lockedPaths.erase(*i);
|
||||
}
|
24
src/libstore/pathlocks.hh
Normal file
24
src/libstore/pathlocks.hh
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef __PATHLOCKS_H
|
||||
#define __PATHLOCKS_H
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
|
||||
typedef enum LockType { ltRead, ltWrite, ltNone };
|
||||
|
||||
bool lockFile(int fd, LockType lockType, bool wait);
|
||||
|
||||
|
||||
class PathLocks
|
||||
{
|
||||
private:
|
||||
list<int> fds;
|
||||
Paths paths;
|
||||
|
||||
public:
|
||||
PathLocks(const PathSet & _paths);
|
||||
~PathLocks();
|
||||
};
|
||||
|
||||
|
||||
#endif /* !__PATHLOCKS_H */
|
106
src/libstore/references.cc
Normal file
106
src/libstore/references.cc
Normal file
|
@ -0,0 +1,106 @@
|
|||
#include <cerrno>
|
||||
#include <map>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "references.hh"
|
||||
#include "hash.hh"
|
||||
|
||||
|
||||
static void search(const string & s,
|
||||
Strings & ids, Strings & seen)
|
||||
{
|
||||
for (Strings::iterator i = ids.begin();
|
||||
i != ids.end(); )
|
||||
{
|
||||
if (s.find(*i) == string::npos)
|
||||
i++;
|
||||
else {
|
||||
debug(format("found reference to `%1%'") % *i);
|
||||
seen.push_back(*i);
|
||||
i = ids.erase(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void checkPath(const string & path,
|
||||
Strings & ids, Strings & seen)
|
||||
{
|
||||
struct stat st;
|
||||
if (lstat(path.c_str(), &st))
|
||||
throw SysError(format("getting attributes of path `%1%'") % path);
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
AutoCloseDir dir = opendir(path.c_str());
|
||||
|
||||
struct dirent * dirent;
|
||||
while (errno = 0, dirent = readdir(dir)) {
|
||||
string name = dirent->d_name;
|
||||
if (name == "." || name == "..") continue;
|
||||
search(name, ids, seen);
|
||||
checkPath(path + "/" + name, ids, seen);
|
||||
}
|
||||
}
|
||||
|
||||
else if (S_ISREG(st.st_mode)) {
|
||||
|
||||
debug(format("checking `%1%'") % path);
|
||||
|
||||
AutoCloseFD fd = open(path.c_str(), O_RDONLY);
|
||||
if (fd == -1) throw SysError(format("opening file `%1%'") % path);
|
||||
|
||||
unsigned char * buf = new unsigned char[st.st_size];
|
||||
|
||||
readFull(fd, buf, st.st_size);
|
||||
|
||||
search(string((char *) buf, st.st_size), ids, seen);
|
||||
|
||||
delete buf; /* !!! autodelete */
|
||||
}
|
||||
|
||||
else if (S_ISLNK(st.st_mode)) {
|
||||
char buf[st.st_size];
|
||||
if (readlink(path.c_str(), buf, st.st_size) != st.st_size)
|
||||
throw SysError(format("reading symbolic link `%1%'") % path);
|
||||
search(string(buf, st.st_size), ids, seen);
|
||||
}
|
||||
|
||||
else throw Error(format("unknown file type: %1%") % path);
|
||||
}
|
||||
|
||||
|
||||
Strings filterReferences(const string & path, const Strings & paths)
|
||||
{
|
||||
map<string, string> backMap;
|
||||
Strings ids;
|
||||
Strings seen;
|
||||
|
||||
/* For efficiency (and a higher hit rate), just search for the
|
||||
hash part of the file name. (This assumes that all references
|
||||
have the form `HASH-bla'). */
|
||||
for (Strings::const_iterator i = paths.begin();
|
||||
i != paths.end(); i++)
|
||||
{
|
||||
string s = string(baseNameOf(*i), 0, 32);
|
||||
parseHash(s);
|
||||
ids.push_back(s);
|
||||
backMap[s] = *i;
|
||||
}
|
||||
|
||||
checkPath(path, ids, seen);
|
||||
|
||||
Strings found;
|
||||
for (Strings::iterator i = seen.begin(); i != seen.end(); i++)
|
||||
{
|
||||
map<string, string>::iterator j;
|
||||
if ((j = backMap.find(*i)) == backMap.end()) abort();
|
||||
found.push_back(j->second);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
10
src/libstore/references.hh
Normal file
10
src/libstore/references.hh
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ifndef __REFERENCES_H
|
||||
#define __REFERENCES_H
|
||||
|
||||
#include "util.hh"
|
||||
|
||||
|
||||
Strings filterReferences(const Path & path, const Strings & refs);
|
||||
|
||||
|
||||
#endif /* !__REFERENCES_H */
|
407
src/libstore/store.cc
Normal file
407
src/libstore/store.cc
Normal file
|
@ -0,0 +1,407 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "store.hh"
|
||||
#include "globals.hh"
|
||||
#include "db.hh"
|
||||
#include "archive.hh"
|
||||
#include "pathlocks.hh"
|
||||
|
||||
|
||||
/* Nix database. */
|
||||
static Database nixDB;
|
||||
|
||||
|
||||
/* Database tables. */
|
||||
|
||||
/* dbValidPaths :: Path -> ()
|
||||
|
||||
The existence of a key $p$ indicates that path $p$ is valid (that
|
||||
is, produced by a succesful build). */
|
||||
static TableId dbValidPaths;
|
||||
|
||||
/* dbSuccessors :: Path -> Path
|
||||
|
||||
Each pair $(p_1, p_2)$ in this mapping records the fact that the
|
||||
Nix expression stored at path $p_1$ has a successor expression
|
||||
stored at path $p_2$.
|
||||
|
||||
Note that a term $y$ is a successor of $x$ iff there exists a
|
||||
sequence of rewrite steps that rewrites $x$ into $y$.
|
||||
*/
|
||||
static TableId dbSuccessors;
|
||||
|
||||
/* dbSuccessorsRev :: Path -> [Path]
|
||||
|
||||
The reverse mapping of dbSuccessors (i.e., it stores the
|
||||
predecessors of a Nix expression).
|
||||
*/
|
||||
static TableId dbSuccessorsRev;
|
||||
|
||||
/* dbSubstitutes :: Path -> [Path]
|
||||
|
||||
Each pair $(p, [ps])$ tells Nix that it can realise any of the
|
||||
Nix expressions stored at paths $ps$ to produce a path $p$.
|
||||
|
||||
The main purpose of this is for distributed caching of derivates.
|
||||
One system can compute a derivate and put it on a website (as a Nix
|
||||
archive), for instance, and then another system can register a
|
||||
substitute for that derivate. The substitute in this case might be
|
||||
a Nix expression that fetches the Nix archive.
|
||||
*/
|
||||
static TableId dbSubstitutes;
|
||||
|
||||
/* dbSubstitutesRev :: Path -> [Path]
|
||||
|
||||
The reverse mapping of dbSubstitutes.
|
||||
*/
|
||||
static TableId dbSubstitutesRev;
|
||||
|
||||
|
||||
void openDB()
|
||||
{
|
||||
nixDB.open(nixDBPath);
|
||||
dbValidPaths = nixDB.openTable("validpaths");
|
||||
dbSuccessors = nixDB.openTable("successors");
|
||||
dbSuccessorsRev = nixDB.openTable("successors-rev");
|
||||
dbSubstitutes = nixDB.openTable("substitutes");
|
||||
dbSubstitutesRev = nixDB.openTable("substitutes-rev");
|
||||
}
|
||||
|
||||
|
||||
void initDB()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void createStoreTransaction(Transaction & txn)
|
||||
{
|
||||
Transaction txn2(nixDB);
|
||||
txn2.moveTo(txn);
|
||||
}
|
||||
|
||||
|
||||
/* Path copying. */
|
||||
|
||||
struct CopySink : DumpSink
|
||||
{
|
||||
int fd;
|
||||
virtual void operator () (const unsigned char * data, unsigned int len)
|
||||
{
|
||||
writeFull(fd, data, len);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct CopySource : RestoreSource
|
||||
{
|
||||
int fd;
|
||||
virtual void operator () (unsigned char * data, unsigned int len)
|
||||
{
|
||||
readFull(fd, data, len);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void copyPath(const Path & src, const Path & dst)
|
||||
{
|
||||
debug(format("copying `%1%' to `%2%'") % src % dst);
|
||||
|
||||
/* Unfortunately C++ doesn't support coprocedures, so we have no
|
||||
nice way to chain CopySink and CopySource together. Instead we
|
||||
fork off a child to run the sink. (Fork-less platforms should
|
||||
use a thread). */
|
||||
|
||||
/* Create a pipe. */
|
||||
int fds[2];
|
||||
if (pipe(fds) == -1) throw SysError("creating pipe");
|
||||
|
||||
/* Fork. */
|
||||
pid_t pid;
|
||||
switch (pid = fork()) {
|
||||
|
||||
case -1:
|
||||
throw SysError("unable to fork");
|
||||
|
||||
case 0: /* child */
|
||||
try {
|
||||
close(fds[1]);
|
||||
CopySource source;
|
||||
source.fd = fds[0];
|
||||
restorePath(dst, source);
|
||||
_exit(0);
|
||||
} catch (exception & e) {
|
||||
cerr << "error: " << e.what() << endl;
|
||||
}
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
close(fds[0]);
|
||||
|
||||
/* Parent. */
|
||||
|
||||
CopySink sink;
|
||||
sink.fd = fds[1];
|
||||
dumpPath(src, sink);
|
||||
|
||||
/* Wait for the child to finish. */
|
||||
int status;
|
||||
if (waitpid(pid, &status, 0) != pid)
|
||||
throw SysError("waiting for child");
|
||||
|
||||
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
|
||||
throw Error("cannot copy file: child died");
|
||||
}
|
||||
|
||||
|
||||
void registerSuccessor(const Transaction & txn,
|
||||
const Path & srcPath, const Path & sucPath)
|
||||
{
|
||||
Path known;
|
||||
if (nixDB.queryString(txn, dbSuccessors, srcPath, known) &&
|
||||
known != sucPath)
|
||||
{
|
||||
throw Error(format(
|
||||
"the `impossible' happened: expression in path "
|
||||
"`%1%' appears to have multiple successors "
|
||||
"(known `%2%', new `%3%'")
|
||||
% srcPath % known % sucPath);
|
||||
}
|
||||
|
||||
Paths revs;
|
||||
nixDB.queryStrings(txn, dbSuccessorsRev, sucPath, revs);
|
||||
if (find(revs.begin(), revs.end(), srcPath) == revs.end())
|
||||
revs.push_back(srcPath);
|
||||
|
||||
nixDB.setString(txn, dbSuccessors, srcPath, sucPath);
|
||||
nixDB.setStrings(txn, dbSuccessorsRev, sucPath, revs);
|
||||
}
|
||||
|
||||
|
||||
bool querySuccessor(const Path & srcPath, Path & sucPath)
|
||||
{
|
||||
return nixDB.queryString(noTxn, dbSuccessors, srcPath, sucPath);
|
||||
}
|
||||
|
||||
|
||||
Paths queryPredecessors(const Path & sucPath)
|
||||
{
|
||||
Paths revs;
|
||||
nixDB.queryStrings(noTxn, dbSuccessorsRev, sucPath, revs);
|
||||
return revs;
|
||||
}
|
||||
|
||||
|
||||
void registerSubstitute(const Path & srcPath, const Path & subPath)
|
||||
{
|
||||
Transaction txn(nixDB);
|
||||
|
||||
Paths subs;
|
||||
nixDB.queryStrings(txn, dbSubstitutes, srcPath, subs);
|
||||
|
||||
if (find(subs.begin(), subs.end(), subPath) != subs.end()) {
|
||||
/* Nothing to do if the substitute is already known. */
|
||||
txn.abort();
|
||||
return;
|
||||
}
|
||||
subs.push_front(subPath); /* new substitutes take precedence */
|
||||
|
||||
Paths revs;
|
||||
nixDB.queryStrings(txn, dbSubstitutesRev, subPath, revs);
|
||||
if (find(revs.begin(), revs.end(), srcPath) == revs.end())
|
||||
revs.push_back(srcPath);
|
||||
|
||||
nixDB.setStrings(txn, dbSubstitutes, srcPath, subs);
|
||||
nixDB.setStrings(txn, dbSubstitutesRev, subPath, revs);
|
||||
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
|
||||
Paths querySubstitutes(const Path & srcPath)
|
||||
{
|
||||
Paths subPaths;
|
||||
nixDB.queryStrings(noTxn, dbSubstitutes, srcPath, subPaths);
|
||||
return subPaths;
|
||||
}
|
||||
|
||||
|
||||
void registerValidPath(const Transaction & txn, const Path & _path)
|
||||
{
|
||||
Path path(canonPath(_path));
|
||||
debug(format("registering path `%1%'") % path);
|
||||
nixDB.setString(txn, dbValidPaths, path, "");
|
||||
}
|
||||
|
||||
|
||||
bool isValidPath(const Path & path)
|
||||
{
|
||||
string s;
|
||||
return nixDB.queryString(noTxn, dbValidPaths, path, s);
|
||||
}
|
||||
|
||||
|
||||
void unregisterValidPath(const Path & _path)
|
||||
{
|
||||
Path path(canonPath(_path));
|
||||
Transaction txn(nixDB);
|
||||
|
||||
debug(format("unregistering path `%1%'") % path);
|
||||
|
||||
nixDB.delPair(txn, dbValidPaths, path);
|
||||
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
|
||||
static bool isInPrefix(const string & path, const string & _prefix)
|
||||
{
|
||||
string prefix = canonPath(_prefix + "/");
|
||||
return string(path, 0, prefix.size()) == prefix;
|
||||
}
|
||||
|
||||
|
||||
Path addToStore(const Path & _srcPath)
|
||||
{
|
||||
Path srcPath(absPath(_srcPath));
|
||||
debug(format("adding `%1%' to the store") % srcPath);
|
||||
|
||||
Hash h = hashPath(srcPath);
|
||||
|
||||
string baseName = baseNameOf(srcPath);
|
||||
Path dstPath = canonPath(nixStore + "/" + (string) h + "-" + baseName);
|
||||
|
||||
if (!isValidPath(dstPath)) {
|
||||
|
||||
/* The first check above is an optimisation to prevent
|
||||
unnecessary lock acquisition. */
|
||||
|
||||
PathSet lockPaths;
|
||||
lockPaths.insert(dstPath);
|
||||
PathLocks outputLock(lockPaths);
|
||||
|
||||
if (!isValidPath(dstPath)) {
|
||||
copyPath(srcPath, dstPath);
|
||||
|
||||
Transaction txn(nixDB);
|
||||
registerValidPath(txn, dstPath);
|
||||
txn.commit();
|
||||
}
|
||||
}
|
||||
|
||||
return dstPath;
|
||||
}
|
||||
|
||||
|
||||
void addTextToStore(const Path & dstPath, const string & s)
|
||||
{
|
||||
if (!isValidPath(dstPath)) {
|
||||
|
||||
PathSet lockPaths;
|
||||
lockPaths.insert(dstPath);
|
||||
PathLocks outputLock(lockPaths);
|
||||
|
||||
if (!isValidPath(dstPath)) {
|
||||
|
||||
AutoCloseFD fd = open(dstPath.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0666);
|
||||
if (fd == -1) throw SysError(format("creating store file `%1%'") % dstPath);
|
||||
|
||||
writeFull(fd, (unsigned char *) s.c_str(), s.size());
|
||||
|
||||
Transaction txn(nixDB);
|
||||
registerValidPath(txn, dstPath);
|
||||
txn.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void deleteFromStore(const Path & _path)
|
||||
{
|
||||
Path path(canonPath(_path));
|
||||
|
||||
if (!isInPrefix(path, nixStore))
|
||||
throw Error(format("path `%1%' is not in the store") % path);
|
||||
|
||||
unregisterValidPath(path);
|
||||
|
||||
deletePath(path);
|
||||
}
|
||||
|
||||
|
||||
void verifyStore()
|
||||
{
|
||||
Transaction txn(nixDB);
|
||||
|
||||
Paths paths;
|
||||
nixDB.enumTable(txn, dbValidPaths, paths);
|
||||
|
||||
for (Paths::iterator i = paths.begin();
|
||||
i != paths.end(); i++)
|
||||
{
|
||||
Path path = *i;
|
||||
if (!pathExists(path)) {
|
||||
debug(format("path `%1%' disappeared") % path);
|
||||
nixDB.delPair(txn, dbValidPaths, path);
|
||||
nixDB.delPair(txn, dbSuccessorsRev, path);
|
||||
nixDB.delPair(txn, dbSubstitutesRev, path);
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
Strings subs;
|
||||
nixDB.enumTable(txn, dbSubstitutes, subs);
|
||||
|
||||
for (Strings::iterator i = subs.begin();
|
||||
i != subs.end(); i++)
|
||||
{
|
||||
FSId srcId = parseHash(*i);
|
||||
|
||||
Strings subIds;
|
||||
nixDB.queryStrings(txn, dbSubstitutes, srcId, subIds);
|
||||
|
||||
for (Strings::iterator j = subIds.begin();
|
||||
j != subIds.end(); )
|
||||
{
|
||||
FSId subId = parseHash(*j);
|
||||
|
||||
Strings subPaths;
|
||||
nixDB.queryStrings(txn, dbId2Paths, subId, subPaths);
|
||||
if (subPaths.size() == 0) {
|
||||
debug(format("erasing substitute %1% for %2%")
|
||||
% (string) subId % (string) srcId);
|
||||
j = subIds.erase(j);
|
||||
} else j++;
|
||||
}
|
||||
|
||||
nixDB.setStrings(txn, dbSubstitutes, srcId, subIds);
|
||||
}
|
||||
#endif
|
||||
|
||||
Paths sucs;
|
||||
nixDB.enumTable(txn, dbSuccessors, sucs);
|
||||
|
||||
for (Paths::iterator i = sucs.begin(); i != sucs.end(); i++) {
|
||||
Path srcPath = *i;
|
||||
|
||||
Path sucPath;
|
||||
if (!nixDB.queryString(txn, dbSuccessors, srcPath, sucPath)) abort();
|
||||
|
||||
Paths revs;
|
||||
nixDB.queryStrings(txn, dbSuccessorsRev, sucPath, revs);
|
||||
|
||||
if (find(revs.begin(), revs.end(), srcPath) == revs.end()) {
|
||||
debug(format("reverse successor mapping from `%1%' to `%2%' missing")
|
||||
% srcPath % sucPath);
|
||||
revs.push_back(srcPath);
|
||||
nixDB.setStrings(txn, dbSuccessorsRev, sucPath, revs);
|
||||
}
|
||||
}
|
||||
|
||||
txn.commit();
|
||||
}
|
72
src/libstore/store.hh
Normal file
72
src/libstore/store.hh
Normal file
|
@ -0,0 +1,72 @@
|
|||
#ifndef __STORE_H
|
||||
#define __STORE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "hash.hh"
|
||||
#include "db.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
/* Open the database environment. */
|
||||
void openDB();
|
||||
|
||||
/* Create the required database tables. */
|
||||
void initDB();
|
||||
|
||||
/* Get a transaction object. */
|
||||
void createStoreTransaction(Transaction & txn);
|
||||
|
||||
/* Copy a path recursively. */
|
||||
void copyPath(const Path & src, const Path & dst);
|
||||
|
||||
/* Register a successor. This function accepts a transaction handle
|
||||
so that it can be enclosed in an atomic operation with calls to
|
||||
registerValidPath(). This must be atomic, since if we register a
|
||||
successor for a derivation without registering the paths built in
|
||||
the derivation, we have a successor with dangling pointers, and if
|
||||
we do it in reverse order, we can get an obstructed build (since to
|
||||
rebuild the successor, the outputs paths must not exist). */
|
||||
void registerSuccessor(const Transaction & txn,
|
||||
const Path & srcPath, const Path & sucPath);
|
||||
|
||||
/* Return the predecessors of the Nix expression stored at the given
|
||||
path. */
|
||||
bool querySuccessor(const Path & srcPath, Path & sucPath);
|
||||
|
||||
/* Return the predecessors of the Nix expression stored at the given
|
||||
path. */
|
||||
Paths queryPredecessors(const Path & sucPath);
|
||||
|
||||
/* Register a substitute. */
|
||||
void registerSubstitute(const Path & srcPath, const Path & subPath);
|
||||
|
||||
/* Return the substitutes expression for the given path. */
|
||||
Paths querySubstitutes(const Path & srcPath);
|
||||
|
||||
/* Register the validity of a path. */
|
||||
void registerValidPath(const Transaction & txn, const Path & path);
|
||||
|
||||
/* Unregister the validity of a path. */
|
||||
void unregisterValidPath(const Path & path);
|
||||
|
||||
/* Checks whether a path is valid. */
|
||||
bool isValidPath(const Path & path);
|
||||
|
||||
/* Copy the contents of a path to the store and register the validity
|
||||
the resulting path. The resulting path is returned. */
|
||||
Path addToStore(const Path & srcPath);
|
||||
|
||||
/* Like addToStore, but the path of the output is given, and the
|
||||
contents written to the output path is a regular file containing
|
||||
the given string. */
|
||||
void addTextToStore(const Path & dstPath, const string & s);
|
||||
|
||||
/* Delete a value from the nixStore directory. */
|
||||
void deleteFromStore(const Path & path);
|
||||
|
||||
void verifyStore();
|
||||
|
||||
|
||||
#endif /* !__STORE_H */
|
3
src/libstore/test-builder-1.sh
Executable file
3
src/libstore/test-builder-1.sh
Executable file
|
@ -0,0 +1,3 @@
|
|||
#! /bin/sh
|
||||
|
||||
echo "Hello World" > $out
|
9
src/libstore/test-builder-2.sh
Executable file
9
src/libstore/test-builder-2.sh
Executable file
|
@ -0,0 +1,9 @@
|
|||
#! /bin/sh
|
||||
|
||||
echo "builder 2"
|
||||
|
||||
/bin/mkdir $out || exit 1
|
||||
cd $out || exit 1
|
||||
echo "Hallo Wereld" > bla
|
||||
echo $builder >> bla
|
||||
echo $out >> bla
|
162
src/libstore/test.cc
Normal file
162
src/libstore/test.cc
Normal file
|
@ -0,0 +1,162 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "hash.hh"
|
||||
#include "archive.hh"
|
||||
#include "util.hh"
|
||||
#include "normalise.hh"
|
||||
#include "globals.hh"
|
||||
|
||||
|
||||
void realise(Path nePath)
|
||||
{
|
||||
Nest nest(lvlDebug, format("TEST: realising `%1%'") % nePath);
|
||||
realiseClosure(normaliseNixExpr(nePath));
|
||||
}
|
||||
|
||||
|
||||
struct MySink : DumpSink
|
||||
{
|
||||
virtual void operator () (const unsigned char * data, unsigned int len)
|
||||
{
|
||||
/* Don't use cout, it's slow as hell! */
|
||||
writeFull(STDOUT_FILENO, data, len);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct MySource : RestoreSource
|
||||
{
|
||||
virtual void operator () (unsigned char * data, unsigned int len)
|
||||
{
|
||||
readFull(STDIN_FILENO, data, len);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void runTests()
|
||||
{
|
||||
verbosity = (Verbosity) 100;
|
||||
|
||||
/* Hashing. */
|
||||
string s = "0b0ffd0538622bfe20b92c4aa57254d9";
|
||||
Hash h = parseHash(s);
|
||||
if ((string) h != s) abort();
|
||||
|
||||
try {
|
||||
h = parseHash("blah blah");
|
||||
abort();
|
||||
} catch (Error err) { };
|
||||
|
||||
try {
|
||||
h = parseHash("0b0ffd0538622bfe20b92c4aa57254d99");
|
||||
abort();
|
||||
} catch (Error err) { };
|
||||
|
||||
/* Path canonicalisation. */
|
||||
cout << canonPath("/./../././//") << endl;
|
||||
cout << canonPath("/foo/bar") << endl;
|
||||
cout << canonPath("///foo/////bar//") << endl;
|
||||
cout << canonPath("/././/foo/////bar//.") << endl;
|
||||
cout << canonPath("/foo////bar//..///x/") << endl;
|
||||
cout << canonPath("/foo////bar//..//..//x/y/../z/") << endl;
|
||||
cout << canonPath("/foo/bar/../../../..///") << endl;
|
||||
|
||||
/* Dumping. */
|
||||
|
||||
#if 0
|
||||
MySink sink;
|
||||
dumpPath("scratch", sink);
|
||||
cout << (string) hashPath("scratch") << endl;
|
||||
#endif
|
||||
|
||||
/* Restoring. */
|
||||
#if 0
|
||||
MySource source;
|
||||
restorePath("outdir", source);
|
||||
cout << (string) hashPath("outdir") << endl;
|
||||
return;
|
||||
#endif
|
||||
|
||||
/* Set up the test environment. */
|
||||
|
||||
mkdir("scratch", 0777);
|
||||
mkdir("scratch/db", 0777);
|
||||
|
||||
string testDir = absPath("scratch");
|
||||
cout << testDir << endl;
|
||||
|
||||
nixStore = testDir;
|
||||
nixLogDir = testDir;
|
||||
nixDBPath = testDir + "/db";
|
||||
|
||||
openDB();
|
||||
initDB();
|
||||
|
||||
/* Expression evaluation. */
|
||||
|
||||
Path builder1fn;
|
||||
builder1fn = addToStore("./test-builder-1.sh");
|
||||
|
||||
ATerm fs1 = ATmake(
|
||||
"Closure([<str>], [(<str>, [])])",
|
||||
builder1fn.c_str(),
|
||||
builder1fn.c_str());
|
||||
Path fs1ne = writeTerm(fs1, "-c");
|
||||
|
||||
realise(fs1ne);
|
||||
realise(fs1ne);
|
||||
|
||||
string out1h = hashString("foo"); /* !!! bad */
|
||||
Path out1fn = nixStore + "/" + (string) out1h + "-hello.txt";
|
||||
ATerm fs3 = ATmake(
|
||||
"Derive([<str>], [<str>], <str>, <str>, [], [(\"out\", <str>)])",
|
||||
out1fn.c_str(),
|
||||
fs1ne.c_str(),
|
||||
thisSystem.c_str(),
|
||||
builder1fn.c_str(),
|
||||
out1fn.c_str());
|
||||
debug(printTerm(fs3));
|
||||
Path fs3ne = writeTerm(fs3, "-d");
|
||||
|
||||
realise(fs3ne);
|
||||
realise(fs3ne);
|
||||
|
||||
|
||||
Path builder4fn = addToStore("./test-builder-2.sh");
|
||||
|
||||
ATerm fs4 = ATmake(
|
||||
"Closure([<str>], [(<str>, [])])",
|
||||
builder4fn.c_str(),
|
||||
builder4fn.c_str());
|
||||
Path fs4ne = writeTerm(fs4, "-c");
|
||||
|
||||
realise(fs4ne);
|
||||
|
||||
string out5h = hashString("bar"); /* !!! bad */
|
||||
Path out5fn = nixStore + "/" + (string) out5h + "-hello2";
|
||||
ATerm fs5 = ATmake(
|
||||
"Derive([<str>], [<str>], <str>, <str>, [], [(\"out\", <str>), (\"builder\", <str>)])",
|
||||
out5fn.c_str(),
|
||||
fs4ne.c_str(),
|
||||
thisSystem.c_str(),
|
||||
builder4fn.c_str(),
|
||||
out5fn.c_str(),
|
||||
builder4fn.c_str());
|
||||
debug(printTerm(fs5));
|
||||
Path fs5ne = writeTerm(fs5, "-d");
|
||||
|
||||
realise(fs5ne);
|
||||
realise(fs5ne);
|
||||
}
|
||||
|
||||
|
||||
void run(Strings args)
|
||||
{
|
||||
runTests();
|
||||
}
|
||||
|
||||
|
||||
string programId = "test";
|
Loading…
Add table
Add a link
Reference in a new issue