about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--perl/lib/Nix/CopyClosure.pm77
-rw-r--r--src/download-via-ssh/download-via-ssh.cc8
-rw-r--r--src/nix-store/nix-store.cc96
-rw-r--r--src/nix-store/serve-protocol.hh15
4 files changed, 136 insertions, 60 deletions
diff --git a/perl/lib/Nix/CopyClosure.pm b/perl/lib/Nix/CopyClosure.pm
index 41ceabd85847..cba365aa1745 100644
--- a/perl/lib/Nix/CopyClosure.pm
+++ b/perl/lib/Nix/CopyClosure.pm
@@ -4,6 +4,15 @@ use strict;
 use Nix::Config;
 use Nix::Store;
 use List::Util qw(sum);
+use IPC::Open2;
+
+
+sub readInt {
+    my ($from) = @_;
+    my $resp;
+    sysread($from, $resp, 8) == 8 or die "did not receive valid reply from remote host\n";
+    return unpack("L<x4", $resp);
+}
 
 
 sub copyTo {
@@ -20,14 +29,76 @@ sub copyTo {
         # Ignore exit status because this is just an optimisation.
     }
 
+    # Start ‘nix-store --serve’ on the remote host.
+    my ($from, $to);
+    my $pid = open2($from, $to, "ssh $sshHost @{$sshOpts} nix-store --serve --write");
+
+    # Do the handshake.
+    eval {
+        my $SERVE_MAGIC_1 = 0x390c9deb; # FIXME
+        my $clientVersion = 0x200;
+        syswrite($to, pack("L<x4L<x4", $SERVE_MAGIC_1, $clientVersion)) or die;
+        die "did not get valid handshake from remote host\n" if readInt($from) != 0x5452eecb;
+        my $serverVersion = readInt($from);
+        die "unsupported server version\n" if $serverVersion < 0x200 || $serverVersion >= 0x300;
+    };
+    if ($@) {
+        chomp $@;
+        warn "$@; falling back to old closure copying method\n";
+        return oldCopyTo(\@closure, @_);
+    }
+
+    # Send the "query valid paths" command with the "lock" option
+    # enabled. This prevens a race where the remote host
+    # garbage-collect paths that are already there.
+    my $req = pack("L<x4L<x4L<x4", 1, 1, scalar @closure);
+    for my $s (@closure) {
+        my $len = length $s;
+        $req .= pack("L<x4", $len);
+        $req .= $s;
+        $req .= "\000" x (8 - $len % 8) if $len % 8;
+    }
+    syswrite($to, $req) or die;
+
+    # Get back the set of paths that are already valid on the remote host.
+    my %present;
+    my $n = readInt($from);
+    while ($n--) {
+        my $len = readInt($from);
+        my $s;
+        sysread($from, $s, $len) == $len or die;
+        $present{$s} = 1;
+        sysread($from, $s, 8 - $len % 8) if $len % 8; # skip padding
+    }
+
+    my @missing = grep { !$present{$_} } @closure;
+    return if !@missing;
+
+    # Send the "import paths" command.
+    syswrite($to, pack("L<x4", 4)) or die;
+    exportPaths(fileno($to), $sign, @missing);
+    readInt($from) == 1 or die;
+
+    # Shut down the server process.
+    close $to;
+    waitpid $pid, 0;
+}
+
+
+# For backwards compatibility with Nix <= 1.7. Will be removed
+# eventually.
+sub oldCopyTo {
+    my ($closure, $sshHost, $sshOpts, $storePaths, $compressor, $decompressor,
+        $includeOutputs, $dryRun, $sign, $progressViewer, $useSubstitutes) = @_;
+
     # Ask the remote host which paths are invalid.  Because of limits
     # to the command line length, do this in chunks.  Eventually,
     # we'll want to use ‘--from-stdin’, but we can't rely on the
     # target having this option yet.
-    my @missing = ();
+    my @missing;
     my $missingSize = 0;
-    while (scalar(@closure) > 0) {
-        my @ps = splice(@closure, 0, 1500);
+    while (scalar(@$closure) > 0) {
+        my @ps = splice(@$closure, 0, 1500);
         open(READ, "set -f; ssh $sshHost @{$sshOpts} nix-store --check-validity --print-invalid @ps|");
         while (<READ>) {
             chomp;
diff --git a/src/download-via-ssh/download-via-ssh.cc b/src/download-via-ssh/download-via-ssh.cc
index 6361e71e9927..6cbcd9891cf2 100644
--- a/src/download-via-ssh/download-via-ssh.cc
+++ b/src/download-via-ssh/download-via-ssh.cc
@@ -58,7 +58,7 @@ static std::pair<FdSink, FdSource> connect(const string & conn)
 
 static void substitute(std::pair<FdSink, FdSource> & pipes, Path storePath, Path destPath)
 {
-    writeInt(cmdSubstitute, pipes.first);
+    writeInt(cmdDumpStorePath, pipes.first);
     writeString(storePath, pipes.first);
     pipes.first.flush();
     restorePath(destPath, pipes.second);
@@ -68,20 +68,20 @@ static void substitute(std::pair<FdSink, FdSource> & pipes, Path storePath, Path
 
 static void query(std::pair<FdSink, FdSource> & pipes)
 {
-    writeInt(cmdQuery, pipes.first);
     for (string line; getline(std::cin, line);) {
         Strings tokenized = tokenizeString<Strings>(line);
         string cmd = tokenized.front();
         tokenized.pop_front();
         if (cmd == "have") {
-            writeInt(qCmdHave, pipes.first);
+            writeInt(cmdQueryValidPaths, pipes.first);
+            writeInt(0, pipes.first); // don't lock
             writeStrings(tokenized, pipes.first);
             pipes.first.flush();
             PathSet paths = readStrings<PathSet>(pipes.second);
             foreach (PathSet::iterator, i, paths)
                 std::cout << *i << std::endl;
         } else if (cmd == "info") {
-            writeInt(qCmdInfo, pipes.first);
+            writeInt(cmdQueryPathInfos, pipes.first);
             writeStrings(tokenized, pipes.first);
             pipes.first.flush();
             while (1) {
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index 5bcb82f324fa..849cb7e8a77b 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -869,8 +869,12 @@ static void opClearFailedPaths(Strings opFlags, Strings opArgs)
 /* Serve the nix store in a way usable by a restricted ssh user. */
 static void opServe(Strings opFlags, Strings opArgs)
 {
-    if (!opArgs.empty() || !opFlags.empty())
-        throw UsageError("no arguments or flags expected");
+    bool writeAllowed = false;
+    foreach (Strings::iterator, i, opFlags)
+        if (*i == "--write") writeAllowed = true;
+        else throw UsageError(format("unknown flag `%1%'") % *i);
+
+    if (!opArgs.empty()) throw UsageError("no arguments expected");
 
     FdSource in(STDIN_FILENO);
     FdSink out(STDOUT_FILENO);
@@ -883,50 +887,56 @@ static void opServe(Strings opFlags, Strings opArgs)
     out.flush();
     readInt(in); // Client version, unused for now
 
-    ServeCommand cmd = (ServeCommand) readInt(in);
-    switch (cmd) {
-        case cmdQuery:
-            while (true) {
-                QueryCommand qCmd;
-                try {
-                    qCmd = (QueryCommand) readInt(in);
-                } catch (EndOfFile & e) {
-                    break;
-                }
-                switch (qCmd) {
-                    case qCmdHave: {
-                        PathSet paths = readStorePaths<PathSet>(in);
-                        writeStrings(store->queryValidPaths(paths), out);
-                        break;
-                    }
-                    case qCmdInfo: {
-                        PathSet paths = readStorePaths<PathSet>(in);
-                        // !!! Maybe we want a queryPathInfos?
-                        foreach (PathSet::iterator, i, paths) {
-                            if (!store->isValidPath(*i))
-                                continue;
-                            ValidPathInfo info = store->queryPathInfo(*i);
-                            writeString(info.path, out);
-                            writeString(info.deriver, out);
-                            writeStrings(info.references, out);
-                            // !!! Maybe we want compression?
-                            writeLongLong(info.narSize, out); // downloadSize
-                            writeLongLong(info.narSize, out);
-                        }
-                        writeString("", out);
-                        break;
-                    }
-                    default:
-                        throw Error(format("unknown serve query `%1%'") % cmd);
+    while (true) {
+        ServeCommand cmd;
+        try {
+            cmd = (ServeCommand) readInt(in);
+        } catch (EndOfFile & e) {
+            break;
+        }
+
+        switch (cmd) {
+            case cmdQueryValidPaths: {
+                bool lock = readInt(in);
+                PathSet paths = readStorePaths<PathSet>(in);
+                if (lock && writeAllowed)
+                    for (auto & path : paths)
+                        store->addTempRoot(path);
+                writeStrings(store->queryValidPaths(paths), out);
+                out.flush();
+                break;
+            }
+            case cmdQueryPathInfos: {
+                PathSet paths = readStorePaths<PathSet>(in);
+                // !!! Maybe we want a queryPathInfos?
+                foreach (PathSet::iterator, i, paths) {
+                    if (!store->isValidPath(*i))
+                        continue;
+                    ValidPathInfo info = store->queryPathInfo(*i);
+                    writeString(info.path, out);
+                    writeString(info.deriver, out);
+                    writeStrings(info.references, out);
+                    // !!! Maybe we want compression?
+                    writeLongLong(info.narSize, out); // downloadSize
+                    writeLongLong(info.narSize, out);
                 }
+                writeString("", out);
                 out.flush();
+                break;
             }
-            break;
-        case cmdSubstitute:
-            dumpPath(readStorePath(in), out);
-            break;
-        default:
-            throw Error(format("unknown serve command `%1%'") % cmd);
+            case cmdDumpStorePath:
+                dumpPath(readStorePath(in), out);
+                out.flush();
+                break;
+            case cmdImportPaths:
+                if (!writeAllowed) throw Error("importing paths not allowed");
+                store->importPaths(false, in);
+                writeInt(1, out); // indicate success
+                out.flush();
+                break;
+            default:
+                throw Error(format("unknown serve command %1%") % cmd);
+        }
     }
 }
 
diff --git a/src/nix-store/serve-protocol.hh b/src/nix-store/serve-protocol.hh
index 69277bc1b99b..07ff4f7a7cc4 100644
--- a/src/nix-store/serve-protocol.hh
+++ b/src/nix-store/serve-protocol.hh
@@ -2,23 +2,18 @@
 
 namespace nix {
 
-
 #define SERVE_MAGIC_1 0x390c9deb
 #define SERVE_MAGIC_2 0x5452eecb
 
-#define SERVE_PROTOCOL_VERSION 0x101
+#define SERVE_PROTOCOL_VERSION 0x200
 #define GET_PROTOCOL_MAJOR(x) ((x) & 0xff00)
 #define GET_PROTOCOL_MINOR(x) ((x) & 0x00ff)
 
-
 typedef enum {
-    cmdQuery = 0,
-    cmdSubstitute = 1,
+    cmdQueryValidPaths = 1,
+    cmdQueryPathInfos = 2,
+    cmdDumpStorePath = 3,
+    cmdImportPaths = 4,
 } ServeCommand;
 
-typedef enum {
-    qCmdHave = 0,
-    qCmdInfo = 1,
-} QueryCommand;
-
 }