about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--perl/lib/Nix/CopyClosure.pm96
-rw-r--r--perl/lib/Nix/SSH.pm81
-rw-r--r--perl/lib/Nix/Store.pm2
-rw-r--r--perl/lib/Nix/Store.xs11
-rw-r--r--perl/local.mk2
-rwxr-xr-xscripts/build-remote.pl.in68
-rw-r--r--src/libstore/remote-store.cc1
-rw-r--r--src/nix-store/nix-store.cc37
-rw-r--r--src/nix-store/serve-protocol.hh2
9 files changed, 185 insertions, 115 deletions
diff --git a/perl/lib/Nix/CopyClosure.pm b/perl/lib/Nix/CopyClosure.pm
index 131f0b5a4557..f701a7c8a0d5 100644
--- a/perl/lib/Nix/CopyClosure.pm
+++ b/perl/lib/Nix/CopyClosure.pm
@@ -3,76 +3,27 @@ package Nix::CopyClosure;
 use strict;
 use Nix::Config;
 use Nix::Store;
+use Nix::SSH;
 use List::Util qw(sum);
 use IPC::Open2;
 
 
-sub readN {
-    my ($bytes, $from) = @_;
-    my $res = "";
-    while ($bytes > 0) {
-        my $s;
-        my $n = sysread($from, $s, $bytes);
-        die "I/O error reading from remote side\n" if !defined $n;
-        die "got EOF while expecting $bytes bytes from remote side\n" if !$n;
-        $bytes -= $n;
-        $res .= $s;
-    }
-    return $res;
-}
-
-
-sub readInt {
-    my ($from) = @_;
-    return unpack("L<x4", readN(8, $from));
-}
-
-
-sub writeString {
-    my ($s, $to) = @_;
-    my $len = length $s;
-    my $req .= pack("L<x4", $len);
-    $req .= $s;
-    $req .= "\000" x (8 - $len % 8) if $len % 8;
-    syswrite($to, $req) or die;
-}
-
-
-sub copyTo {
-    my ($sshHost, $sshOpts, $storePaths, $compressor, $decompressor,
+sub copyToOpen {
+    my ($from, $to, $sshHost, $storePaths, $compressor, $decompressor,
         $includeOutputs, $dryRun, $sign, $progressViewer, $useSubstitutes) = @_;
 
-    $useSubstitutes = 0 if $dryRun;
+    $useSubstitutes = 0 if $dryRun || !defined $useSubstitutes;
 
     # Get the closure of this path.
     my @closure = reverse(topoSortPaths(computeFSClosure(0, $includeOutputs,
         map { followLinksToStorePath $_ } @{$storePaths})));
 
-    # 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 prevents a race where the remote host
     # garbage-collect paths that are already there. Optionally, ask
     # the remote host to substitute missing paths.
-    syswrite($to, pack("L<x4L<x4L<x4L<x4", 1, 1, $useSubstitutes, scalar @closure)) or die;
-    writeString($_, $to) foreach @closure;
+    syswrite($to, pack("L<x4L<x4L<x4", 1, 1, $useSubstitutes)) or die;
+    writeStrings(\@closure, $to);
 
     # Get back the set of paths that are already valid on the remote host.
     my %present;
@@ -115,22 +66,47 @@ sub copyTo {
 
     } else {
         exportPaths(fileno($to), $sign, @missing);
-        close $to;
     }
 
     readInt($from) == 1 or die "remote machine \`$sshHost' failed to import closure\n";
 }
 
 
+sub copyTo {
+    my ($sshHost, $sshOpts, $storePaths, $compressor, $decompressor,
+        $includeOutputs, $dryRun, $sign, $progressViewer, $useSubstitutes) = @_;
+
+    # Connect to the remote host.
+    my ($from, $to);
+    eval {
+        ($from, $to) = connectToRemoteNix($sshHost, $sshOpts);
+    };
+    if ($@) {
+        chomp $@;
+        warn "$@; falling back to old closure copying method\n";
+        return oldCopyTo(@_);
+    }
+
+    copyToOpen($from, $to, $sshHost, $storePaths, $compressor, $decompressor,
+               $includeOutputs, $dryRun, $sign, $progressViewer, $useSubstitutes);
+
+    close $to;
+}
+
+
 # For backwards compatibility with Nix <= 1.7. Will be removed
 # eventually.
 sub oldCopyTo {
-    my ($closure, $sshHost, $sshOpts, $storePaths, $compressor, $decompressor,
+    my ($sshHost, $sshOpts, $storePaths, $compressor, $decompressor,
         $includeOutputs, $dryRun, $sign, $progressViewer, $useSubstitutes) = @_;
 
+    # Get the closure of this path.
+    my @closure = reverse(topoSortPaths(computeFSClosure(0, $includeOutputs,
+        map { followLinksToStorePath $_ } @{$storePaths})));
+
     # Optionally use substitutes on the remote host.
     if (!$dryRun && $useSubstitutes) {
-        system "ssh $sshHost @{$sshOpts} nix-store -r --ignore-unknown @$closure";
+        system "ssh $sshHost @{$sshOpts} nix-store -r --ignore-unknown @closure";
         # Ignore exit status because this is just an optimisation.
     }
 
@@ -140,8 +116,8 @@ sub oldCopyTo {
     # target having this option yet.
     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/perl/lib/Nix/SSH.pm b/perl/lib/Nix/SSH.pm
index 584c44500981..c8792043c20c 100644
--- a/perl/lib/Nix/SSH.pm
+++ b/perl/lib/Nix/SSH.pm
@@ -1,5 +1,16 @@
+package Nix::SSH;
+
 use strict;
 use File::Temp qw(tempdir);
+use IPC::Open2;
+
+our @ISA = qw(Exporter);
+our @EXPORT = qw(
+  sshOpts openSSHConnection closeSSHConnection
+  readN readInt writeInt writeString writeStrings
+  connectToRemoteNix
+);
+
 
 our @sshOpts = split ' ', ($ENV{"NIX_SSHOPTS"} or "");
 
@@ -8,6 +19,7 @@ push @sshOpts, "-x";
 my $sshStarted = 0;
 my $sshHost;
 
+
 # Open a master SSH connection to `host', unless there already is a
 # running master connection (as determined by `-O check').
 sub openSSHConnection {
@@ -18,7 +30,7 @@ sub openSSHConnection {
 
     my $tmpDir = tempdir("nix-ssh.XXXXXX", CLEANUP => 1, TMPDIR => 1)
         or die "cannot create a temporary directory";
-    
+
     push @sshOpts, "-S", "$tmpDir/control";
 
     # Start the master.  We can't use the `-f' flag (fork into
@@ -39,6 +51,7 @@ sub openSSHConnection {
     return 0;
 }
 
+
 # Tell the master SSH client to exit.
 sub closeSSHConnection {
     if ($sshStarted) {
@@ -48,6 +61,70 @@ sub closeSSHConnection {
     }
 }
 
+
+sub readN {
+    my ($bytes, $from) = @_;
+    my $res = "";
+    while ($bytes > 0) {
+        my $s;
+        my $n = sysread($from, $s, $bytes);
+        die "I/O error reading from remote side\n" if !defined $n;
+        die "got EOF while expecting $bytes bytes from remote side\n" if !$n;
+        $bytes -= $n;
+        $res .= $s;
+    }
+    return $res;
+}
+
+
+sub readInt {
+    my ($from) = @_;
+    return unpack("L<x4", readN(8, $from));
+}
+
+
+sub writeInt {
+    my ($n, $to) = @_;
+    syswrite($to, pack("L<x4", $n)) or die;
+}
+
+
+sub writeString {
+    my ($s, $to) = @_;
+    my $len = length $s;
+    my $req .= pack("L<x4", $len);
+    $req .= $s;
+    $req .= "\000" x (8 - $len % 8) if $len % 8;
+    syswrite($to, $req) or die;
+}
+
+
+sub writeStrings {
+    my ($ss, $to) = @_;
+    writeInt(scalar(@{$ss}), $to);
+    writeString($_, $to) foreach @{$ss};
+}
+
+
+sub connectToRemoteNix {
+    my ($sshHost, $sshOpts) = @_;
+
+    # 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.
+    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;
+
+    return ($from, $to, $pid);
+}
+
+
 END { my $saved = $?; closeSSHConnection; $? = $saved; }
 
-return 1;
+1;
diff --git a/perl/lib/Nix/Store.pm b/perl/lib/Nix/Store.pm
index 191116ee5637..89cfaefa5fd4 100644
--- a/perl/lib/Nix/Store.pm
+++ b/perl/lib/Nix/Store.pm
@@ -15,7 +15,7 @@ our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
 our @EXPORT = qw(
     isValidPath queryReferences queryPathInfo queryDeriver queryPathHash
     queryPathFromHashPart
-    topoSortPaths computeFSClosure followLinksToStorePath exportPaths
+    topoSortPaths computeFSClosure followLinksToStorePath exportPaths importPaths
     hashPath hashFile hashString
     addToStore makeFixedOutputPath
     derivationFromPath
diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs
index 07ccebe62f51..ff90616d3766 100644
--- a/perl/lib/Nix/Store.xs
+++ b/perl/lib/Nix/Store.xs
@@ -179,6 +179,17 @@ void exportPaths(int fd, int sign, ...)
         }
 
 
+void importPaths(int fd)
+    PPCODE:
+        try {
+            doInit();
+            FdSource source(fd);
+            store->importPaths(false, source);
+        } catch (Error & e) {
+            croak(e.what());
+        }
+
+
 SV * hashPath(char * algo, int base32, char * path)
     PPCODE:
         try {
diff --git a/perl/local.mk b/perl/local.mk
index 74c054e7133f..564683dffee2 100644
--- a/perl/local.mk
+++ b/perl/local.mk
@@ -27,7 +27,7 @@ ifeq ($(perlbindings), yes)
 
   Store_CXXFLAGS = \
     -I$(shell $(perl) -e 'use Config; print $$Config{archlibexp};')/CORE \
-    -D_FILE_OFFSET_BITS=64
+    -D_FILE_OFFSET_BITS=64 -Wno-unused-variable -Wno-literal-suffix
 
   Store_ALLOW_UNDEFINED = 1
 
diff --git a/scripts/build-remote.pl.in b/scripts/build-remote.pl.in
index 6dfa16d5cbda..687b0e131066 100755
--- a/scripts/build-remote.pl.in
+++ b/scripts/build-remote.pl.in
@@ -4,7 +4,7 @@ use Fcntl qw(:DEFAULT :flock);
 use English '-no_match_vars';
 use IO::Handle;
 use Nix::Config;
-use Nix::SSH qw/sshOpts openSSHConnection/;
+use Nix::SSH;
 use Nix::CopyClosure;
 use Nix::Store;
 no warnings('once');
@@ -90,6 +90,7 @@ if (defined $conf && -e $conf) {
 
 # Wait for the calling process to ask us whether we can build some derivation.
 my ($drvPath, $hostName, $slotLock);
+my ($from, $to);
 
 REQ: while (1) {
     $_ = <STDIN> || exit 0;
@@ -195,13 +196,15 @@ REQ: while (1) {
         # Connect to the selected machine.
         @sshOpts = ("-i", $machine->{sshKeys}, "-x");
         $hostName = $machine->{hostName};
-        if (openSSHConnection($hostName)) {
-            last REQ if system("ssh $hostName @sshOpts nix-builds-inhibited < /dev/null > /dev/null 2>&1") != 0;
-            warn "machine `$hostName' is refusing builds, trying other available machines...\n";
-            closeSSHConnection;
-        } else {
-            warn "unable to open SSH connection to `$hostName', trying other available machines...\n";
-        }
+        eval {
+            ($from, $to) = connectToRemoteNix($hostName, \@sshOpts);
+            # FIXME: check if builds are inhibited.
+        };
+        last REQ unless $@;
+        print STDERR "$@";
+        warn "unable to open SSH connection to `$hostName', trying other available machines...\n";
+        $from = undef;
+        $to = undef;
         $machine->{enabled} = 0;
     }
 }
@@ -220,18 +223,6 @@ my $maybeSign = "";
 $maybeSign = "--sign" if -e "$Nix::Config::confDir/signing-key.sec";
 
 
-# Register the derivation as a temporary GC root.  Note that $PPID is
-# the PID of the remote SSH process, which, due to the use of a
-# persistant SSH connection, should be the same across all remote
-# command invocations for this session.
-my $rootsDir = "@localstatedir@/nix/gcroots/tmp";
-system("ssh $hostName @sshOpts 'mkdir -m 1777 -p $rootsDir; ln -sfn $drvPath $rootsDir/\$PPID.drv'");
-
-sub removeRoots {
-    system("ssh $hostName @sshOpts 'rm -f $rootsDir/\$PPID.drv $rootsDir/\$PPID.out'");
-}
-
-
 # Copy the derivation and its dependencies to the build machine.  This
 # is guarded by an exclusive lock per machine to prevent multiple
 # build-remote instances from copying to a machine simultaneously.
@@ -255,48 +246,33 @@ if ($@) {
     print STDERR "somebody is hogging $uploadLock, continuing...\n";
     unlink $uploadLock;
 }
-Nix::CopyClosure::copyTo($hostName, [ @sshOpts ], [ $drvPath, @inputs ], "", "", 0, 0, $maybeSign ne "", "");
+Nix::CopyClosure::copyToOpen($from, $to, $hostName, [ $drvPath, @inputs ], "", "", 0, 0, $maybeSign ne "", "");
 close UPLOADLOCK;
 
 
 # Perform the build.
-my $buildFlags =
-    "--max-silent-time $maxSilentTime --option build-timeout $buildTimeout"
-    . " --fallback --add-root $rootsDir/\$PPID.out --quiet"
-    . " --option build-keep-log false --option build-use-substitutes false";
-
-# We let the remote side kill its process group when the connection is
-# closed unexpectedly.  This is necessary to ensure that no processes
-# are left running on the remote system if the local Nix process is
-# killed.  (SSH itself doesn't kill child processes if the connection
-# is interrupted unless the `-tt' flag is used to force a pseudo-tty,
-# in which case every child receives SIGHUP; however, `-tt' doesn't
-# work on some platforms when connection sharing is used.)
 print STDERR "building `$drvPath' on `$hostName'\n";
-pipe STDIN, DUMMY; # make sure we have a readable STDIN
-if (system("exec ssh $hostName @sshOpts '(read; kill -INT -\$\$) <&0 & exec nix-store -r $drvPath $buildFlags > /dev/null' 2>&4") != 0) {
+writeInt(6, $to) or die; # == cmdBuildPaths
+writeStrings([$drvPath], $to);
+writeInt($maxSilentTime, $to);
+writeInt($buildTimeout, $to);
+my $res = readInt($from);
+if ($res != 0) {
     # Note that if we get exit code 100 from `nix-store -r', it
     # denotes a permanent build failure (as opposed to an SSH problem
     # or a temporary Nix problem).  We propagate this to the caller to
     # allow it to distinguish between transient and permanent
     # failures.
-    my $res = $? >> 8;
     print STDERR "build of `$drvPath' on `$hostName' failed with exit code $res\n";
-    removeRoots;
     exit $res;
 }
 
-#print "build of `$drvPath' on `$hostName' succeeded\n";
-
 
 # Copy the output from the build machine.
 my @outputs2 = grep { !isValidPath($_) } @outputs;
 if (scalar @outputs2 > 0) {
-    system("exec ssh $hostName @sshOpts 'nix-store --export @outputs2'" .
-           "| NIX_HELD_LOCKS='@outputs2' @bindir@/nix-store --import > /dev/null") == 0
-        or die("cannot copy paths " . join(", ", @outputs) . " from `$hostName': $?");
+    writeInt(5, $to) or die; # == cmdExportPaths
+    writeStrings(\@outputs2, $to);
+    $ENV{'NIX_HELD_LOCKS'} = "@outputs2"; # FIXME: ugly
+    importPaths(fileno($from));
 }
-
-
-# Get rid of the temporary GC roots.
-removeRoots;
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index 3b021bb2a50c..f566ccf53122 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -35,6 +35,7 @@ template<class T> T readStorePaths(Source & from)
 }
 
 template PathSet readStorePaths(Source & from);
+template Paths readStorePaths(Source & from);
 
 
 RemoteStore::RemoteStore()
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index f31eb0e29a36..0196c2fc1873 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -928,7 +928,6 @@ static void opServe(Strings opFlags, Strings opArgs)
                 }
 
                 writeStrings(store->queryValidPaths(paths), out);
-                out.flush();
                 break;
             }
 
@@ -947,17 +946,15 @@ static void opServe(Strings opFlags, Strings opArgs)
                     writeLongLong(info.narSize, out);
                 }
                 writeString("", out);
-                out.flush();
                 break;
             }
 
             case cmdDumpStorePath:
                 dumpPath(readStorePath(in), out);
-                out.flush();
                 break;
 
             case cmdImportPaths: {
-                if (!writeAllowed) throw Error("importing paths not allowed");
+                if (!writeAllowed) throw Error("importing paths is not allowed");
                 string compression = readString(in);
 
                 if (compression != "") {
@@ -986,7 +983,6 @@ static void opServe(Strings opFlags, Strings opArgs)
                     store->importPaths(false, in);
 
                 writeInt(1, out); // indicate success
-                out.flush();
 
                 /* The decompressor will have left stdin in an
                    undefined state, so we can't continue. */
@@ -995,9 +991,40 @@ static void opServe(Strings opFlags, Strings opArgs)
                 break;
             }
 
+            case cmdExportPaths: {
+                exportPaths(*store, readStorePaths<Paths>(in), false, out);
+                break;
+            }
+
+            case cmdBuildPaths: {
+                /* Used by build-remote.pl. */
+                if (!writeAllowed) throw Error("building paths is not allowed");
+                PathSet paths = readStorePaths<PathSet>(in);
+
+                // FIXME: changing options here doesn't work if we're
+                // building through the daemon.
+                verbosity = lvlError;
+                settings.keepLog = false;
+                settings.useSubstitutes = false;
+                settings.maxSilentTime = readInt(in);
+                settings.buildTimeout = readInt(in);
+
+                int res = 0;
+                try {
+                    store->buildPaths(paths);
+                } catch (Error & e) {
+                    printMsg(lvlError, format("error: %1%") % e.msg());
+                    res = e.status;
+                }
+                writeInt(res, out);
+                break;
+            }
+
             default:
                 throw Error(format("unknown serve command %1%") % cmd);
         }
+
+        out.flush();
     }
 }
 
diff --git a/src/nix-store/serve-protocol.hh b/src/nix-store/serve-protocol.hh
index 07ff4f7a7cc4..eb13b46e51bd 100644
--- a/src/nix-store/serve-protocol.hh
+++ b/src/nix-store/serve-protocol.hh
@@ -14,6 +14,8 @@ typedef enum {
     cmdQueryPathInfos = 2,
     cmdDumpStorePath = 3,
     cmdImportPaths = 4,
+    cmdExportPaths = 5,
+    cmdBuildPaths = 6,
 } ServeCommand;
 
 }