about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--configure.ac6
-rw-r--r--src/Makefile.am8
-rw-r--r--src/eval.cc6
-rw-r--r--src/nix.cc744
-rw-r--r--src/values.cc6
5 files changed, 117 insertions, 653 deletions
diff --git a/configure.ac b/configure.ac
index a25ea785c287..ff9b5beb72f3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -10,5 +10,11 @@ AC_CANONICAL_HOST
 AC_PROG_CC
 AC_PROG_CXX
 
+# Unix shell scripting should die a slow and painful death.
+AC_DEFINE_UNQUOTED(NIX_VALUES_DIR, "$(eval echo $prefix/values)", Nix values directory.)
+AC_DEFINE_UNQUOTED(NIX_STATE_DIR, "$(eval echo $localstatedir/nix)", Nix state directory.)
+AC_DEFINE_UNQUOTED(NIX_LOG_DIR, "$(eval echo $localstatedir/log/nix)", Nix log file directory.)
+
+AC_CONFIG_HEADER([config.h])
 AC_CONFIG_FILES([Makefile src/Makefile scripts/Makefile])
 AC_OUTPUT
diff --git a/src/Makefile.am b/src/Makefile.am
index 80d9e4af8dc0..8c8ba32711a7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,9 +1,9 @@
-bin_PROGRAMS = nix fix
+bin_PROGRAMS = nix # fix
 noinst_PROGRAMS = test
 
-AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall
+AM_CXXFLAGS = -DSYSTEM=\"@host@\" -Wall -I..
 
-nix_SOURCES = nix.cc db.cc util.cc hash.cc md5.c
+nix_SOURCES = nix.cc util.cc hash.cc md5.c eval.cc values.cc globals.cc db.cc
 nix_LDADD = -ldb_cxx-4 -lATerm
 
 fix_SOURCES = fix.cc util.cc hash.cc md5.c
@@ -20,4 +20,4 @@ install-data-local:
 #	$(INSTALL) -d $(localstatedir)/nix/prebuilts/exports
 	$(INSTALL) -d $(localstatedir)/log/nix
 	$(INSTALL) -d $(prefix)/values
-	$(bindir)/nix init
+	$(bindir)/nix --init
diff --git a/src/eval.cc b/src/eval.cc
index 3f8aa7b2326b..7cb13d9ccaae 100644
--- a/src/eval.cc
+++ b/src/eval.cc
@@ -279,7 +279,7 @@ Expr evalValue(Expr e)
         string fn = queryValuePath(evalHash(e2));
         ATerm e3 = ATreadFromNamedFile(fn.c_str());
         if (!e3) throw Error("reading aterm from " + fn);
-        return e3;
+        return evalValue(e3);
     }
 
     /* Execution primitive. */
@@ -298,8 +298,8 @@ Expr evalValue(Expr e)
         evalArgs(args, argsNF, env);
 
         Hash sourceHash = hashExpr(
-            ATmake("Exec(Str(<str>), Hash(<str>), [])",
-                buildPlatform.c_str(), ((string) prog).c_str()));
+            ATmake("Exec(Str(<str>), Hash(<str>), <term>)",
+                buildPlatform.c_str(), ((string) prog).c_str(), argsNF));
 
         /* Do we know a normal form for sourceHash? */
         Hash targetHash;
diff --git a/src/nix.cc b/src/nix.cc
index db9f187e20d8..fae3175ba4dc 100644
--- a/src/nix.cc
+++ b/src/nix.cc
@@ -1,715 +1,171 @@
 #include <iostream>
