about summary refs log tree commit diff
path: root/src/nix-env
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix-env')
-rw-r--r--src/nix-env/local.mk7
-rw-r--r--src/nix-env/nix-env.cc1439
-rw-r--r--src/nix-env/profiles.cc146
-rw-r--r--src/nix-env/profiles.hh55
-rw-r--r--src/nix-env/user-env.cc151
-rw-r--r--src/nix-env/user-env.hh13
6 files changed, 1811 insertions, 0 deletions
diff --git a/src/nix-env/local.mk b/src/nix-env/local.mk
new file mode 100644
index 000000000000..e80719cd76f7
--- /dev/null
+++ b/src/nix-env/local.mk
@@ -0,0 +1,7 @@
+programs += nix-env
+
+nix-env_DIR := $(d)
+
+nix-env_SOURCES := $(wildcard $(d)/*.cc)
+
+nix-env_LIBS = libexpr libmain libstore libutil libformat
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
new file mode 100644
index 000000000000..325c8b928fea
--- /dev/null
+++ b/src/nix-env/nix-env.cc
@@ -0,0 +1,1439 @@
+#include "profiles.hh"
+#include "names.hh"
+#include "globals.hh"
+#include "misc.hh"
+#include "shared.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "attr-path.hh"
+#include "common-opts.hh"
+#include "xml-writer.hh"
+#include "store-api.hh"
+#include "user-env.hh"
+#include "util.hh"
+#include "value-to-json.hh"
+
+#include <cerrno>
+#include <ctime>
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+
+using namespace nix;
+using std::cout;
+
+
+typedef enum {
+    srcNixExprDrvs,
+    srcNixExprs,
+    srcStorePaths,
+    srcProfile,
+    srcAttrPath,
+    srcUnknown
+} InstallSourceType;
+
+
+struct InstallSourceInfo
+{
+    InstallSourceType type;
+    Path nixExprPath; /* for srcNixExprDrvs, srcNixExprs */
+    Path profile; /* for srcProfile */
+    string systemFilter; /* for srcNixExprDrvs */
+    Bindings autoArgs;
+};
+
+
+struct Globals
+{
+    InstallSourceInfo instSource;
+    Path profile;
+    std::shared_ptr<EvalState> state;
+    bool dryRun;
+    bool preserveInstalled;
+    bool removeAll;
+    string forceName;
+    bool prebuiltOnly;
+};
+
+
+typedef void (* Operation) (Globals & globals,
+    Strings opFlags, Strings opArgs);
+
+
+static string needArg(Strings::iterator & i,
+    Strings & args, const string & arg)
+{
+    if (i == args.end()) throw UsageError(
+        format("‘%1%’ requires an argument") % arg);
+    return *i++;
+}
+
+
+static bool parseInstallSourceOptions(Globals & globals,
+    Strings::iterator & i, Strings & args, const string & arg)
+{
+    if (arg == "--from-expression" || arg == "-E")
+        globals.instSource.type = srcNixExprs;
+    else if (arg == "--from-profile") {
+        globals.instSource.type = srcProfile;
+        globals.instSource.profile = needArg(i, args, arg);
+    }
+    else if (arg == "--attr" || arg == "-A")
+        globals.instSource.type = srcAttrPath;
+    else return false;
+    return true;
+}
+
+
+static bool isNixExpr(const Path & path, struct stat & st)
+{
+    return S_ISREG(st.st_mode) || (S_ISDIR(st.st_mode) && pathExists(path + "/default.nix"));
+}
+
+
+static void getAllExprs(EvalState & state,
+    const Path & path, StringSet & attrs, Value & v)
+{
+    StringSet namesSorted;
+    for (auto & i : readDirectory(path)) namesSorted.insert(i.name);
+
+    for (auto & i : namesSorted) {
+        /* Ignore the manifest.nix used by profiles.  This is
+           necessary to prevent it from showing up in channels (which
+           are implemented using profiles). */
+        if (i == "manifest.nix") continue;
+
+        Path path2 = path + "/" + i;
+
+        struct stat st;
+        if (stat(path2.c_str(), &st) == -1)
+            continue; // ignore dangling symlinks in ~/.nix-defexpr
+
+        if (isNixExpr(path2, st) && (!S_ISREG(st.st_mode) || hasSuffix(path2, ".nix"))) {
+            /* Strip off the `.nix' filename suffix (if applicable),
+               otherwise the attribute cannot be selected with the
+               `-A' option.  Useful if you want to stick a Nix
+               expression directly in ~/.nix-defexpr. */
+            string attrName = i;
+            if (hasSuffix(attrName, ".nix"))
+                attrName = string(attrName, 0, attrName.size() - 4);
+            if (attrs.find(attrName) != attrs.end()) {
+                printMsg(lvlError, format("warning: name collision in input Nix expressions, skipping ‘%1%’") % path2);
+                continue;
+            }
+            attrs.insert(attrName);
+            /* Load the expression on demand. */
+            Value & vFun(*state.allocValue());
+            Value & vArg(*state.allocValue());
+            state.getBuiltin("import", vFun);
+            mkString(vArg, path2);
+            mkApp(*state.allocAttr(v, state.symbols.create(attrName)), vFun, vArg);
+        }
+        else if (S_ISDIR(st.st_mode))
+            /* `path2' is a directory (with no default.nix in it);
+               recurse into it. */
+            getAllExprs(state, path2, attrs, v);
+    }
+}
+
+
+static void loadSourceExpr(EvalState & state, const Path & path, Value & v)
+{
+    struct stat st;
+    if (stat(path.c_str(), &st) == -1)
+        throw SysError(format("getting information about ‘%1%’") % path);
+
+    if (isNixExpr(path, st)) {
+        state.evalFile(path, v);
+        return;
+    }
+
+    /* The path is a directory.  Put the Nix expressions in the
+       directory in a set, with the file name of each expression as
+       the attribute name.  Recurse into subdirectories (but keep the
+       set flat, not nested, to make it easier for a user to have a
+       ~/.nix-defexpr directory that includes some system-wide
+       directory). */
+    if (S_ISDIR(st.st_mode)) {
+        state.mkAttrs(v, 16);
+        state.mkList(*state.allocAttr(v, state.symbols.create("_combineChannels")), 0);
+        StringSet attrs;
+        getAllExprs(state, path, attrs, v);
+        v.attrs->sort();
+    }
+}
+
+
+static void loadDerivations(EvalState & state, Path nixExprPath,
+    string systemFilter, Bindings & autoArgs,
+    const string & pathPrefix, DrvInfos & elems)
+{
+    Value vRoot;
+    loadSourceExpr(state, nixExprPath, vRoot);
+
+    Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot));
+
+    getDerivations(state, v, pathPrefix, autoArgs, elems, true);
+
+    /* Filter out all derivations not applicable to the current
+       system. */
+    for (DrvInfos::iterator i = elems.begin(), j; i != elems.end(); i = j) {
+        j = i; j++;
+        if (systemFilter != "*" && i->system != systemFilter)
+            elems.erase(i);
+    }
+}
+
+
+static Path getHomeDir()
+{
+    Path homeDir(getEnv("HOME", ""));
+    if (homeDir == "") throw Error("HOME environment variable not set");
+    return homeDir;
+}
+
+
+static Path getDefNixExprPath()
+{
+    return getHomeDir() + "/.nix-defexpr";
+}
+
+
+static int getPriority(EvalState & state, DrvInfo & drv)
+{
+    return drv.queryMetaInt("priority", 0);
+}
+
+
+static int comparePriorities(EvalState & state, DrvInfo & drv1, DrvInfo & drv2)
+{
+    return getPriority(state, drv2) - getPriority(state, drv1);
+}
+
+
+// FIXME: this function is rather slow since it checks a single path
+// at a time.
+static bool isPrebuilt(EvalState & state, DrvInfo & elem)
+{
+    Path path = elem.queryOutPath();
+    if (store->isValidPath(path)) return true;
+    PathSet ps = store->querySubstitutablePaths(singleton<PathSet>(path));
+    return ps.find(path) != ps.end();
+}
+
+
+static void checkSelectorUse(DrvNames & selectors)
+{
+    /* Check that all selectors have been used. */
+    foreach (DrvNames::iterator, i, selectors)
+        if (i->hits == 0 && i->fullName != "*")
+            throw Error(format("selector ‘%1%’ matches no derivations") % i->fullName);
+}
+
+
+static DrvInfos filterBySelector(EvalState & state, const DrvInfos & allElems,
+    const Strings & args, bool newestOnly)
+{
+    DrvNames selectors = drvNamesFromArgs(args);
+    if (selectors.empty())
+        selectors.push_back(DrvName("*"));
+
+    DrvInfos elems;
+    set<unsigned int> done;
+
+    foreach (DrvNames::iterator, i, selectors) {
+        typedef list<std::pair<DrvInfo, unsigned int> > Matches;
+        Matches matches;
+        unsigned int n = 0;
+        for (DrvInfos::const_iterator j = allElems.begin();
+             j != allElems.end(); ++j, ++n)
+        {
+            DrvName drvName(j->name);
+            if (i->matches(drvName)) {
+                i->hits++;
+                matches.push_back(std::pair<DrvInfo, unsigned int>(*j, n));
+            }
+        }
+
+        /* If `newestOnly', if a selector matches multiple derivations
+           with the same name, pick the one matching the current
+           system.  If there are still multiple derivations, pick the
+           one with the highest priority.  If there are still multiple
+           derivations, pick the one with the highest version.
+           Finally, if there are still multiple derivations,
+           arbitrarily pick the first one. */
+        if (newestOnly) {
+
+            /* Map from package names to derivations. */
+            typedef map<string, std::pair<DrvInfo, unsigned int> > Newest;
+            Newest newest;
+            StringSet multiple;
+
+            for (Matches::iterator j = matches.begin(); j != matches.end(); ++j) {
+                DrvName drvName(j->first.name);
+                int d = 1;
+
+                Newest::iterator k = newest.find(drvName.name);
+
+                if (k != newest.end()) {
+                    d = j->first.system == k->second.first.system ? 0 :
+                        j->first.system == settings.thisSystem ? 1 :
+                        k->second.first.system == settings.thisSystem ? -1 : 0;
+                    if (d == 0)
+                        d = comparePriorities(state, j->first, k->second.first);
+                    if (d == 0)
+                        d = compareVersions(drvName.version, DrvName(k->second.first.name).version);
+                }
+
+                if (d > 0) {
+                    newest.erase(drvName.name);
+                    newest.insert(Newest::value_type(drvName.name, *j));
+                    multiple.erase(j->first.name);
+                } else if (d == 0) {
+                    multiple.insert(j->first.name);
+                }
+            }
+
+            matches.clear();
+            for (Newest::iterator j = newest.begin(); j != newest.end(); ++j) {
+                if (multiple.find(j->second.first.name) != multiple.end())
+                    printMsg(lvlInfo,
+                        format("warning: there are multiple derivations named ‘%1%’; using the first one")
+                        % j->second.first.name);
+                matches.push_back(j->second);
+            }
+        }
+
+        /* Insert only those elements in the final list that we
+           haven't inserted before. */
+        for (Matches::iterator j = matches.begin(); j != matches.end(); ++j)
+            if (done.find(j->second) == done.end()) {
+                done.insert(j->second);
+                elems.push_back(j->first);
+            }
+    }
+
+    checkSelectorUse(selectors);
+
+    return elems;
+}
+
+
+static bool isPath(const string & s)
+{
+    return s.find('/') != string::npos;
+}
+
+
+static void queryInstSources(EvalState & state,
+    InstallSourceInfo & instSource, const Strings & args,
+    DrvInfos & elems, bool newestOnly)
+{
+    InstallSourceType type = instSource.type;
+    if (type == srcUnknown && args.size() > 0 && isPath(args.front()))
+        type = srcStorePaths;
+
+    switch (type) {
+
+        /* Get the available user environment elements from the
+           derivations specified in a Nix expression, including only
+           those with names matching any of the names in `args'. */
+        case srcUnknown:
+        case srcNixExprDrvs: {
+
+            /* Load the derivations from the (default or specified)
+               Nix expression. */
+            DrvInfos allElems;
+            loadDerivations(state, instSource.nixExprPath,
+                instSource.systemFilter, instSource.autoArgs, "", allElems);
+
+            elems = filterBySelector(state, allElems, args, newestOnly);
+
+            break;
+        }
+
+        /* Get the available user environment elements from the Nix
+           expressions specified on the command line; these should be
+           functions that take the default Nix expression file as
+           argument, e.g., if the file is `./foo.nix', then the
+           argument `x: x.bar' is equivalent to `(x: x.bar)
+           (import ./foo.nix)' = `(import ./foo.nix).bar'. */
+        case srcNixExprs: {
+
+            Value vArg;
+            loadSourceExpr(state, instSource.nixExprPath, vArg);
+
+            foreach (Strings::const_iterator, i, args) {
+                Expr * eFun = state.parseExprFromString(*i, absPath("."));
+                Value vFun, vTmp;
+                state.eval(eFun, vFun);
+                mkApp(vTmp, vFun, vArg);
+                getDerivations(state, vTmp, "", instSource.autoArgs, elems, true);
+            }
+
+            break;
+        }
+
+        /* The available user environment elements are specified as a
+           list of store paths (which may or may not be
+           derivations). */
+        case srcStorePaths: {
+
+            foreach (Strings::const_iterator, i, args) {
+                Path path = followLinksToStorePath(*i);
+
+                string name = baseNameOf(path);
+                string::size_type dash = name.find('-');
+                if (dash != string::npos)
+                    name = string(name, dash + 1);
+
+                DrvInfo elem(state, name, "", "", 0);
+
+                if (isDerivation(path)) {
+                    elem.setDrvPath(path);
+                    elem.setOutPath(findOutput(derivationFromPath(*store, path), "out"));
+                    if (name.size() >= drvExtension.size() &&
+                        string(name, name.size() - drvExtension.size()) == drvExtension)
+                        name = string(name, 0, name.size() - drvExtension.size());
+                }
+                else elem.setOutPath(path);
+
+                elems.push_back(elem);
+            }
+
+            break;
+        }
+
+        /* Get the available user environment elements from another
+           user environment.  These are then filtered as in the
+           `srcNixExprDrvs' case. */
+        case srcProfile: {
+            elems = filterBySelector(state,
+                queryInstalled(state, instSource.profile),
+                args, newestOnly);
+            break;
+        }
+
+        case srcAttrPath: {
+            Value vRoot;
+            loadSourceExpr(state, instSource.nixExprPath, vRoot);
+            foreach (Strings::const_iterator, i, args) {
+                Value & v(*findAlongAttrPath(state, *i, instSource.autoArgs, vRoot));
+                getDerivations(state, v, "", instSource.autoArgs, elems, true);
+            }
+            break;
+        }
+    }
+}
+
+
+static void printMissing(EvalState & state, DrvInfos & elems)
+{
+    PathSet targets;
+    foreach (DrvInfos::iterator, i, elems) {
+        Path drvPath = i->queryDrvPath();
+        if (drvPath != "")
+            targets.insert(drvPath);
+        else
+            targets.insert(i->queryOutPath());
+    }
+
+    printMissing(*store, targets);
+}
+
+
+static bool keep(DrvInfo & drv)
+{
+    return drv.queryMetaBool("keep", false);
+}
+
+
+static void installDerivations(Globals & globals,
+    const Strings & args, const Path & profile)
+{
+    debug(format("installing derivations"));
+
+    /* Get the set of user environment elements to be installed. */
+    DrvInfos newElems, newElemsTmp;
+    queryInstSources(*globals.state, globals.instSource, args, newElemsTmp, true);
+
+    /* If --prebuilt-only is given, filter out source-only packages. */
+    foreach (DrvInfos::iterator, i, newElemsTmp)
+        if (!globals.prebuiltOnly || isPrebuilt(*globals.state, *i))
+            newElems.push_back(*i);
+
+    StringSet newNames;
+    for (DrvInfos::iterator i = newElems.begin(); i != newElems.end(); ++i) {
+        /* `forceName' is a hack to get package names right in some
+           one-click installs, namely those where the name used in the
+           path is not the one we want (e.g., `java-front' versus
+           `java-front-0.9pre15899'). */
+        if (globals.forceName != "")
+            i->name = globals.forceName;
+        newNames.insert(DrvName(i->name).name);
+    }
+
+
+    while (true) {
+        string lockToken = optimisticLockProfile(profile);
+
+        DrvInfos allElems(newElems);
+
+        /* Add in the already installed derivations, unless they have
+           the same name as a to-be-installed element. */
+        if (!globals.removeAll) {
+            DrvInfos installedElems = queryInstalled(*globals.state, profile);
+
+            foreach (DrvInfos::iterator, i, installedElems) {
+                DrvName drvName(i->name);
+                if (!globals.preserveInstalled &&
+                    newNames.find(drvName.name) != newNames.end() &&
+                    !keep(*i))
+                    printMsg(lvlInfo, format("replacing old ‘%1%’") % i->name);
+                else
+                    allElems.push_back(*i);
+            }
+
+            foreach (DrvInfos::iterator, i, newElems)
+                printMsg(lvlInfo, format("installing ‘%1%’") % i->name);
+        }
+
+        printMissing(*globals.state, newElems);
+
+        if (globals.dryRun) return;
+
+        if (createUserEnv(*globals.state, allElems,
+                profile, settings.envKeepDerivations, lockToken)) break;
+    }
+}
+
+
+static void opInstall(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
+        string arg = *i++;
+        if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
+        else if (arg == "--preserve-installed" || arg == "-P")
+            globals.preserveInstalled = true;
+        else if (arg == "--remove-all" || arg == "-r")
+            globals.removeAll = true;
+        else throw UsageError(format("unknown flag ‘%1%’") % arg);
+    }
+
+    installDerivations(globals, opArgs, globals.profile);
+}
+
+
+typedef enum { utLt, utLeq, utEq, utAlways } UpgradeType;
+
+
+static void upgradeDerivations(Globals & globals,
+    const Strings & args, UpgradeType upgradeType)
+{
+    debug(format("upgrading derivations"));
+
+    /* Upgrade works as follows: we take all currently installed
+       derivations, and for any derivation matching any selector, look
+       for a derivation in the input Nix expression that has the same
+       name and a higher version number. */
+
+    while (true) {
+        string lockToken = optimisticLockProfile(globals.profile);
+
+        DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
+
+        /* Fetch all derivations from the input file. */
+        DrvInfos availElems;
+        queryInstSources(*globals.state, globals.instSource, args, availElems, false);
+
+        /* Go through all installed derivations. */
+        DrvInfos newElems;
+        foreach (DrvInfos::iterator, i, installedElems) {
+            DrvName drvName(i->name);
+
+            try {
+
+                if (keep(*i)) {
+                    newElems.push_back(*i);
+                    continue;
+                }
+
+                /* Find the derivation in the input Nix expression
+                   with the same name that satisfies the version
+                   constraints specified by upgradeType.  If there are
+                   multiple matches, take the one with the highest
+                   priority.  If there are still multiple matches,
+                   take the one with the highest version. */
+                DrvInfos::iterator bestElem = availElems.end();
+                DrvName bestName;
+                foreach (DrvInfos::iterator, j, availElems) {
+                    DrvName newName(j->name);
+                    if (newName.name == drvName.name) {
+                        int d = comparePriorities(*globals.state, *i, *j);
+                        if (d == 0) d = compareVersions(drvName.version, newName.version);
+                        if ((upgradeType == utLt && d < 0) ||
+                            (upgradeType == utLeq && d <= 0) ||
+                            (upgradeType == utEq && d == 0) ||
+                            upgradeType == utAlways)
+                        {
+                            int d2 = -1;
+                            if (bestElem != availElems.end()) {
+                                d2 = comparePriorities(*globals.state, *bestElem, *j);
+                                if (d2 == 0) d2 = compareVersions(bestName.version, newName.version);
+                            }
+                            if (d2 < 0 && (!globals.prebuiltOnly || isPrebuilt(*globals.state, *j))) {
+                                bestElem = j;
+                                bestName = newName;
+                            }
+                        }
+                    }
+                }
+
+                if (bestElem != availElems.end() &&
+                    i->queryOutPath() !=
+                    bestElem->queryOutPath())
+                {
+                    printMsg(lvlInfo,
+                        format("upgrading ‘%1%’ to ‘%2%’")
+                        % i->name % bestElem->name);
+                    newElems.push_back(*bestElem);
+                } else newElems.push_back(*i);
+
+            } catch (Error & e) {
+                e.addPrefix(format("while trying to find an upgrade for ‘%1%’:\n") % i->name);
+                throw;
+            }
+        }
+
+        printMissing(*globals.state, newElems);
+
+        if (globals.dryRun) return;
+
+        if (createUserEnv(*globals.state, newElems,
+                globals.profile, settings.envKeepDerivations, lockToken)) break;
+    }
+}
+
+
+static void opUpgrade(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    UpgradeType upgradeType = utLt;
+    for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
+        string arg = *i++;
+        if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
+        else if (arg == "--lt") upgradeType = utLt;
+        else if (arg == "--leq") upgradeType = utLeq;
+        else if (arg == "--eq") upgradeType = utEq;
+        else if (arg == "--always") upgradeType = utAlways;
+        else throw UsageError(format("unknown flag ‘%1%’") % arg);
+    }
+
+    upgradeDerivations(globals, opArgs, upgradeType);
+}
+
+
+static void setMetaFlag(EvalState & state, DrvInfo & drv,
+    const string & name, const string & value)
+{
+    Value * v = state.allocValue();
+    mkString(*v, value.c_str());
+    drv.setMeta(name, v);
+}
+
+
+static void opSetFlag(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    if (opFlags.size() > 0)
+        throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
+    if (opArgs.size() < 2)
+        throw UsageError("not enough arguments to ‘--set-flag’");
+
+    Strings::iterator arg = opArgs.begin();
+    string flagName = *arg++;
+    string flagValue = *arg++;
+    DrvNames selectors = drvNamesFromArgs(Strings(arg, opArgs.end()));
+
+    while (true) {
+        string lockToken = optimisticLockProfile(globals.profile);
+
+        DrvInfos installedElems = queryInstalled(*globals.state, globals.profile);
+
+        /* Update all matching derivations. */
+        foreach (DrvInfos::iterator, i, installedElems) {
+            DrvName drvName(i->name);
+            foreach (DrvNames::iterator, j, selectors)
+                if (j->matches(drvName)) {
+                    printMsg(lvlInfo, format("setting flag on ‘%1%’") % i->name);
+                    j->hits++;
+                    setMetaFlag(*globals.state, *i, flagName, flagValue);
+                    break;
+                }
+        }
+
+        checkSelectorUse(selectors);
+
+        /* Write the new user environment. */
+        if (createUserEnv(*globals.state, installedElems,
+                globals.profile, settings.envKeepDerivations, lockToken)) break;
+    }
+}
+
+
+static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
+        string arg = *i++;
+        if (parseInstallSourceOptions(globals, i, opFlags, arg)) ;
+        else throw UsageError(format("unknown flag ‘%1%’") % arg);
+    }
+
+    DrvInfos elems;
+    queryInstSources(*globals.state, globals.instSource, opArgs, elems, true);
+
+    if (elems.size() != 1)
+        throw Error("--set requires exactly one derivation");
+
+    DrvInfo & drv(elems.front());
+
+    if (drv.queryDrvPath() != "") {
+        PathSet paths = singleton<PathSet>(drv.queryDrvPath());
+        printMissing(*store, paths);
+        if (globals.dryRun) return;
+        store->buildPaths(paths, globals.state->repair ? bmRepair : bmNormal);
+    }
+    else {
+        printMissing(*store, singleton<PathSet>(drv.queryOutPath()));
+        if (globals.dryRun) return;
+        store->ensurePath(drv.queryOutPath());
+    }
+
+    debug(format("switching to new user environment"));
+    Path generation = createGeneration(globals.profile, drv.queryOutPath());
+    switchLink(globals.profile, generation);
+}
+
+
+static void uninstallDerivations(Globals & globals, Strings & selectors,
+    Path & profile)
+{
+    while (true) {
+        string lockToken = optimisticLockProfile(profile);
+
+        DrvInfos installedElems = queryInstalled(*globals.state, profile);
+        DrvInfos newElems;
+
+        foreach (DrvInfos::iterator, i, installedElems) {
+            DrvName drvName(i->name);
+            bool found = false;
+            foreach (Strings::iterator, j, selectors)
+                /* !!! the repeated calls to followLinksToStorePath()
+                   are expensive, should pre-compute them. */
+                if ((isPath(*j) && i->queryOutPath() == followLinksToStorePath(*j))
+                    || DrvName(*j).matches(drvName))
+                {
+                    printMsg(lvlInfo, format("uninstalling ‘%1%’") % i->name);
+                    found = true;
+                    break;
+                }
+            if (!found) newElems.push_back(*i);
+        }
+
+        if (globals.dryRun) return;
+
+        if (createUserEnv(*globals.state, newElems,
+                profile, settings.envKeepDerivations, lockToken)) break;
+    }
+}
+
+
+static void opUninstall(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    if (opFlags.size() > 0)
+        throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
+    uninstallDerivations(globals, opArgs, globals.profile);
+}
+
+
+static bool cmpChars(char a, char b)
+{
+    return toupper(a) < toupper(b);
+}
+
+
+static bool cmpElemByName(const DrvInfo & a, const DrvInfo & b)
+{
+    return lexicographical_compare(
+        a.name.begin(), a.name.end(),
+        b.name.begin(), b.name.end(), cmpChars);
+}
+
+
+typedef list<Strings> Table;
+
+
+void printTable(Table & table)
+{
+    unsigned int nrColumns = table.size() > 0 ? table.front().size() : 0;
+
+    vector<unsigned int> widths;
+    widths.resize(nrColumns);
+
+    foreach (Table::iterator, i, table) {
+        assert(i->size() == nrColumns);
+        Strings::iterator j;
+        unsigned int column;
+        for (j = i->begin(), column = 0; j != i->end(); ++j, ++column)
+            if (j->size() > widths[column]) widths[column] = j->size();
+    }
+
+    foreach (Table::iterator, i, table) {
+        Strings::iterator j;
+        unsigned int column;
+        for (j = i->begin(), column = 0; j != i->end(); ++j, ++column) {
+            string s = *j;
+            replace(s.begin(), s.end(), '\n', ' ');
+            cout << s;
+            if (column < nrColumns - 1)
+                cout << string(widths[column] - s.size() + 2, ' ');
+        }
+        cout << std::endl;
+    }
+}
+
+
+/* This function compares the version of an element against the
+   versions in the given set of elements.  `cvLess' means that only
+   lower versions are in the set, `cvEqual' means that at most an
+   equal version is in the set, and `cvGreater' means that there is at
+   least one element with a higher version in the set.  `cvUnavail'
+   means that there are no elements with the same name in the set. */
+
+typedef enum { cvLess, cvEqual, cvGreater, cvUnavail } VersionDiff;
+
+static VersionDiff compareVersionAgainstSet(
+    const DrvInfo & elem, const DrvInfos & elems, string & version)
+{
+    DrvName name(elem.name);
+
+    VersionDiff diff = cvUnavail;
+    version = "?";
+
+    for (DrvInfos::const_iterator i = elems.begin(); i != elems.end(); ++i) {
+        DrvName name2(i->name);
+        if (name.name == name2.name) {
+            int d = compareVersions(name.version, name2.version);
+            if (d < 0) {
+                diff = cvGreater;
+                version = name2.version;
+            }
+            else if (diff != cvGreater && d == 0) {
+                diff = cvEqual;
+                version = name2.version;
+            }
+            else if (diff != cvGreater && diff != cvEqual && d > 0) {
+                diff = cvLess;
+                if (version == "" || compareVersions(version, name2.version) < 0)
+                    version = name2.version;
+            }
+        }
+    }
+
+    return diff;
+}
+
+
+static void queryJSON(Globals & globals, vector<DrvInfo> & elems)
+{
+    JSONObject topObj(cout);
+    foreach (vector<DrvInfo>::iterator, i, elems) {
+        topObj.attr(i->attrPath);
+        JSONObject pkgObj(cout);
+
+        pkgObj.attr("name", i->name);
+        pkgObj.attr("system", i->system);
+
+        pkgObj.attr("meta");
+        JSONObject metaObj(cout);
+        StringSet metaNames = i->queryMetaNames();
+        foreach (StringSet::iterator, j, metaNames) {
+            metaObj.attr(*j);
+            Value * v = i->queryMeta(*j);
+            if (!v) {
+                printMsg(lvlError, format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i->name % *j);
+                cout << "null";
+            } else {
+                PathSet context;
+                printValueAsJSON(*globals.state, true, *v, cout, context);
+            }
+        }
+    }
+}
+
+
+static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    Strings remaining;
+    string attrPath;
+
+    bool printStatus = false;
+    bool printName = true;
+    bool printAttrPath = false;
+    bool printSystem = false;
+    bool printDrvPath = false;
+    bool printOutPath = false;
+    bool printDescription = false;
+    bool printMeta = false;
+    bool compareVersions = false;
+    bool xmlOutput = false;
+    bool jsonOutput = false;
+
+    enum { sInstalled, sAvailable } source = sInstalled;
+
+    settings.readOnlyMode = true; /* makes evaluation a bit faster */
+
+    for (Strings::iterator i = opFlags.begin(); i != opFlags.end(); ) {
+        string arg = *i++;
+        if (arg == "--status" || arg == "-s") printStatus = true;
+        else if (arg == "--no-name") printName = false;
+        else if (arg == "--system") printSystem = true;
+        else if (arg == "--description") printDescription = true;
+        else if (arg == "--compare-versions" || arg == "-c") compareVersions = true;
+        else if (arg == "--drv-path") printDrvPath = true;
+        else if (arg == "--out-path") printOutPath = true;
+        else if (arg == "--meta") printMeta = true;
+        else if (arg == "--installed") source = sInstalled;
+        else if (arg == "--available" || arg == "-a") source = sAvailable;
+        else if (arg == "--xml") xmlOutput = true;
+        else if (arg == "--json") jsonOutput = true;
+        else if (arg == "--attr-path" || arg == "-P") printAttrPath = true;
+        else if (arg == "--attr" || arg == "-A")
+            attrPath = needArg(i, opFlags, arg);
+        else
+            throw UsageError(format("unknown flag ‘%1%’") % arg);
+    }
+
+
+    /* Obtain derivation information from the specified source. */
+    DrvInfos availElems, installedElems;
+
+    if (source == sInstalled || compareVersions || printStatus)
+        installedElems = queryInstalled(*globals.state, globals.profile);
+
+    if (source == sAvailable || compareVersions)
+        loadDerivations(*globals.state, globals.instSource.nixExprPath,
+            globals.instSource.systemFilter, globals.instSource.autoArgs,
+            attrPath, availElems);
+
+    DrvInfos elems_ = filterBySelector(*globals.state,
+        source == sInstalled ? installedElems : availElems,
+        opArgs, false);
+
+    DrvInfos & otherElems(source == sInstalled ? availElems : installedElems);
+
+
+    /* Sort them by name. */
+    /* !!! */
+    vector<DrvInfo> elems;
+    for (DrvInfos::iterator i = elems_.begin(); i != elems_.end(); ++i)
+        elems.push_back(*i);
+    sort(elems.begin(), elems.end(), cmpElemByName);
+
+
+    /* We only need to know the installed paths when we are querying
+       the status of the derivation. */
+    PathSet installed; /* installed paths */
+
+    if (printStatus) {
+        for (DrvInfos::iterator i = installedElems.begin();
+             i != installedElems.end(); ++i)
+            installed.insert(i->queryOutPath());
+    }
+
+
+    /* Query which paths have substitutes. */
+    PathSet validPaths, substitutablePaths;
+    if (printStatus || globals.prebuiltOnly) {
+        PathSet paths;
+        foreach (vector<DrvInfo>::iterator, i, elems)
+            try {
+                paths.insert(i->queryOutPath());
+            } catch (AssertionError & e) {
+                printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i->name);
+                i->setFailed();
+            }
+        validPaths = store->queryValidPaths(paths);
+        substitutablePaths = store->querySubstitutablePaths(paths);
+    }
+
+
+    /* Print the desired columns, or XML output. */
+    if (jsonOutput) {
+        queryJSON(globals, elems);
+        return;
+    }
+
+    Table table;
+    std::ostringstream dummy;
+    XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
+    XMLOpenElement xmlRoot(xml, "items");
+
+    foreach (vector<DrvInfo>::iterator, i, elems) {
+        try {
+            if (i->hasFailed()) continue;
+
+            startNest(nest, lvlDebug, format("outputting query result ‘%1%’") % i->attrPath);
+
+            if (globals.prebuiltOnly &&
+                validPaths.find(i->queryOutPath()) == validPaths.end() &&
+                substitutablePaths.find(i->queryOutPath()) == substitutablePaths.end())
+                continue;
+
+            /* For table output. */
+            Strings columns;
+
+            /* For XML output. */
+            XMLAttrs attrs;
+
+            if (printStatus) {
+                Path outPath = i->queryOutPath();
+                bool hasSubs = substitutablePaths.find(outPath) != substitutablePaths.end();
+                bool isInstalled = installed.find(outPath) != installed.end();
+                bool isValid = validPaths.find(outPath) != validPaths.end();
+                if (xmlOutput) {
+                    attrs["installed"] = isInstalled ? "1" : "0";
+                    attrs["valid"] = isValid ? "1" : "0";
+                    attrs["substitutable"] = hasSubs ? "1" : "0";
+                } else
+                    columns.push_back(
+                        (string) (isInstalled ? "I" : "-")
+                        + (isValid ? "P" : "-")
+                        + (hasSubs ? "S" : "-"));
+            }
+
+            if (xmlOutput)
+                attrs["attrPath"] = i->attrPath;
+            else if (printAttrPath)
+                columns.push_back(i->attrPath);
+
+            if (xmlOutput)
+                attrs["name"] = i->name;
+            else if (printName)
+                columns.push_back(i->name);
+
+            if (compareVersions) {
+                /* Compare this element against the versions of the
+                   same named packages in either the set of available
+                   elements, or the set of installed elements.  !!!
+                   This is O(N * M), should be O(N * lg M). */
+                string version;
+                VersionDiff diff = compareVersionAgainstSet(*i, otherElems, version);
+
+                char ch;
+                switch (diff) {
+                    case cvLess: ch = '>'; break;
+                    case cvEqual: ch = '='; break;
+                    case cvGreater: ch = '<'; break;
+                    case cvUnavail: ch = '-'; break;
+                    default: abort();
+                }
+
+                if (xmlOutput) {
+                    if (diff != cvUnavail) {
+                        attrs["versionDiff"] = ch;
+                        attrs["maxComparedVersion"] = version;
+                    }
+                } else {
+                    string column = (string) "" + ch + " " + version;
+                    if (diff == cvGreater && isatty(STDOUT_FILENO))
+                        column = ANSI_RED + column + ANSI_NORMAL;
+                    columns.push_back(column);
+                }
+            }
+
+            if (xmlOutput) {
+                if (i->system != "") attrs["system"] = i->system;
+            }
+            else if (printSystem)
+                columns.push_back(i->system);
+
+            if (printDrvPath) {
+                string drvPath = i->queryDrvPath();
+                if (xmlOutput) {
+                    if (drvPath != "") attrs["drvPath"] = drvPath;
+                } else
+                    columns.push_back(drvPath == "" ? "-" : drvPath);
+            }
+
+            if (printOutPath && !xmlOutput) {
+                DrvInfo::Outputs outputs = i->queryOutputs();
+                string s;
+                foreach (DrvInfo::Outputs::iterator, j, outputs) {
+                    if (!s.empty()) s += ';';
+                    if (j->first != "out") { s += j->first; s += "="; }
+                    s += j->second;
+                }
+                columns.push_back(s);
+            }
+
+            if (printDescription) {
+                string descr = i->queryMetaString("description");
+                if (xmlOutput) {
+                    if (descr != "") attrs["description"] = descr;
+                } else
+                    columns.push_back(descr);
+            }
+
+            if (xmlOutput) {
+                if (printOutPath || printMeta) {
+                    XMLOpenElement item(xml, "item", attrs);
+                    if (printOutPath) {
+                        DrvInfo::Outputs outputs = i->queryOutputs();
+                        foreach (DrvInfo::Outputs::iterator, j, outputs) {
+                            XMLAttrs attrs2;
+                            attrs2["name"] = j->first;
+                            attrs2["path"] = j->second;
+                            xml.writeEmptyElement("output", attrs2);
+                        }
+                    }
+                    if (printMeta) {
+                        StringSet metaNames = i->queryMetaNames();
+                        foreach (StringSet::iterator, j, metaNames) {
+                            XMLAttrs attrs2;
+                            attrs2["name"] = *j;
+                            Value * v = i->queryMeta(*j);
+                            if (!v)
+                                printMsg(lvlError, format("derivation ‘%1%’ has invalid meta attribute ‘%2%’") % i->name % *j);
+                            else {
+                                if (v->type == tString) {
+                                    attrs2["type"] = "string";
+                                    attrs2["value"] = v->string.s;
+                                    xml.writeEmptyElement("meta", attrs2);
+                                } else if (v->type == tInt) {
+                                    attrs2["type"] = "int";
+                                    attrs2["value"] = (format("%1%") % v->integer).str();
+                                    xml.writeEmptyElement("meta", attrs2);
+                                } else if (v->type == tBool) {
+                                    attrs2["type"] = "bool";
+                                    attrs2["value"] = v->boolean ? "true" : "false";
+                                    xml.writeEmptyElement("meta", attrs2);
+                                } else if (v->type == tList) {
+                                    attrs2["type"] = "strings";
+                                    XMLOpenElement m(xml, "meta", attrs2);
+                                    for (unsigned int j = 0; j < v->list.length; ++j) {
+                                        if (v->list.elems[j]->type != tString) continue;
+                                        XMLAttrs attrs3;
+                                        attrs3["value"] = v->list.elems[j]->string.s;
+                                        xml.writeEmptyElement("string", attrs3);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                } else
+                    xml.writeEmptyElement("item", attrs);
+            } else
+                table.push_back(columns);
+
+            cout.flush();
+
+        } catch (AssertionError & e) {
+            printMsg(lvlTalkative, format("skipping derivation named ‘%1%’ which gives an assertion failure") % i->name);
+        } catch (Error & e) {
+            e.addPrefix(format("while querying the derivation named ‘%1%’:\n") % i->name);
+            throw;
+        }
+    }
+
+    if (!xmlOutput) printTable(table);
+}
+
+
+static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    if (opFlags.size() > 0)
+        throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
+    if (opArgs.size() != 1)
+        throw UsageError(format("exactly one argument expected"));
+
+    Path profile = absPath(opArgs.front());
+    Path profileLink = getHomeDir() + "/.nix-profile";
+
+    switchLink(profileLink, profile);
+}
+
+
+static const int prevGen = -2;
+
+
+static void switchGeneration(Globals & globals, int dstGen)
+{
+    PathLocks lock;
+    lockProfile(lock, globals.profile);
+
+    int curGen;
+    Generations gens = findGenerations(globals.profile, curGen);
+
+    Generation dst;
+    for (Generations::iterator i = gens.begin(); i != gens.end(); ++i)
+        if ((dstGen == prevGen && i->number < curGen) ||
+            (dstGen >= 0 && i->number == dstGen))
+            dst = *i;
+
+    if (!dst) {
+        if (dstGen == prevGen)
+            throw Error(format("no generation older than the current (%1%) exists")
+                % curGen);
+        else
+            throw Error(format("generation %1% does not exist") % dstGen);
+    }
+
+    printMsg(lvlInfo, format("switching from generation %1% to %2%")
+        % curGen % dst.number);
+
+    if (globals.dryRun) return;
+
+    switchLink(globals.profile, dst.path);
+}
+
+
+static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    if (opFlags.size() > 0)
+        throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
+    if (opArgs.size() != 1)
+        throw UsageError(format("exactly one argument expected"));
+
+    int dstGen;
+    if (!string2Int(opArgs.front(), dstGen))
+        throw UsageError(format("expected a generation number"));
+
+    switchGeneration(globals, dstGen);
+}
+
+
+static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    if (opFlags.size() > 0)
+        throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
+    if (opArgs.size() != 0)
+        throw UsageError(format("no arguments expected"));
+
+    switchGeneration(globals, prevGen);
+}
+
+
+static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    if (opFlags.size() > 0)
+        throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
+    if (opArgs.size() != 0)
+        throw UsageError(format("no arguments expected"));
+
+    PathLocks lock;
+    lockProfile(lock, globals.profile);
+
+    int curGen;
+    Generations gens = findGenerations(globals.profile, curGen);
+
+    for (Generations::iterator i = gens.begin(); i != gens.end(); ++i) {
+        tm t;
+        if (!localtime_r(&i->creationTime, &t)) throw Error("cannot convert time");
+        cout << format("%|4|   %|4|-%|02|-%|02| %|02|:%|02|:%|02|   %||\n")
+            % i->number
+            % (t.tm_year + 1900) % (t.tm_mon + 1) % t.tm_mday
+            % t.tm_hour % t.tm_min % t.tm_sec
+            % (i->number == curGen ? "(current)" : "");
+    }
+}
+
+
+static void deleteGeneration2(Globals & globals, unsigned int gen)
+{
+    if (globals.dryRun)
+        printMsg(lvlInfo, format("would remove generation %1%") % gen);
+    else {
+        printMsg(lvlInfo, format("removing generation %1%") % gen);
+        deleteGeneration(globals.profile, gen);
+    }
+
+}
+
+
+static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
+{
+    if (opFlags.size() > 0)
+        throw UsageError(format("unknown flag ‘%1%’") % opFlags.front());
+
+    PathLocks lock;
+    lockProfile(lock, globals.profile);
+
+    int curGen;
+    Generations gens = findGenerations(globals.profile, curGen);
+
+    for (Strings::iterator i = opArgs.begin(); i != opArgs.end(); ++i) {
+
+        if (*i == "old") {
+            for (Generations::iterator j = gens.begin(); j != gens.end(); ++j)
+                if (j->number != curGen)
+                    deleteGeneration2(globals, j->number);
+        } else if (i->size() >= 2 && tolower(*i->rbegin()) == 'd') {
+            time_t curTime = time(NULL);
+            time_t oldTime;
+            string strDays = string(*i, 0, i->size() - 1);
+            int days;
+
+            if (!string2Int(strDays, days) || days < 1)
+                throw UsageError(format("invalid number of days specifier ‘%1%’") % *i);
+
+            oldTime = curTime - days * 24 * 3600;
+
+            bool canDelete = false;
+            for (Generations::reverse_iterator j = gens.rbegin(); j != gens.rend(); ++j) {
+                if (canDelete) {
+                    assert(j->creationTime < oldTime);
+                    deleteGeneration2(globals, j->number);
+                } else if (j->creationTime < oldTime) {
+                    /* We may now start deleting generations, but we don't delete
+                       this generation yet, because this generation was still the
+                       one that was active at the requested point in time. */
+                    canDelete = true;
+                }
+            }
+        } else {
+            int n;
+            if (!string2Int(*i, n) || n < 0)
+                throw UsageError(format("invalid generation specifier ‘%1%’")  % *i);
+            bool found = false;
+            for (Generations::iterator j = gens.begin(); j != gens.end(); ++j) {
+                if (j->number == n) {
+                    deleteGeneration2(globals, j->number);
+                    found = true;
+                    break;
+                }
+            }
+            if (!found)
+                printMsg(lvlError, format("generation %1% does not exist") % n);
+        }
+    }
+}
+
+
+int main(int argc, char * * argv)
+{
+    return handleExceptions(argv[0], [&]() {
+        initNix();
+
+        Strings opFlags, opArgs, searchPath;
+        std::map<string, string> autoArgs_;
+        Operation op = 0;
+        bool repair = false;
+        string file;
+
+        Globals globals;
+
+        globals.instSource.type = srcUnknown;
+        globals.instSource.nixExprPath = getDefNixExprPath();
+        globals.instSource.systemFilter = "*";
+
+        globals.dryRun = false;
+        globals.preserveInstalled = false;
+        globals.removeAll = false;
+        globals.prebuiltOnly = false;
+
+        parseCmdLine(argc, argv, [&](Strings::iterator & arg, const Strings::iterator & end) {
+            Operation oldOp = op;
+
+            if (*arg == "--help")
+                showManPage("nix-env");
+            else if (*arg == "--version")
+                printVersion("nix-env");
+            else if (*arg == "--install" || *arg == "-i")
+                op = opInstall;
+            else if (parseAutoArgs(arg, end, autoArgs_))
+                ;
+            else if (parseSearchPathArg(arg, end, searchPath))
+                ;
+            else if (*arg == "--force-name") // undocumented flag for nix-install-package
+                globals.forceName = getArg(*arg, arg, end);
+            else if (*arg == "--uninstall" || *arg == "-e")
+                op = opUninstall;
+            else if (*arg == "--upgrade" || *arg == "-u")
+                op = opUpgrade;
+            else if (*arg == "--set-flag")
+                op = opSetFlag;
+            else if (*arg == "--set")
+                op = opSet;
+            else if (*arg == "--query" || *arg == "-q")
+                op = opQuery;
+            else if (*arg == "--profile" || *arg == "-p")
+                globals.profile = absPath(getArg(*arg, arg, end));
+            else if (*arg == "--file" || *arg == "-f")
+                file = getArg(*arg, arg, end);
+            else if (*arg == "--switch-profile" || *arg == "-S")
+                op = opSwitchProfile;
+            else if (*arg == "--switch-generation" || *arg == "-G")
+                op = opSwitchGeneration;
+            else if (*arg == "--rollback")
+                op = opRollback;
+            else if (*arg == "--list-generations")
+                op = opListGenerations;
+            else if (*arg == "--delete-generations")
+                op = opDeleteGenerations;
+            else if (*arg == "--dry-run") {
+                printMsg(lvlInfo, "(dry run; not doing anything)");
+                globals.dryRun = true;
+            }
+            else if (*arg == "--system-filter")
+                globals.instSource.systemFilter = getArg(*arg, arg, end);
+            else if (*arg == "--prebuilt-only" || *arg == "-b")
+                globals.prebuiltOnly = true;
+            else if (*arg == "--repair")
+                repair = true;
+            else if (*arg != "" && arg->at(0) == '-') {
+                opFlags.push_back(*arg);
+                /* FIXME: hacky */
+                if (*arg == "--from-profile" ||
+                    (op == opQuery && (*arg == "--attr" || *arg == "-A")))
+                    opFlags.push_back(getArg(*arg, arg, end));
+            }
+            else
+                opArgs.push_back(*arg);
+
+            if (oldOp && oldOp != op)
+                throw UsageError("only one operation may be specified");
+
+            return true;
+        });
+
+        if (!op) throw UsageError("no operation specified");
+
+        globals.state = std::shared_ptr<EvalState>(new EvalState(searchPath));
+        globals.state->repair = repair;
+
+        if (file != "")
+            globals.instSource.nixExprPath = lookupFileArg(*globals.state, file);
+
+        evalAutoArgs(*globals.state, autoArgs_, globals.instSource.autoArgs);
+
+        if (globals.profile == "")
+            globals.profile = getEnv("NIX_PROFILE", "");
+
+        if (globals.profile == "") {
+            Path profileLink = getHomeDir() + "/.nix-profile";
+            globals.profile = pathExists(profileLink)
+                ? absPath(readLink(profileLink), dirOf(profileLink))
+                : canonPath(settings.nixStateDir + "/profiles/default");
+        }
+
+        store = openStore();
+
+        op(globals, opFlags, opArgs);
+
+        globals.state->printStats();
+    });
+}
diff --git a/src/nix-env/profiles.cc b/src/nix-env/profiles.cc
new file mode 100644
index 000000000000..d8eb0ef5269c
--- /dev/null
+++ b/src/nix-env/profiles.cc
@@ -0,0 +1,146 @@
+#include "profiles.hh"
+#include "store-api.hh"
+#include "util.hh"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+
+
+namespace nix {
+
+
+static bool cmpGensByNumber(const Generation & a, const Generation & b)
+{
+    return a.number < b.number;
+}
+
+
+/* Parse a generation name of the format
+   `<profilename>-<number>-link'. */
+static int parseName(const string & profileName, const string & name)
+{
+    if (string(name, 0, profileName.size() + 1) != profileName + "-") return -1;
+    string s = string(name, profileName.size() + 1);
+    string::size_type p = s.find("-link");
+    if (p == string::npos) return -1;
+    int n;
+    if (string2Int(string(s, 0, p), n) && n >= 0)
+        return n;
+    else
+        return -1;
+}
+
+
+
+Generations findGenerations(Path profile, int & curGen)
+{
+    Generations gens;
+
+    Path profileDir = dirOf(profile);
+    string profileName = baseNameOf(profile);
+
+    for (auto & i : readDirectory(profileDir)) {
+        int n;
+        if ((n = parseName(profileName, i.name)) != -1) {
+            Generation gen;
+            gen.path = profileDir + "/" + i.name;
+            gen.number = n;
+            struct stat st;
+            if (lstat(gen.path.c_str(), &st) != 0)
+                throw SysError(format("statting ‘%1%’") % gen.path);
+            gen.creationTime = st.st_mtime;
+            gens.push_back(gen);
+        }
+    }
+
+    gens.sort(cmpGensByNumber);
+
+    curGen = pathExists(profile)
+        ? parseName(profileName, readLink(profile))
+        : -1;
+
+    return gens;
+}
+
+
+static void makeName(const Path & profile, unsigned int num,
+    Path & outLink)
+{
+    Path prefix = (format("%1%-%2%") % profile % num).str();
+    outLink = prefix + "-link";
+}
+
+
+Path createGeneration(Path profile, Path outPath)
+{
+    /* The new generation number should be higher than old the
+       previous ones. */
+    int dummy;
+    Generations gens = findGenerations(profile, dummy);
+    unsigned int num = gens.size() > 0 ? gens.back().number : 0;
+
+    /* Create the new generation.  Note that addPermRoot() blocks if
+       the garbage collector is running to prevent the stuff we've
+       built from moving from the temporary roots (which the GC knows)
+       to the permanent roots (of which the GC would have a stale
+       view).  If we didn't do it this way, the GC might remove the
+       user environment etc. we've just built. */
+    Path generation;
+    makeName(profile, num + 1, generation);
+    addPermRoot(*store, outPath, generation, false, true);
+
+    return generation;
+}
+
+
+static void removeFile(const Path & path)
+{
+    if (remove(path.c_str()) == -1)
+        throw SysError(format("cannot unlink ‘%1%’") % path);
+}
+
+
+void deleteGeneration(const Path & profile, unsigned int gen)
+{
+    Path generation;
+    makeName(profile, gen, generation);
+    removeFile(generation);
+}
+
+
+void switchLink(Path link, Path target)
+{
+    /* Hacky. */
+    if (dirOf(target) == dirOf(link)) target = baseNameOf(target);
+
+    Path tmp = canonPath(dirOf(link) + "/.new_" + baseNameOf(link));
+    createSymlink(target, tmp);
+    /* The rename() system call is supposed to be essentially atomic
+       on Unix.  That is, if we have links `current -> X' and
+       `new_current -> Y', and we rename new_current to current, a
+       process accessing current will see X or Y, but never a
+       file-not-found or other error condition.  This is sufficient to
+       atomically switch user environments. */
+    if (rename(tmp.c_str(), link.c_str()) != 0)
+        throw SysError(format("renaming ‘%1%’ to ‘%2%’") % tmp % link);
+}
+
+
+void lockProfile(PathLocks & lock, const Path & profile)
+{
+    lock.lockPaths(singleton<PathSet>(profile),
+        (format("waiting for lock on profile ‘%1%’") % profile).str());
+    lock.setDeletion(true);
+}
+
+
+string optimisticLockProfile(const Path & profile)
+{
+    return pathExists(profile) ? readLink(profile) : "";
+}
+
+
+}
diff --git a/src/nix-env/profiles.hh b/src/nix-env/profiles.hh
new file mode 100644
index 000000000000..30d2376d998c
--- /dev/null
+++ b/src/nix-env/profiles.hh
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "types.hh"
+#include "pathlocks.hh"
+
+#include <time.h>
+
+
+namespace nix {
+
+
+struct Generation
+{
+    int number;
+    Path path;
+    time_t creationTime;
+    Generation()
+    {
+        number = -1;
+    }
+    operator bool() const
+    {
+        return number != -1;
+    }
+};
+
+typedef list<Generation> Generations;
+
+
+/* Returns the list of currently present generations for the specified
+   profile, sorted by generation number. */
+Generations findGenerations(Path profile, int & curGen);
+    
+Path createGeneration(Path profile, Path outPath);
+
+void deleteGeneration(const Path & profile, unsigned int gen);
+
+void switchLink(Path link, Path target);
+
+/* Ensure exclusive access to a profile.  Any command that modifies
+   the profile first acquires this lock. */
+void lockProfile(PathLocks & lock, const Path & profile);
+
+/* Optimistic locking is used by long-running operations like `nix-env
+   -i'.  Instead of acquiring the exclusive lock for the entire
+   duration of the operation, we just perform the operation
+   optimistically (without an exclusive lock), and check at the end
+   whether the profile changed while we were busy (i.e., the symlink
+   target changed).  If so, the operation is restarted.  Restarting is
+   generally cheap, since the build results are still in the Nix
+   store.  Most of the time, only the user environment has to be
+   rebuilt. */
+string optimisticLockProfile(const Path & profile);
+
+}
diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc
new file mode 100644
index 000000000000..3ebd6c1f2362
--- /dev/null
+++ b/src/nix-env/user-env.cc
@@ -0,0 +1,151 @@
+#include "user-env.hh"
+#include "util.hh"
+#include "derivations.hh"
+#include "store-api.hh"
+#include "globals.hh"
+#include "shared.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "profiles.hh"
+
+
+namespace nix {
+
+
+DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
+{
+    DrvInfos elems;
+    Path manifestFile = userEnv + "/manifest.nix";
+    if (pathExists(manifestFile)) {
+        Value v;
+        state.evalFile(manifestFile, v);
+        Bindings bindings;
+        getDerivations(state, v, "", bindings, elems, false);
+    }
+    return elems;
+}
+
+
+bool createUserEnv(EvalState & state, DrvInfos & elems,
+    const Path & profile, bool keepDerivations,
+    const string & lockToken)
+{
+    /* Build the components in the user environment, if they don't
+       exist already. */
+    PathSet drvsToBuild;
+    foreach (DrvInfos::iterator, i, elems)
+        if (i->queryDrvPath() != "")
+            drvsToBuild.insert(i->queryDrvPath());
+
+    debug(format("building user environment dependencies"));
+    store->buildPaths(drvsToBuild, state.repair ? bmRepair : bmNormal);
+
+    /* Construct the whole top level derivation. */
+    PathSet references;
+    Value manifest;
+    state.mkList(manifest, elems.size());
+    unsigned int n = 0;
+    foreach (DrvInfos::iterator, i, elems) {
+        /* Create a pseudo-derivation containing the name, system,
+           output paths, and optionally the derivation path, as well
+           as the meta attributes. */
+        Path drvPath = keepDerivations ? i->queryDrvPath() : "";
+
+        Value & v(*state.allocValue());
+        manifest.list.elems[n++] = &v;
+        state.mkAttrs(v, 16);
+
+        mkString(*state.allocAttr(v, state.sType), "derivation");
+        mkString(*state.allocAttr(v, state.sName), i->name);
+        if (!i->system.empty())
+            mkString(*state.allocAttr(v, state.sSystem), i->system);
+        mkString(*state.allocAttr(v, state.sOutPath), i->queryOutPath());
+        if (drvPath != "")
+            mkString(*state.allocAttr(v, state.sDrvPath), i->queryDrvPath());
+
+        // Copy each output.
+        DrvInfo::Outputs outputs = i->queryOutputs();
+        Value & vOutputs = *state.allocAttr(v, state.sOutputs);
+        state.mkList(vOutputs, outputs.size());
+        unsigned int m = 0;
+        foreach (DrvInfo::Outputs::iterator, j, outputs) {
+            mkString(*(vOutputs.list.elems[m++] = state.allocValue()), j->first);
+            Value & vOutputs = *state.allocAttr(v, state.symbols.create(j->first));
+            state.mkAttrs(vOutputs, 2);
+            mkString(*state.allocAttr(vOutputs, state.sOutPath), j->second);
+
+            /* This is only necessary when installing store paths, e.g.,
+               `nix-env -i /nix/store/abcd...-foo'. */
+            store->addTempRoot(j->second);
+            store->ensurePath(j->second);
+
+            references.insert(j->second);
+        }
+
+        // Copy the meta attributes.
+        Value & vMeta = *state.allocAttr(v, state.sMeta);
+        state.mkAttrs(vMeta, 16);
+        StringSet metaNames = i->queryMetaNames();
+        foreach (StringSet::iterator, j, metaNames) {
+            Value * v = i->queryMeta(*j);
+            if (!v) continue;
+            vMeta.attrs->push_back(Attr(state.symbols.create(*j), v));
+        }
+        vMeta.attrs->sort();
+        v.attrs->sort();
+
+        if (drvPath != "") references.insert(drvPath);
+    }
+
+    /* Also write a copy of the list of user environment elements to
+       the store; we need it for future modifications of the
+       environment. */
+    Path manifestFile = store->addTextToStore("env-manifest.nix",
+        (format("%1%") % manifest).str(), references);
+
+    /* Get the environment builder expression. */
+    Value envBuilder;
+    state.evalFile(state.findFile("nix/buildenv.nix"), envBuilder);
+
+    /* Construct a Nix expression that calls the user environment
+       builder with the manifest as argument. */
+    Value args, topLevel;
+    state.mkAttrs(args, 3);
+    mkString(*state.allocAttr(args, state.symbols.create("manifest")),
+        manifestFile, singleton<PathSet>(manifestFile));
+    args.attrs->push_back(Attr(state.symbols.create("derivations"), &manifest));
+    args.attrs->sort();
+    mkApp(topLevel, envBuilder, args);
+
+    /* Evaluate it. */
+    debug("evaluating user environment builder");
+    state.forceValue(topLevel);
+    PathSet context;
+    Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
+    Path topLevelDrv = state.coerceToPath(aDrvPath.pos ? *(aDrvPath.pos) : noPos, *(aDrvPath.value), context);
+    Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
+    Path topLevelOut = state.coerceToPath(aOutPath.pos ? *(aOutPath.pos) : noPos, *(aOutPath.value), context);
+
+    /* Realise the resulting store expression. */
+    debug("building user environment");
+    store->buildPaths(singleton<PathSet>(topLevelDrv), state.repair ? bmRepair : bmNormal);
+
+    /* Switch the current user environment to the output path. */
+    PathLocks lock;
+    lockProfile(lock, profile);
+
+    Path lockTokenCur = optimisticLockProfile(profile);
+    if (lockToken != lockTokenCur) {
+        printMsg(lvlError, format("profile ‘%1%’ changed while we were busy; restarting") % profile);
+        return false;
+    }
+
+    debug(format("switching to new user environment"));
+    Path generation = createGeneration(profile, topLevelOut);
+    switchLink(profile, generation);
+
+    return true;
+}
+
+
+}
diff --git a/src/nix-env/user-env.hh b/src/nix-env/user-env.hh
new file mode 100644
index 000000000000..f188efe9b4a9
--- /dev/null
+++ b/src/nix-env/user-env.hh
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "get-drvs.hh"
+
+namespace nix {
+
+DrvInfos queryInstalled(EvalState & state, const Path & userEnv);
+
+bool createUserEnv(EvalState & state, DrvInfos & elems,
+    const Path & profile, bool keepDerivations,
+    const string & lockToken);
+
+}