about summary refs log tree commit diff
path: root/src/nix
diff options
context:
space:
mode:
Diffstat (limited to 'src/nix')
-rw-r--r--src/nix/build.cc4
-rw-r--r--src/nix/command.cc2
-rw-r--r--src/nix/command.hh6
-rw-r--r--src/nix/copy.cc12
-rw-r--r--src/nix/dump-path.cc1
-rw-r--r--src/nix/edit.cc11
-rw-r--r--src/nix/eval.cc32
-rw-r--r--src/nix/installables.cc10
-rw-r--r--src/nix/local.mk4
-rw-r--r--src/nix/log.cc1
-rw-r--r--src/nix/ls.cc41
-rw-r--r--src/nix/main.cc11
-rw-r--r--src/nix/path-info.cc2
-rw-r--r--src/nix/ping-store.cc35
-rw-r--r--src/nix/progress-bar.cc93
-rw-r--r--src/nix/repl.cc15
-rw-r--r--src/nix/run.cc21
-rw-r--r--src/nix/search.cc96
-rw-r--r--src/nix/show-config.cc2
-rw-r--r--src/nix/upgrade-nix.cc131
20 files changed, 395 insertions, 135 deletions
diff --git a/src/nix/build.cc b/src/nix/build.cc
index f7c99f12dbbf..b329ac38ac2b 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -50,7 +50,9 @@ struct CmdBuild : MixDryRun, InstallablesCommand
 
     void run(ref<Store> store) override
     {
-        auto buildables = toBuildables(store, dryRun ? DryRun : Build, installables);
+        auto buildables = build(store, dryRun ? DryRun : Build, installables);
+
+        if (dryRun) return;
 
         for (size_t i = 0; i < buildables.size(); ++i) {
             auto & b(buildables[i]);
diff --git a/src/nix/command.cc b/src/nix/command.cc
index 1e6f0d2bb75d..3d7d582d6f5e 100644
--- a/src/nix/command.cc
+++ b/src/nix/command.cc
@@ -57,8 +57,10 @@ void MultiCommand::printHelp(const string & programName, std::ostream & out)
     }
     printTable(out, table);
 
+#if 0
     out << "\n";
     out << "For full documentation, run 'man " << programName << "' or 'man " << programName << "-<COMMAND>'.\n";
+#endif
 }
 
 bool MultiCommand::processFlag(Strings::iterator & pos, Strings::iterator end)