-#include <list>
-#include <vector>
-#include <set>
-#include <map>
-#include <algorithm>
-#include <cstdio>
-
-#include <errno.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-
-#include "util.hh"
-#include "hash.hh"
-#include "db.hh"
-#include "nix.hh"
-#include "eval.hh"
-
-using namespace std;
-
-
-void readPkgDescr(Hash hash,
-    Params & pkgImports, Params & fileImports, Params & arguments)
-{
-    string pkgfile;
-
-    pkgfile = getFile(hash);
-
-    ATerm term = ATreadFromNamedFile(pkgfile.c_str());
-    if (!term) throw Error("cannot read aterm " + pkgfile);
-
-    ATerm bindings;
-    if (!ATmatch(term, "Descr(<term>)", &bindings))
-        throw Error("invalid term in " + pkgfile);
-
-    char * cname;
-    ATerm value;
-    while (ATmatch(bindings, "[Bind(<str>, <term>), <list>]", 
-               &cname, &value, &bindings))
-    {
-        string name(cname);
-        char * arg;
-        if (ATmatch(value, "Pkg(<str>)", &arg)) {
-            parseHash(arg);
-            pkgImports[name] = arg;
-        } else if (ATmatch(value, "File(<str>)", &arg)) {
-            parseHash(arg);
-            fileImports[name] = arg;
-        } else if (ATmatch(value, "Str(<str>)", &arg))
-            arguments[name] = arg;
-        else if (ATmatch(value, "Bool(True)"))
-            arguments[name] = "1";
-        else if (ATmatch(value, "Bool(False)"))
-            arguments[name] = "";
-        else {
-            ATprintf("%t\n", value);
-            throw Error("invalid binding in " + pkgfile);
-        }
-    }
-}
-
-
-string getPkg(Hash hash);
-
-
-void fetchDeps(Hash hash, Environment & env)
-{
-    /* Read the package description file. */
-    Params pkgImports, fileImports, arguments;
-    readPkgDescr(hash, pkgImports, fileImports, arguments);
-
-    /* Recursively fetch all the dependencies, filling in the
-       environment as we go along. */
-    for (Params::iterator it = pkgImports.begin();
-         it != pkgImports.end(); it++)
-    {
-        cerr << "fetching package dependency "
-             << it->first << " <- " << it->second
-             << endl;
-        env[it->first] = getPkg(parseHash(it->second));
-    }
-
-    for (Params::iterator it = fileImports.begin();
-         it != fileImports.end(); it++)
-    {
-        cerr << "fetching file dependency "
-             << it->first << " = " << it->second
-             << endl;
-
-        string file;
-
-        file = getFile(parseHash(it->second));
-
-        env[it->first] = file;
-    }
-
-    string buildSystem;
-
-    for (Params::iterator it = arguments.begin();
-         it != arguments.end(); it++)
-    {
-        env[it->first] = it->second;
-        if (it->first == "system")
-            buildSystem = it->second;
-    }
-
-    if (buildSystem != thisSystem)
-        throw Error("descriptor requires a `" + buildSystem +
-            "' but I am a `" + thisSystem + "'");
-}
 
+#include "config.h"
 
-string getFromEnv(const Environment & env, const string & key)
-{
-    Environment::const_iterator it = env.find(key);
-    if (it == env.end())
-        throw Error("key " + key + " not found in the environment");
-    return it->second;
-}
+#include "globals.hh"
+#include "values.hh"
+#include "eval.hh"
 
 
-string queryPkgId(Hash hash)
-{
-    Params pkgImports, fileImports, arguments;
-    readPkgDescr(hash, pkgImports, fileImports, arguments);
-    return getFromEnv(arguments, "id");
-}
+typedef void (* Operation) (Strings opFlags, Strings opArgs);
 
 
-void installPkg(Hash hash)
+/* Parse a supposed value argument.  This can be a hash (the simple
+   case), a symbolic name (in which case we do a lookup to obtain the
+   hash), or a file name (which we import to obtain the hash).  Note
+   that in order to disambiguate between symbolic names and file
+   names, a file name should contain at least one `/'. */
+Hash parseValueArg(string s)
 {
-    string pkgfile;
-    string src;
-    string path;
-    string cmd;
-    string builder;
-    Environment env;
-
-    /* Fetch dependencies. */
-    fetchDeps(hash, env);
-
-    builder = getFromEnv(env, "build");
-
-    string id = getFromEnv(env, "id");
-
-    /* Construct a path for the installed package. */
-    path = nixHomeDir + "/pkg/" + id + "-" + (string) hash;
-
-    /* Create the path. */
-    if (mkdir(path.c_str(), 0777))
-        throw Error("unable to create directory " + path);
-
-    /* Create a log file. */
-    string logFileName = 
-        nixLogDir + "/" + id + "-" + (string) hash + ".log";
-    /* !!! auto-pclose on exit */
-    FILE * logFile = popen(("tee " + logFileName + " >&2").c_str(), "w"); /* !!! escaping */
-    if (!logFile)
-        throw Error("unable to create log file " + logFileName);
-
     try {
+        return parseHash(s);
+    } catch (BadRefError e) { };
 
-        /* Fork a child to build the package. */
-        pid_t pid;
-        switch (pid = fork()) {
-            
-        case -1:
-            throw Error("unable to fork");
-
-        case 0: 
-
-            try { /* child */
-
-                /* Go to the build directory. */
-                if (chdir(path.c_str())) {
-                    cerr << "unable to chdir to package directory\n";
-                    _exit(1);
-                }
-
-                /* Try to use a prebuilt. */
-                string prebuiltHashS, prebuiltFile;
-                if (queryDB(nixDB, dbPrebuilts, hash, prebuiltHashS)) {
-
-                    try {
-                        prebuiltFile = getFile(parseHash(prebuiltHashS));
-                    } catch (Error e) {
-                        cerr << "cannot obtain prebuilt (ignoring): " << e.what() << endl;
-                        goto build;
-                    }
-                
-                    cerr << "substituting prebuilt " << prebuiltFile << endl;
-
-                    int res = system(("tar xfj " + prebuiltFile + " 1>&2").c_str()); // !!! escaping
-                    if (WEXITSTATUS(res) != 0)
-                        /* This is a fatal error, because path may now
-                           have clobbered. */
-                        throw Error("cannot unpack " + prebuiltFile);
-
-                    _exit(0);
-                }
-
-            build:
-
-                /* Fill in the environment.  We don't bother freeing
-                   the strings, since we'll exec or die soon
-                   anyway. */
-                const char * env2[env.size() + 1];
-                int i = 0;
-                for (Environment::iterator it = env.begin();
-                     it != env.end(); it++, i++)
-                    env2[i] = (new string(it->first + "=" + it->second))->c_str();
-                env2[i] = 0;
-
-                /* Dup the log handle into stderr. */
-                if (dup2(fileno(logFile), STDERR_FILENO) == -1)
-                    throw Error("cannot pipe standard error into log file: " + string(strerror(errno)));
-            
-                /* Dup stderr to stdin. */
-                if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
-                    throw Error("cannot dup stderr into stdout");
-
-                /* Execute the builder.  This should not return. */
-                execle(builder.c_str(), builder.c_str(), 0, env2);
-
-                throw Error("unable to execute builder: " +
-                    string(strerror(errno)));
-            
-            } catch (exception & e) {
-                cerr << "build error: " << e.what() << endl;
-                _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)
-            throw Error("unable to build package");
-
-        /* Remove write permission from the build directory. */
-        int res = system(("chmod -R -w " + path).c_str()); // !!! escaping
-        if (WEXITSTATUS(res) != 0)
-            throw Error("cannot remove write permission from " + path);
-
-    } catch (exception &) {
-//         system(("rm -rf " + path).c_str());
-        throw;
+    if (s.find('/') != string::npos) {
+        return addValue(s);
+    } else {
+        throw Error("not implemented");
     }
-
-    setDB(nixDB, dbInstPkgs, hash, path);
 }
 
 
-string getPkg(Hash hash)
+/* Evaluate values. */
+static void opEvaluate(Strings opFlags, Strings opArgs)
 {
-    string path;
-    while (!queryDB(nixDB, dbInstPkgs, hash, path))
-        installPkg(hash);
-    return path;
-}
+    if (!opFlags.empty()) throw UsageError("unknown flag");
 
-
-void runPkg(Hash hash, 
-    Strings::iterator firstArg, 
-    Strings::iterator lastArg)
-{
-    string src;
-    string path;
-    string cmd;
-    string runner;
-    Environment env;
-
-    /* Fetch dependencies. */
-    fetchDeps(hash, env);
-
-    runner = getFromEnv(env, "run");
-    
-    /* Fill in the environment.  We don't bother freeing the
-       strings, since we'll exec or die soon anyway. */
-    for (Environment::iterator it = env.begin();
-         it != env.end(); it++)
+    for (Strings::iterator it = opArgs.begin();
+         it != opArgs.end(); it++)
     {
-        string * s = new string(it->first + "=" + it->second);
-        putenv((char *) s->c_str());
-    }
-
-    /* Create the list of arguments. */
-    const char * args2[env.size() + 2];
-    int i = 0;
-    args2[i++] = runner.c_str();
-    for (Strings::const_iterator it = firstArg; it != lastArg; it++, i++)
-        args2[i] = it->c_str();
-    args2[i] = 0;
-
-    /* Execute the runner.  This should not return. */
-    execv(runner.c_str(), (char * *) args2);
-
-    cerr << strerror(errno) << endl;
-    throw Error("unable to execute runner");
-}
-
-
-void ensurePkg(Hash hash)
-{
-    Params pkgImports, fileImports, arguments;
-    readPkgDescr(hash, pkgImports, fileImports, arguments);
-
-    if (fileImports.find("build") != fileImports.end())
-        getPkg(hash);
-    else if (fileImports.find("run") != fileImports.end()) {
-        Environment env;
-        fetchDeps(hash, env);
-    } else throw Error("invalid descriptor");
-}
-
-
-void delPkg(Hash hash)
-{
-    string path;
-    if (queryDB(nixDB, dbInstPkgs, hash, path)) {
-        int res = system(("chmod -R +w " + path + " && rm -rf " + path).c_str()); // !!! escaping
-        delDB(nixDB, dbInstPkgs, hash); // not a bug ??? 
-        if (WEXITSTATUS(res) != 0)
-            cerr << "errors deleting " + path + ", ignoring" << endl;
+        Hash hash = parseValueArg(*it);
+        Expr e = ATmake("Deref(Hash(<str>))", ((string) hash).c_str());
+        cerr << printExpr(evalValue(e)) << endl;
     }
 }
 
 
-void exportPkgs(string outDir, 
-    Strings::iterator firstHash, 
-    Strings::iterator lastHash)
+static void opDelete(Strings opFlags, Strings opArgs)
 {
-    outDir = absPath(outDir);
-
-    for (Strings::iterator it = firstHash; it != lastHash; it++) {
-        Hash hash = parseHash(*it);
-        string pkgDir = getPkg(hash);
-        string tmpFile = outDir + "/export_tmp";
-
-        string cmd = "cd " + pkgDir + " && tar cfj " + tmpFile + " .";
-        int res = system(cmd.c_str()); // !!! escaping
-        if (!WIFEXITED(res) || WEXITSTATUS(res) != 0)
-            throw Error("cannot tar " + pkgDir);
-
-        string prebuiltHash = hashFile(tmpFile);
-        string pkgId = queryPkgId(hash);
-        string prebuiltFile = outDir + "/" +
-            pkgId + "-" + (string) hash + "-" + prebuiltHash + ".tar.bz2";
-        
-        rename(tmpFile.c_str(), prebuiltFile.c_str());
-    }
+    cerr << "delete!\n";
 }
 
 
-void registerPrebuilt(Hash pkgHash, Hash prebuiltHash)
+/* Add values to the Nix values directory and print the hashes of
+   those values. */
+static void opAdd(Strings opFlags, Strings opArgs)
 {
-    setDB(nixDB, dbPrebuilts, pkgHash, prebuiltHash);
-}
-
+    if (!opFlags.empty()) throw UsageError("unknown flag");
 
-Hash registerFile(string filename)
-{
-    filename = absPath(filename);
-    Hash hash = hashFile(filename);
-    setDB(nixDB, dbRefs, hash, filename);
-    return hash;
+    for (Strings::iterator it = opArgs.begin();
+         it != opArgs.end(); it++)
+        cout << (string) addValue(*it) << endl;
 }
 
 
-void registerURL(Hash hash, string url)
+/* Initialise the Nix databases. */
+static void opInit(Strings opFlags, Strings opArgs)
 {
-    setDB(nixDB, dbNetSources, hash, url);
-    /* !!! currently we allow only one network source per hash */
-}
-
-
-/* This is primarily used for bootstrapping. */
-void registerInstalledPkg(Hash hash, string path)
-{
-    if (path == "")
-        delDB(nixDB, dbInstPkgs, hash);
-    else
-        setDB(nixDB, dbInstPkgs, hash, path);
-}
-
-
-void verifyDB()
-{
-    /* Check that all file references are still valid. */
-    DBPairs fileRefs;
-    
-    enumDB(nixDB, dbRefs, fileRefs);
-
-    for (DBPairs::iterator it = fileRefs.begin();
-         it != fileRefs.end(); it++)
-    {
-        try {
-            Hash hash = parseHash(it->first);
-            if (hashFile(it->second) != hash) {
-                cerr << "file " << it->second << " has changed\n";
-                delDB(nixDB, dbRefs, it->first);
-            }
-        } catch (Error e) { /* !!! better error check */ 
-            cerr << "error: " << e.what() << endl;
-            delDB(nixDB, dbRefs, it->first);
-        }
-    }
-
-    /* Check that all installed packages are still there. */
-    DBPairs instPkgs;
-
-    enumDB(nixDB, dbInstPkgs, instPkgs);
-
-    for (DBPairs::iterator it = instPkgs.begin();
-         it != instPkgs.end(); it++)
-    {
-        struct stat st;
-        if (stat(it->second.c_str(), &st) == -1) {
-            cerr << "package " << it->first << " has disappeared\n";
-            delDB(nixDB, dbInstPkgs, it->first);
-        }
-    }
-
-    /* TODO: check that all directories in pkgHome are installed
-       packages. */
+    if (!opFlags.empty()) throw UsageError("unknown flag");
+    if (!opArgs.empty())
+        throw UsageError("--init does not have arguments");
+    initDB();
 }
 
 
-void listInstalledPkgs()
-{
-    DBPairs instPkgs;
+/* Nix syntax:
 
-    enumDB(nixDB, dbInstPkgs, instPkgs);
+   nix [OPTIONS...] [ARGUMENTS...]
 
-    for (DBPairs::iterator it = instPkgs.begin();
-         it != instPkgs.end(); it++)
-        cout << it->first << endl;
-}
+   Operations:
 
+     --evaluate / -e: evaluate values
+     --delete / -d: delete values
+     --query / -q: query stored values
+     --add: add values
+     --verify: verify Nix structures
+     --dump: dump a value
+     --init: initialise the Nix database
+     --version: output version information
+     --help: display help
 
-void printInfo(Strings::iterator first, Strings::iterator last)
-{
-    for (Strings::iterator it = first; it != last; it++) {
-        try {
-            cout << *it << " " << queryPkgId(parseHash(*it)) << endl;
-        } catch (Error & e) { // !!! more specific
-            cout << *it << " (descriptor missing)\n";
-        }
-    }
-}
+   Operations that work on values accept the hash code of a value, the
+   symbolic name of a value, or a file name of a external value that
+   will be added prior to the operation.
 
+   Query suboptions:
 
-void computeClosure(Strings::iterator first, Strings::iterator last, 
-    set<string> & result)
-{
-    list<string> workList(first, last);
-    set<string> doneSet;
-
-    while (!workList.empty()) {
-        Hash hash = parseHash(workList.front());
-        workList.pop_front();
-        
-        if (doneSet.find(hash) == doneSet.end()) {
-            doneSet.insert(hash);
-    
-            Params pkgImports, fileImports, arguments;
-            readPkgDescr(hash, pkgImports, fileImports, arguments);
-
-            for (Params::iterator it = pkgImports.begin();
-                 it != pkgImports.end(); it++)
-                workList.push_back(it->second);
-        }
-    }
-
-    result = doneSet;
-}
+     Selection:
 
+     --all / -a: query all stored values, otherwise given values
 
-void printClosure(Strings::iterator first, Strings::iterator last)
-{
-    set<string> allHashes;
-    computeClosure(first, last, allHashes);
-    for (set<string>::iterator it = allHashes.begin();
-         it != allHashes.end(); it++)
-        cout << *it << endl;
-}
+     Information:
 
+     --info / -i: general value information
 
-string dotQuote(const string & s)
-{
-    return "\"" + s + "\"";
-}
+   Options:
 
+     --verbose / -v: verbose operation
+*/
 
-void printGraph(Strings::iterator first, Strings::iterator last)
+/* Initialize, process arguments, and dispatch to the right
+   operation. */
+void run(Strings::iterator argCur, Strings::iterator argEnd)
 {
-    set<string> allHashes;
-    computeClosure(first, last, allHashes);
+    Strings opFlags, opArgs;
+    Operation op = 0;
 
-    cout << "digraph G {\n";
+    /* Setup Nix paths. */
+    nixValues = NIX_VALUES_DIR;
+    nixLogDir = NIX_LOG_DIR;
+    nixDB = (string) NIX_STATE_DIR + "/nixstate.db";
 
-    for (set<string>::iterator it = allHashes.begin();
-         it != allHashes.end(); it++)
-    {
-        Params pkgImports, fileImports, arguments;
-        readPkgDescr(parseHash(*it), pkgImports, fileImports, arguments);
+    /* Scan the arguments; find the operation, set global flags, put
+       all other flags in a list, and put all other arguments in
+       another list. */
 
-        cout << dotQuote(*it) << "[label = \"" 
-             << getFromEnv(arguments, "id")
-             << "\"];\n";
+    while (argCur != argEnd) {
+        string arg = *argCur++;
 
-        for (Params::iterator it2 = pkgImports.begin();
-             it2 != pkgImports.end(); it2++)
-            cout << dotQuote(it2->second) << " -> " 
-                 << dotQuote(*it) << ";\n";
-    }
+        Operation oldOp = op;
 
-    cout << "}\n";
-}
-
-
-void fetch(string id)
-{
-    string fn;
+        if (arg == "--evaluate" || arg == "-e")
+            op = opEvaluate;
+        else if (arg == "--delete" || arg == "-d")
+            op = opDelete;
+        else if (arg == "--add")
+            op = opAdd;
+        else if (arg == "--init")
+            op = opInit;
+        else if (arg[0] == '-')
+            opFlags.push_back(arg);
+        else
+            opArgs.push_back(arg);
 
-    /* Fetch the object referenced by id. */
-    if (isHash(id)) {
-        throw Error("not implemented");
-    } else {
-        fn = fetchURL(id);
+        if (oldOp && oldOp != op)
+            throw UsageError("only one operation may be specified");
     }
 
-    /* Register it by hash. */
-    Hash hash = registerFile(fn);
-    cout << (string) hash << endl;
-}
-
-
-void fetch(Strings::iterator first, Strings::iterator last)
-{
-    for (Strings::iterator it = first; it != last; it++)
-        fetch(*it);
-}
-
+    if (!op) throw UsageError("no operation specified");
 
-void printUsage()
-{
-    cerr <<
-"Usage: nix SUBCOMMAND OPTIONS...\n\
-\n\
-Subcommands:\n\
-\n\
-  init\n\
-    Initialize the database.\n\
-\n\
-  verify\n\
-    Remove stale entries from the database.\n\
-\n\
-  regfile FILENAME...\n\
-    Register each FILENAME keyed by its hash.\n\
-\n\
-  reginst HASH PATH\n\
-    Register an installed package.\n\
-\n\
-  getpkg HASH...\n\
-    For each HASH, ensure that the package referenced by HASH is\n\
-    installed. Print out the path of the installation on stdout.\n\
-\n\
-  delpkg HASH...\n\
-    Uninstall the package referenced by each HASH, disregarding any\n\
-    dependencies that other packages may have on HASH.\n\
-\n\
-  listinst\n\
-    Prints a list of installed packages.\n\
-\n\
-  run HASH ARGS...\n\
-    Run the descriptor referenced by HASH with the given arguments.\n\
-\n\
-  ensure HASH...\n\
-    Like getpkg, but if HASH refers to a run descriptor, fetch only\n\
-    the dependencies.\n\
-\n\
-  export DIR HASH...\n\
-    Export installed packages to DIR.\n\
-\n\
-  regprebuilt HASH1 HASH2\n\
-    Inform Nix that an export HASH2 can be used to fast-build HASH1.\n\
-\n\
-  info HASH...\n\
-    Print information about the specified descriptors.\n\
-\n\
-  closure HASH...\n\
-    Determine the closure of the set of descriptors under the import\n\
-    relation, starting at the given roots.\n\
-\n\
-  graph HASH...\n\
-    Like closure, but print a dot graph specification.\n\
-\n\
-  fetch ID...\n\
-    Fetch the objects identified by ID and place them in the Nix\n\
-    sources directory.  ID can be a hash or URL.  Print out the hash\n\
-    of the object.\n\
-";
-}
-
-
-void run(Strings::iterator argCur, Strings::iterator argEnd)
-{
-    umask(0022);
-
-    char * homeDir = getenv(nixHomeDirEnvVar.c_str());
-    if (homeDir) nixHomeDir = homeDir;
-
-    nixSourcesDir = nixHomeDir + "/var/nix/sources";
-    nixLogDir = nixHomeDir + "/var/log/nix";
-    nixDB = nixHomeDir + "/var/nix/pkginfo.db";
-
-    /* Parse the global flags. */
-    for ( ; argCur != argEnd; argCur++) {
-        string arg(*argCur);
-        if (arg == "-h" || arg == "--help") {
-            printUsage();
-            return;
-        } else if (arg[0] == '-') {
-            throw UsageError("invalid option `" + arg + "'");
-        } else break;
-    }
-
-    UsageError argcError("wrong number of arguments");
-
-    /* Parse the command. */
-    if (argCur == argEnd) throw UsageError("no command specified");
-    string cmd = *argCur++;
-    int argc = argEnd - argCur;
-
-    if (cmd == "init") {
-        if (argc != 0) throw argcError;
-        initDB();
-    } else if (cmd == "verify") {
-        if (argc != 0) throw argcError;
-        verifyDB();
-    } else if (cmd == "getpkg") {
-        for (Strings::iterator it = argCur; it != argEnd; it++) {
-            string path = getPkg(parseHash(*it));
-            cout << path << endl;
-        }
-    } else if (cmd == "delpkg") {
-        for (Strings::iterator it = argCur; it != argEnd; it++)
-            delPkg(parseHash(*it));
-    } else if (cmd == "run") {
-        if (argc < 1) throw argcError;
-        runPkg(parseHash(*argCur), argCur + 1, argEnd);
-    } else if (cmd == "ensure") {
-        for (Strings::iterator it = argCur; it != argEnd; it++)
-            ensurePkg(parseHash(*it));
-    } else if (cmd == "export") {
-        if (argc < 1) throw argcError;
-        exportPkgs(*argCur, argCur + 1, argEnd);
-    } else if (cmd == "regprebuilt") {
-        if (argc != 2) throw argcError;
-        registerPrebuilt(parseHash(argCur[0]), parseHash(argCur[1]));
-    } else if (cmd == "regfile") {
-        for_each(argCur, argEnd, registerFile);
-    } else if (cmd == "regurl") {
-        registerURL(parseHash(argCur[0]), argCur[1]);
-    } else if (cmd == "reginst") {
-        if (argc != 2) throw argcError;
-        registerInstalledPkg(parseHash(argCur[0]), argCur[1]);
-    } else if (cmd == "listinst") {
-        if (argc != 0) throw argcError;
-        listInstalledPkgs();
-    } else if (cmd == "info") {
-        printInfo(argCur, argEnd);
-    } else if (cmd == "closure") {
-        printClosure(argCur, argEnd);
-    } else if (cmd == "graph") {
-        printGraph(argCur, argEnd);
-    } else if (cmd == "fetch") {
-        fetch(argCur, argEnd);
-    } else
-        throw UsageError("unknown command: " + string(cmd));
+    op(opFlags, opArgs);
 }
 
 
 int main(int argc, char * * argv)
 {
+    /* ATerm setup. */
     ATerm bottomOfStack;
     ATinit(argc, argv, &bottomOfStack);
 
-    /* Put the arguments in a vector. */
-    Strings args;
-    while (argc--) args.push_back(*argv++);
-    Strings::iterator argCur = args.begin(), argEnd = args.end();
+    try {
 
-    argCur++;
+        Strings args;
+        while (argc--) args.push_back(*argv++);
+        run(args.begin() + 1, args.end());
 
-    try {
-        run(argCur, argEnd);
     } catch (UsageError & e) {
         cerr << "error: " << e.what() << endl
-             << "Try `nix -h' for more information.\n";
+             << "Try `nix --help' for more information.\n";
         return 1;
     } catch (exception & e) {
         cerr << "error: " << e.what() << endl;
diff --git a/src/values.cc b/src/values.cc
index 77a6f928e1a2..7cef5d3def2b 100644
--- a/src/values.cc
+++ b/src/values.cc
@@ -19,6 +19,8 @@ static string absValuePath(string s)
 
 Hash addValue(string path)
 {
+    path = absPath(path);
+
     Hash hash = hashPath(path);
 
     string name;
@@ -79,7 +81,7 @@ string queryValuePath(Hash hash)
             return fn;
         }
 
-        throw Error("a file with hash " + (string) hash + " is requested, "
+        throw Error("a file with hash " + (string) hash + " is required, "
             "but it is not known to exist locally or on the network");
 #if 0
         if (checkedNet)
@@ -87,7 +89,7 @@ string queryValuePath(Hash hash)
                 " should have hash " + (string) hash + ", but it doesn't");
 
         if (!queryDB(nixDB, dbNetSources, hash, url))
-            throw Error("a file with hash " + (string) hash + " is requested, "
+            throw Error("a file with hash " + (string) hash + " is required, "
                 "but it is not known to exist locally or on the network");
 
         checkedNet = true;