diff --git a/src/nix/command.hh b/src/nix/command.hh
index daa3b3fa7030..97a6fee7fd27 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -5,8 +5,10 @@
 
 namespace nix {
 
+extern std::string programPath;
+
 struct Value;
-struct Bindings;
+class Bindings;
 class EvalState;
 
 /* A command is an argument parser that can be executed by calling its
@@ -196,7 +198,7 @@ std::shared_ptr<Installable> parseInstallable(
     SourceExprCommand & cmd, ref<Store> store, const std::string & installable,
     bool useDefaultInstallables);
 
-Buildables toBuildables(ref<Store> store, RealiseMode mode,
+Buildables build(ref<Store> store, RealiseMode mode,
     std::vector<std::shared_ptr<Installable>> installables);
 
 PathSet toStorePaths(ref<Store> store, RealiseMode mode,
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
index 2ddea9e70a6a..e4e6c3e303ed 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -57,16 +57,22 @@ struct CmdCopy : StorePathsCommand
         return {
             Example{
                 "To copy Firefox from the local store to a binary cache in file:///tmp/cache:",
-                "nix copy --to file:///tmp/cache -r $(type -p firefox)"
+                "nix copy --to file:///tmp/cache $(type -p firefox)"
             },
             Example{
                 "To copy the entire current NixOS system closure to another machine via SSH:",
-                "nix copy --to ssh://server -r /run/current-system"
+                "nix copy --to ssh://server /run/current-system"
             },
             Example{
                 "To copy a closure from another machine via SSH:",
-                "nix copy --from ssh://server -r /nix/store/a6cnl93nk1wxnq84brbbwr6hxw9gp2w9-blender-2.79-rc2"
+                "nix copy --from ssh://server /nix/store/a6cnl93nk1wxnq84brbbwr6hxw9gp2w9-blender-2.79-rc2"
             },
+#ifdef ENABLE_S3
+            Example{
+                "To populate the current folder build output to a S3 binary cache:",
+                "nix copy --to s3://my-bucket?region=eu-west-1"
+            },
+#endif
         };
     }
 
diff --git a/src/nix/dump-path.cc b/src/nix/dump-path.cc
index 1a1866437b07..f411c0cb7c89 100644
--- a/src/nix/dump-path.cc
+++ b/src/nix/dump-path.cc
@@ -29,6 +29,7 @@ struct CmdDumpPath : StorePathCommand
     {
         FdSink sink(STDOUT_FILENO);
         store->narFromPath(storePath, sink);
+        sink.flush();
     }
 };
 
diff --git a/src/nix/edit.cc b/src/nix/edit.cc
index 127be321eee2..c9671f76d0fa 100644
--- a/src/nix/edit.cc
+++ b/src/nix/edit.cc
@@ -52,11 +52,16 @@ struct CmdEdit : InstallableCommand
             throw Error("cannot parse meta.position attribute '%s'", pos);
 
         std::string filename(pos, 0, colon);
-        int lineno = std::stoi(std::string(pos, colon + 1));
+        int lineno;
+        try {
+            lineno = std::stoi(std::string(pos, colon + 1));
+        } catch (std::invalid_argument e) {
+            throw Error("cannot parse line number '%s'", pos);
+        }
 
         auto editor = getEnv("EDITOR", "cat");
 
-        Strings args{editor};
+        auto args = tokenizeString<Strings>(editor);
 
         if (editor.find("emacs") != std::string::npos ||
             editor.find("nano") != std::string::npos ||
@@ -67,7 +72,7 @@ struct CmdEdit : InstallableCommand
 
         stopProgressBar();
 
-        execvp(editor.c_str(), stringsToCharPtrs(args).data());
+        execvp(args.front().c_str(), stringsToCharPtrs(args).data());
 
         throw SysError("cannot run editor '%s'", editor);
     }
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index e22128692630..b7058361cbec 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -5,10 +5,11 @@
 #include "eval.hh"
 #include "json.hh"
 #include "value-to-json.hh"
+#include "progress-bar.hh"
 
 using namespace nix;
 
-struct CmdEval : MixJSON, InstallablesCommand
+struct CmdEval : MixJSON, InstallableCommand
 {
     bool raw = false;
 
@@ -42,6 +43,10 @@ struct CmdEval : MixJSON, InstallablesCommand
                 "To get the current version of Nixpkgs:",
                 "nix eval --raw nixpkgs.lib.nixpkgsVersion"
             },
+            Example{
+                "To print the store path of the Hello package:",
+                "nix eval --raw nixpkgs.hello"
+            },
         };
     }
 
@@ -52,20 +57,19 @@ struct CmdEval : MixJSON, InstallablesCommand
 
         auto state = getEvalState();
 
-        auto jsonOut = json ? std::make_unique<JSONList>(std::cout) : nullptr;
+        auto v = installable->toValue(*state);
+        PathSet context;
+
+        stopProgressBar();
 
-        for (auto & i : installables) {
-            auto v = i->toValue(*state);
-            if (raw) {
-                std::cout << state->forceString(*v);
-            } else if (json) {
-                PathSet context;
-                auto jsonElem = jsonOut->placeholder();
-                printValueAsJSON(*state, true, *v, jsonElem, context);
-            } else {
-                state->forceValueDeep(*v);
-                std::cout << *v << "\n";
-            }
+        if (raw) {
+            std::cout << state->coerceToString(noPos, *v, context);
+        } else if (json) {
+            JSONPlaceholder jsonOut(std::cout);
+            printValueAsJSON(*state, true, *v, jsonOut, context);
+        } else {
+            state->forceValueDeep(*v);
+            std::cout << *v << "\n";
         }
     }
 };
diff --git a/src/nix/installables.cc b/src/nix/installables.cc
index ae93c4ef649e..a3fdd8a2808d 100644
--- a/src/nix/installables.cc
+++ b/src/nix/installables.cc
@@ -30,10 +30,8 @@ Value * SourceExprCommand::getSourceExpr(EvalState & state)
 
     vSourceExpr = state.allocValue();
 
-    if (file != "") {
-        Expr * e = state.parseExprFromFile(resolveExprPath(lookupFileArg(state, file)));
-        state.eval(e, *vSourceExpr);
-    }
+    if (file != "")
+        state.evalFile(lookupFileArg(state, file), *vSourceExpr);
 
     else {
 
@@ -255,7 +253,7 @@ std::shared_ptr<Installable> parseInstallable(
     return installables.front();
 }
 
-Buildables toBuildables(ref<Store> store, RealiseMode mode,
+Buildables build(ref<Store> store, RealiseMode mode,
     std::vector<std::shared_ptr<Installable>> installables)
 {
     if (mode != Build)
@@ -293,7 +291,7 @@ PathSet toStorePaths(ref<Store> store, RealiseMode mode,
 {
     PathSet outPaths;
 
-    for (auto & b : toBuildables(store, mode, installables))
+    for (auto & b : build(store, mode, installables))
         for (auto & output : b.outputs)
             outPaths.insert(output.second);
 
diff --git a/src/nix/local.mk b/src/nix/local.mk
index c7d2d328aab5..f76da194467c 100644
--- a/src/nix/local.mk
+++ b/src/nix/local.mk
@@ -2,8 +2,10 @@ programs += nix
 
 nix_DIR := $(d)
 
-nix_SOURCES := $(wildcard $(d)/*.cc) src/linenoise/linenoise.c
+nix_SOURCES := $(wildcard $(d)/*.cc) $(wildcard src/linenoise/*.cpp)
 
 nix_LIBS = libexpr libmain libstore libutil libformat
 
+nix_LDFLAGS = -pthread
+
 $(eval $(call install-symlink, nix, $(bindir)/nix-hash))
diff --git a/src/nix/log.cc b/src/nix/log.cc
index 966ad8b65087..f07ec4e93a16 100644
--- a/src/nix/log.cc
+++ b/src/nix/log.cc
@@ -50,6 +50,7 @@ struct CmdLog : InstallableCommand
 
         auto b = installable->toBuildable();
 
+        RunPager pager;
         for (auto & sub : subs) {
             auto log = b.drvPath != "" ? sub->getBuildLog(b.drvPath) : nullptr;
             for (auto & output : b.outputs) {
diff --git a/src/nix/ls.cc b/src/nix/ls.cc
index 5a5fa8f62d92..e99622faf472 100644
--- a/src/nix/ls.cc
+++ b/src/nix/ls.cc
@@ -2,10 +2,12 @@
 #include "store-api.hh"
 #include "fs-accessor.hh"
 #include "nar-accessor.hh"
+#include "common-args.hh"
+#include "json.hh"
 
 using namespace nix;
 
-struct MixLs : virtual Args
+struct MixLs : virtual Args, MixJSON
 {
     std::string path;
 
@@ -20,7 +22,7 @@ struct MixLs : virtual Args
         mkFlag('d', "directory", "show directories rather than their contents", &showDirectory);
     }
 
-    void list(ref<FSAccessor> accessor)
+    void listText(ref<FSAccessor> accessor)
     {
         std::function<void(const FSAccessor::Stat &, const Path &, const std::string &, bool)> doPath;
 
@@ -61,10 +63,6 @@ struct MixLs : virtual Args
                 showFile(curPath, relPath);
         };
 
-        if (path == "/") {
-            path = "";
-        }
-
         auto st = accessor->stat(path);
         if (st.type == FSAccessor::Type::tMissing)
             throw Error(format("path '%1%' does not exist") % path);
@@ -72,6 +70,17 @@ struct MixLs : virtual Args
             st.type == FSAccessor::Type::tDirectory ? "." : baseNameOf(path),
             showDirectory);
     }
+
+    void list(ref<FSAccessor> accessor)
+    {
+        if (path == "/") path = "";
+
+        if (json) {
+            JSONPlaceholder jsonRoot(std::cout);
+            listNar(jsonRoot, accessor, path, recursive);
+        } else
+            listText(accessor);
+    }
 };
 
 struct CmdLsStore : StoreCommand, MixLs
@@ -81,6 +90,16 @@ struct CmdLsStore : StoreCommand, MixLs
         expectArg("path", &path);
     }
 
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To list the contents of a store path in a binary cache:",
+                "nix ls-store --store https://cache.nixos.org/ -lR /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10"
+            },
+        };
+    }
+
     std::string name() override
     {
         return "ls-store";
@@ -107,6 +126,16 @@ struct CmdLsNar : Command, MixLs
         expectArg("path", &path);
     }
 
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To list a specific file in a NAR:",
+                "nix ls-nar -l hello.nar /bin/hello"
+            },
+        };
+    }
+
     std::string name() override
     {
         return "ls-nar";
diff --git a/src/nix/main.cc b/src/nix/main.cc
index 060402cd08d5..bb107ec7d3f6 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -16,6 +16,8 @@ void chrootHelper(int argc, char * * argv);
 
 namespace nix {
 
+std::string programPath;
+
 struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
 {
     NixArgs() : MultiCommand(*RegisterCommand::commands), MixCommonArgs("nix")
@@ -43,10 +45,6 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
             .longName("version")
             .description("show version information")
             .handler([&]() { printVersion(programName); });
-
-        std::string cat = "config";
-        settings.convertToArgs(*this, cat);
-        hiddenCategories.insert(cat);
     }
 
     void printFlags(std::ostream & out) override
@@ -82,7 +80,8 @@ void mainWrapped(int argc, char * * argv)
     initNix();
     initGC();
 
-    string programName = baseNameOf(argv[0]);
+    programPath = argv[0];
+    string programName = baseNameOf(programPath);
 
     {
         auto legacy = (*RegisterLegacyCommand::commands)[programName];
@@ -93,6 +92,8 @@ void mainWrapped(int argc, char * * argv)
 
     args.parseCmdline(argvToStrings(argc, argv));
 
+    initPlugins();
+
     if (!args.command) args.showHelpAndExit();
 
     Finally f([]() { stopProgressBar(); });
diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc
index ca02a4c929be..47caa401d3c9 100644
--- a/src/nix/path-info.cc
+++ b/src/nix/path-info.cc
@@ -65,7 +65,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON
             pathLen = std::max(pathLen, storePath.size());
 
         if (json) {
-            JSONPlaceholder jsonRoot(std::cout, true);
+            JSONPlaceholder jsonRoot(std::cout);
             store->pathInfoToJSON(jsonRoot,
                 // FIXME: preserve order?
                 PathSet(storePaths.begin(), storePaths.end()),
diff --git a/src/nix/ping-store.cc b/src/nix/ping-store.cc
new file mode 100644
index 000000000000..310942574a2a
--- /dev/null
+++ b/src/nix/ping-store.cc
@@ -0,0 +1,35 @@
+#include "command.hh"
+#include "shared.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdPingStore : StoreCommand
+{
+    std::string name() override
+    {
+        return "ping-store";
+    }
+
+    std::string description() override
+    {
+        return "test whether a store can be opened";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To test whether connecting to a remote Nix store via SSH works:",
+                "nix ping-store --store ssh://mac1"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        store->connect();
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdPingStore>());
diff --git a/src/nix/progress-bar.cc b/src/nix/progress-bar.cc
index fb9955190b40..40b905ba3243 100644
--- a/src/nix/progress-bar.cc
+++ b/src/nix/progress-bar.cc
@@ -3,8 +3,9 @@
 #include "sync.hh"
 #include "store-api.hh"
 
-#include <map>
 #include <atomic>
+#include <map>
+#include <thread>
 
 namespace nix {
 
@@ -22,44 +23,6 @@ static uint64_t getI(const std::vector<Logger::Field> & fields, size_t n)
     return fields[n].i;
 }
 
-/* Truncate a string to 'width' printable characters. ANSI escape
-   sequences are copied but not included in the character count. Also,
-   tabs are expanded to spaces. */
-static std::string ansiTruncate(const std::string & s, int width)
-{
-    if (width <= 0) return s;
-
-    std::string t;
-    size_t w = 0;
-    auto i = s.begin();
-
-    while (w < (size_t) width && i != s.end()) {
-        if (*i == '\e') {
-            t += *i++;
-            if (i != s.end() && *i == '[') {
-                t += *i++;
-                while (i != s.end() && (*i < 0x40 || *i > 0x7e)) {
-                    t += *i++;
-                }
-                if (i != s.end()) t += *i++;
-            }
-        }
-
-        else if (*i == '\t') {
-            t += ' '; w++;
-            while (w < (size_t) width && w & 8) {
-                t += ' '; w++;
-            }
-        }
-
-        else {
-            t += *i++; w++;
-        }
-    }
-
-    return t;
-}
-
 class ProgressBar : public Logger
 {
 private:
@@ -101,15 +64,28 @@ private:
 
     Sync<State> state_;
 
+    std::thread updateThread;
+
+    std::condition_variable quitCV, updateCV;
+
 public:
 
     ProgressBar()
     {
+        updateThread = std::thread([&]() {
+            auto state(state_.lock());
+            while (state->active) {
+                state.wait(updateCV);
+                draw(*state);
+                state.wait_for(quitCV, std::chrono::milliseconds(50));
+            }
+        });
     }
 
     ~ProgressBar()
     {
         stop();
+        updateThread.join();
     }
 
     void stop()
@@ -121,6 +97,8 @@ public:
         writeToStderr("\r\e[K");
         if (status != "")
             writeToStderr("[" + status + "]\n");
+        updateCV.notify_one();
+        quitCV.notify_one();
     }
 
     void log(Verbosity lvl, const FormatOrString & fs) override
@@ -132,7 +110,7 @@ public:
     void log(State & state, Verbosity lvl, const std::string & s)
     {
         writeToStderr("\r\e[K" + s + ANSI_NORMAL "\n");
-        update(state);
+        draw(state);
     }
 
     void startActivity(ActivityId act, Verbosity lvl, ActivityType type,
@@ -167,7 +145,12 @@ public:
 
         if (type == actSubstitute) {
             auto name = storePathToName(getS(fields, 0));
-            i->s = fmt("fetching " ANSI_BOLD "%s" ANSI_NORMAL " from %s", name, getS(fields, 1));
+            auto sub = getS(fields, 1);
+            i->s = fmt(
+                hasPrefix(sub, "local")
+                ? "copying " ANSI_BOLD "%s" ANSI_NORMAL " from %s"
+                : "fetching " ANSI_BOLD "%s" ANSI_NORMAL " from %s",
+                name, sub);
         }
 
         if (type == actQueryPathInfo) {
@@ -180,7 +163,7 @@ public:
             || (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
             i->visible = false;
 
-        update(*state);
+        update();
     }
 
     /* Check whether an activity has an ancestore with the specified
@@ -215,7 +198,7 @@ public:
             state->its.erase(i);
         }
 
-        update(*state);
+        update();
     }
 
     void result(ActivityId act, ResultType type, const std::vector<Field> & fields) override
@@ -225,7 +208,7 @@ public:
         if (type == resFileLinked) {
             state->filesLinked++;
             state->bytesLinked += getI(fields, 0);
-            update(*state);
+            update();
         }
 
         else if (type == resBuildLogLine) {
@@ -238,25 +221,25 @@ public:
                 info.lastLine = lastLine;
                 state->activities.emplace_back(info);
                 i->second = std::prev(state->activities.end());
-                update(*state);
+                update();
             }
         }
 
         else if (type == resUntrustedPath) {
             state->untrustedPaths++;
-            update(*state);
+            update();
         }
 
         else if (type == resCorruptedPath) {
             state->corruptedPaths++;
-            update(*state);
+            update();
         }
 
         else if (type == resSetPhase) {
             auto i = state->its.find(act);
             assert(i != state->its.end());
             i->second->phase = getS(fields, 0);
-            update(*state);
+            update();
         }
 
         else if (type == resProgress) {
@@ -267,7 +250,7 @@ public:
             actInfo.expected = getI(fields, 1);
             actInfo.running = getI(fields, 2);
             actInfo.failed = getI(fields, 3);
-            update(*state);
+            update();
         }
 
         else if (type == resSetExpected) {
@@ -279,17 +262,16 @@ public:
             state->activitiesByType[type].expected -= j;
             j = getI(fields, 1);
             state->activitiesByType[type].expected += j;
-            update(*state);
+            update();
         }
     }
 
     void update()
     {
-        auto state(state_.lock());
-        update(*state);
+        updateCV.notify_one();
     }
 
-    void update(State & state)
+    void draw(State & state)
     {
         if (!state.active) return;
 
@@ -323,7 +305,10 @@ public:
             }
         }
 
-        writeToStderr("\r" + ansiTruncate(line, getWindowSize().second) + "\e[K");
+        auto width = getWindowSize().second;
+        if (width <= 0) std::numeric_limits<decltype(width)>::max();
+
+        writeToStderr("\r" + filterANSIEscapes(line, false, width) + "\e[K");
     }
 
     std::string getStatus(State & state)
diff --git a/src/nix/repl.cc b/src/nix/repl.cc
index 28a8ebc8c499..f84774a53367 100644
--- a/src/nix/repl.cc
+++ b/src/nix/repl.cc
@@ -185,9 +185,20 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
 bool NixRepl::getLine(string & input, const std::string &prompt)
 {
     char * s = linenoise(prompt.c_str());
-    Finally doFree([&]() { linenoiseFree(s); });
-    if (!s) return false;
+    Finally doFree([&]() { free(s); });
+    if (!s) {
+      switch (auto type = linenoiseKeyType()) {
+        case 1: // ctrl-C
+          input = "";
+          return true;
+        case 2: // ctrl-D
+          return false;
+        default:
+          throw Error(format("Unexpected linenoise keytype: %1%") % type);
+      }
+    }
     input += s;
+    input += '\n';
     return true;
 }
 
diff --git a/src/nix/run.cc b/src/nix/run.cc
index 6657a86314bf..d04e106e037b 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -16,8 +16,6 @@ using namespace nix;
 
 std::string chrootHelperName = "__run_in_chroot";
 
-extern char * * environ;
-
 struct CmdRun : InstallablesCommand
 {
     std::vector<std::string> command = { "bash" };
@@ -30,8 +28,8 @@ struct CmdRun : InstallablesCommand
             .longName("command")
             .shortName('c')
             .description("command and arguments to be executed; defaults to 'bash'")
-            .arity(ArityAny)
             .labels({"command", "args"})
+            .arity(ArityAny)
             .handler([&](std::vector<std::string> ss) {
                 if (ss.empty()) throw UsageError("--command requires at least one argument");
                 command = ss;
@@ -85,6 +83,10 @@ struct CmdRun : InstallablesCommand
                 "To run GNU Hello:",
                 "nix run nixpkgs.hello -c hello --greeting 'Hi everybody!'"
             },
+            Example{
+                "To run GNU Hello in a chroot store:",
+                "nix run --store ~/my-nix nixpkgs.hello -c hello"
+            },
         };
     }
 
@@ -105,7 +107,7 @@ struct CmdRun : InstallablesCommand
                 if (s) kept[var] = s;
             }
 
-            environ = nullptr;
+            clearEnv();
 
             for (auto & var : kept)
                 setenv(var.first.c_str(), var.second.c_str(), 1);
@@ -184,7 +186,7 @@ void chrootHelper(int argc, char * * argv)
        but that doesn't work in a user namespace yet (Ubuntu has a
        patch for this:
        https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1478578). */
-    if (true /* !pathExists(storeDir) */) {
+    if (!pathExists(storeDir)) {
         // FIXME: Use overlayfs?
 
         Path tmpDir = createTempDir();
@@ -195,12 +197,15 @@ void chrootHelper(int argc, char * * argv)
             throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
 
         for (auto entry : readDirectory("/")) {
+            auto src = "/" + entry.name;
+            auto st = lstat(src);
+            if (!S_ISDIR(st.st_mode)) continue;
             Path dst = tmpDir + "/" + entry.name;
             if (pathExists(dst)) continue;
             if (mkdir(dst.c_str(), 0700) == -1)
-                throw SysError(format("creating directory '%s'") % dst);
-            if (mount(("/" + entry.name).c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1)
-                throw SysError(format("mounting '%s' on '%s'") %  ("/" + entry.name) % dst);
+                throw SysError("creating directory '%s'", dst);
+            if (mount(src.c_str(), dst.c_str(), "", MS_BIND | MS_REC, 0) == -1)
+                throw SysError("mounting '%s' on '%s'", src, dst);
         }
 
         char * cwd = getcwd(0, 0);
diff --git a/src/nix/search.cc b/src/nix/search.cc
index f458367dcb55..539676698086 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -25,14 +25,14 @@ std::string hilite(const std::string & s, const std::smatch & m)
 
 struct CmdSearch : SourceExprCommand, MixJSON
 {
-    std::string re;
+    std::vector<std::string> res;
 
     bool writeCache = true;
     bool useCache = true;
 
     CmdSearch()
     {
-        expectArg("regex", &re, true);
+        expectArgs("regex", &res);
 
         mkFlag()
             .longName("update-cache")
@@ -68,9 +68,13 @@ struct CmdSearch : SourceExprCommand, MixJSON
                 "nix search blender"
             },
             Example{
-                "To search for Firefox and Chromium:",
+                "To search for Firefox or Chromium:",
                 "nix search 'firefox|chromium'"
             },
+            Example{
+                "To search for git and frontend or gui:",
+                "nix search git 'frontend|gui'"
+            },
         };
     }
 
@@ -78,25 +82,38 @@ struct CmdSearch : SourceExprCommand, MixJSON
     {
         settings.readOnlyMode = true;
 
-        std::regex regex(re, std::regex::extended | std::regex::icase);
+        // Empty search string should match all packages
+        // Use "^" here instead of ".*" due to differences in resulting highlighting
+        // (see #1893 -- libc++ claims empty search string is not in POSIX grammar)
+        if (res.empty()) {
+            res.push_back("^");
+        }
 
-        auto state = getEvalState();
+        std::vector<std::regex> regexes;
+        regexes.reserve(res.size());
 
-        bool first = true;
+        for (auto &re : res) {
+            regexes.push_back(std::regex(re, std::regex::extended | std::regex::icase));
+        }
+
+        auto state = getEvalState();
 
-        auto jsonOut = json ? std::make_unique<JSONObject>(std::cout, true) : nullptr;
+        auto jsonOut = json ? std::make_unique<JSONObject>(std::cout) : nullptr;
 
         auto sToplevel = state->symbols.create("_toplevel");
         auto sRecurse = state->symbols.create("recurseForDerivations");
 
         bool fromCache = false;
 
+        std::map<std::string, std::string> results;
+
         std::function<void(Value *, std::string, bool, JSONObject *)> doExpr;
 
         doExpr = [&](Value * v, std::string attrPath, bool toplevel, JSONObject * cache) {
             debug("at attribute '%s'", attrPath);
 
             try {
+                uint found = 0;
 
                 state->forceValue(*v);
 
@@ -110,25 +127,33 @@ struct CmdSearch : SourceExprCommand, MixJSON
                 if (state->isDerivation(*v)) {
 
                     DrvInfo drv(*state, attrPath, v->attrs);
+                    std::string description;
+                    std::smatch attrPathMatch;
+                    std::smatch descriptionMatch;
+                    std::smatch nameMatch;
+                    std::string name;
 
                     DrvName parsed(drv.queryName());
 
-                    std::smatch attrPathMatch;
-                    std::regex_search(attrPath, attrPathMatch, regex);
+                    for (auto &regex : regexes) {
+                        std::regex_search(attrPath, attrPathMatch, regex);
 
-                    auto name = parsed.name;
-                    std::smatch nameMatch;
-                    std::regex_search(name, nameMatch, regex);
+                        name = parsed.name;
+                        std::regex_search(name, nameMatch, regex);
 
-                    std::string description = drv.queryMetaString("description");
-                    std::replace(description.begin(), description.end(), '\n', ' ');
-                    std::smatch descriptionMatch;
-                    std::regex_search(description, descriptionMatch, regex);
+                        description = drv.queryMetaString("description");
+                        std::replace(description.begin(), description.end(), '\n', ' ');
+                        std::regex_search(description, descriptionMatch, regex);
+
+                        if (!attrPathMatch.empty()
+                            || !nameMatch.empty()
+                            || !descriptionMatch.empty())
+                        {
+                            found++;
+                        }
+                    }
 
-                    if (!attrPathMatch.empty()
-                        || !nameMatch.empty()
-                        || !descriptionMatch.empty())
-                    {
+                    if (found == res.size()) {
                         if (json) {
 
                             auto jsonElem = jsonOut->object(attrPath);
@@ -138,10 +163,7 @@ struct CmdSearch : SourceExprCommand, MixJSON
                             jsonElem.attr("description", description);
 
                         } else {
-                            if (!first) std::cout << "\n";
-                            first = false;
-
-                            std::cout << fmt(
+                            results[attrPath] = fmt(
                                 "Attribute name: %s\n"
                                 "Package name: %s\n"
                                 "Version: %s\n"
@@ -214,17 +236,35 @@ struct CmdSearch : SourceExprCommand, MixJSON
         }
 
         else {
+            createDirs(dirOf(jsonCacheFileName));
+
             Path tmpFile = fmt("%s.tmp.%d", jsonCacheFileName, getpid());
 
-            std::ofstream jsonCacheFile(tmpFile);
+            std::ofstream jsonCacheFile;
 
-            auto cache = writeCache ? std::make_unique<JSONObject>(jsonCacheFile, false) : nullptr;
+            try {
+                // iostream considered harmful
+                jsonCacheFile.exceptions(std::ofstream::failbit);
+                jsonCacheFile.open(tmpFile);
+
+                auto cache = writeCache ? std::make_unique<JSONObject>(jsonCacheFile, false) : nullptr;
+
+                doExpr(getSourceExpr(*state), "", true, cache.get());
 
-            doExpr(getSourceExpr(*state), "", true, cache.get());
+            } catch (std::exception &) {
+                /* Fun fact: catching std::ios::failure does not work
+                   due to C++11 ABI shenanigans.
+                   https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66145 */
+                if (!jsonCacheFile)
+                    throw Error("error writing to %s", tmpFile);
+            }
 
-            if (rename(tmpFile.c_str(), jsonCacheFileName.c_str()) == -1)
+            if (writeCache && rename(tmpFile.c_str(), jsonCacheFileName.c_str()) == -1)
                 throw SysError("cannot rename '%s' to '%s'", tmpFile, jsonCacheFileName);
         }
+
+        for (auto el : results) std::cout << el.second << "\n";
+
     }
 };
 
diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc
index c628c2898d73..c64b12c8dd62 100644
--- a/src/nix/show-config.cc
+++ b/src/nix/show-config.cc
@@ -26,7 +26,7 @@ struct CmdShowConfig : Command, MixJSON
     {
         if (json) {
             // FIXME: use appropriate JSON types (bool, ints, etc).
-            JSONObject jsonObj(std::cout, true);
+            JSONObject jsonObj(std::cout);
             settings.toJSON(jsonObj);
         } else {
             for (auto & s : settings.getSettings())
diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc
new file mode 100644
index 000000000000..758bbbc688bc
--- /dev/null
+++ b/src/nix/upgrade-nix.cc
@@ -0,0 +1,131 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "download.hh"
+#include "eval.hh"
+#include "attr-path.hh"
+
+using namespace nix;
+
+struct CmdUpgradeNix : StoreCommand
+{
+    Path profileDir;
+
+    CmdUpgradeNix()
+    {
+        mkFlag()
+            .longName("profile")
+            .shortName('p')
+            .labels({"profile-dir"})
+            .description("the Nix profile to upgrade")
+            .dest(&profileDir);
+    }
+
+    std::string name() override
+    {
+        return "upgrade-nix";
+    }
+
+    std::string description() override
+    {
+        return "upgrade Nix to the latest stable version";
+    }
+
+    Examples examples() override
+    {
+        return {
+            Example{
+                "To upgrade Nix to the latest stable version:",
+                "nix upgrade-nix"
+            },
+            Example{
+                "To upgrade Nix in a specific profile:",
+                "nix upgrade-nix -p /nix/var/nix/profiles/per-user/alice/profile"
+            },
+        };
+    }
+
+    void run(ref<Store> store) override
+    {
+        settings.pureEval = true;
+
+        if (profileDir == "")
+            profileDir = getProfileDir(store);
+
+        printInfo("upgrading Nix in profile '%s'", profileDir);
+
+        Path storePath;
+        {
+            Activity act(*logger, lvlInfo, actUnknown, "querying latest Nix version");
+            storePath = getLatestNix(store);
+        }
+
+        {
+            Activity act(*logger, lvlInfo, actUnknown, fmt("downloading '%s'...", storePath));
+            store->ensurePath(storePath);
+        }
+
+        {
+            Activity act(*logger, lvlInfo, actUnknown, fmt("verifying that '%s' works...", storePath));
+            auto program = storePath + "/bin/nix-env";
+            auto s = runProgram(program, false, {"--version"});
+            if (s.find("Nix") == std::string::npos)
+                throw Error("could not verify that '%s' works", program);
+        }
+
+        {
+            Activity act(*logger, lvlInfo, actUnknown, fmt("installing '%s' into profile '%s'...", storePath, profileDir));
+            runProgram(settings.nixBinDir + "/nix-env", false,
+                {"--profile", profileDir, "-i", storePath, "--no-sandbox"});
+        }
+    }
+
+    /* Return the profile in which Nix is installed. */
+    Path getProfileDir(ref<Store> store)
+    {
+        Path where;
+
+        for (auto & dir : tokenizeString<Strings>(getEnv("PATH"), ":"))
+            if (pathExists(dir + "/nix-env")) {
+                where = dir;
+                break;
+            }
+
+        if (where == "")
+            throw Error("couldn't figure out how Nix is installed, so I can't upgrade it");
+
+        printInfo("found Nix in '%s'", where);
+
+        if (hasPrefix(where, "/run/current-system"))
+            throw Error("Nix on NixOS must be upgraded via 'nixos-rebuild'");
+
+        Path profileDir;
+        Path userEnv;
+
+        if (baseNameOf(where) != "bin" ||
+            !hasSuffix(userEnv = canonPath(profileDir = dirOf(where), true), "user-environment"))
+            throw Error("directory '%s' does not appear to be part of a Nix profile", where);
+
+        if (!store->isValidPath(userEnv))
+            throw Error("directory '%s' is not in the Nix store", userEnv);
+
+        return profileDir;
+    }
+
+    /* Return the store path of the latest stable Nix. */
+    Path getLatestNix(ref<Store> store)
+    {
+        // FIXME: use nixos.org?
+        auto req = DownloadRequest("https://github.com/NixOS/nixpkgs/raw/master/nixos/modules/installer/tools/nix-fallback-paths.nix");
+        auto res = getDownloader()->download(req);
+
+        EvalState state(Strings(), store);
+        auto v = state.allocValue();
+        state.eval(state.parseExprFromString(*res.data, "/no-such-path"), *v);
+        Bindings & bindings(*state.allocBindings(0));
+        auto v2 = findAlongAttrPath(state, settings.thisSystem, bindings, *v);
+
+        return state.forceString(*v2);
+    }
+};
+
+static RegisterCommand r1(make_ref<CmdUpgradeNix>());