2014-07-11 16:02:19 +02:00
|
|
|
|
package Nix::SSH;
|
|
|
|
|
|
2010-02-03 16:34:52 +01:00
|
|
|
|
use strict;
|
|
|
|
|
use File::Temp qw(tempdir);
|
2014-07-11 16:02:19 +02:00
|
|
|
|
use IPC::Open2;
|
|
|
|
|
|
|
|
|
|
our @ISA = qw(Exporter);
|
|
|
|
|
our @EXPORT = qw(
|
|
|
|
|
sshOpts openSSHConnection closeSSHConnection
|
|
|
|
|
readN readInt writeInt writeString writeStrings
|
|
|
|
|
connectToRemoteNix
|
|
|
|
|
);
|
|
|
|
|
|
2010-02-03 16:34:52 +01:00
|
|
|
|
|
|
|
|
|
our @sshOpts = split ' ', ($ENV{"NIX_SSHOPTS"} or "");
|
|
|
|
|
|
2010-12-15 15:25:54 +01:00
|
|
|
|
push @sshOpts, "-x";
|
|
|
|
|
|
2010-02-03 16:34:52 +01:00
|
|
|
|
my $sshStarted = 0;
|
|
|
|
|
my $sshHost;
|
|
|
|
|
|
2014-07-11 16:02:19 +02:00
|
|
|
|
|
2010-02-03 16:34:52 +01:00
|
|
|
|
# Open a master SSH connection to `host', unless there already is a
|
|
|
|
|
# running master connection (as determined by `-O check').
|
|
|
|
|
sub openSSHConnection {
|
|
|
|
|
my ($host) = @_;
|
|
|
|
|
die if $sshStarted;
|
|
|
|
|
$sshHost = $host;
|
2010-02-03 21:35:37 +01:00
|
|
|
|
return 1 if system("ssh $sshHost @sshOpts -O check 2> /dev/null") == 0;
|
2010-02-03 16:34:52 +01:00
|
|
|
|
|
|
|
|
|
my $tmpDir = tempdir("nix-ssh.XXXXXX", CLEANUP => 1, TMPDIR => 1)
|
|
|
|
|
or die "cannot create a temporary directory";
|
2014-07-11 16:02:19 +02:00
|
|
|
|
|
2010-02-03 16:34:52 +01:00
|
|
|
|
push @sshOpts, "-S", "$tmpDir/control";
|
2010-02-04 03:38:40 +01:00
|
|
|
|
|
|
|
|
|
# Start the master. We can't use the `-f' flag (fork into
|
|
|
|
|
# background after establishing the connection) because then the
|
|
|
|
|
# child continues to run if we are killed. So instead make SSH
|
|
|
|
|
# print "started" when it has established the connection, and wait
|
|
|
|
|
# until we see that.
|
2010-12-07 13:33:42 +01:00
|
|
|
|
open SSHPIPE, "ssh $sshHost @sshOpts -M -N -o LocalCommand='echo started' -o PermitLocalCommand=yes |" or die;
|
2010-08-24 16:27:07 +02:00
|
|
|
|
|
2010-12-07 13:33:42 +01:00
|
|
|
|
while (<SSHPIPE>) {
|
2010-02-04 03:38:40 +01:00
|
|
|
|
chomp;
|
2010-08-24 16:27:07 +02:00
|
|
|
|
if ($_ eq "started") {
|
|
|
|
|
$sshStarted = 1;
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
2010-02-04 03:38:40 +01:00
|
|
|
|
}
|
2010-08-24 16:27:07 +02:00
|
|
|
|
|
|
|
|
|
return 0;
|
2010-02-03 16:34:52 +01:00
|
|
|
|
}
|
|
|
|
|
|
2014-07-11 16:02:19 +02:00
|
|
|
|
|
2010-02-03 16:34:52 +01:00
|
|
|
|
# Tell the master SSH client to exit.
|
|
|
|
|
sub closeSSHConnection {
|
|
|
|
|
if ($sshStarted) {
|
|
|
|
|
system("ssh $sshHost @sshOpts -O exit 2> /dev/null") == 0
|
|
|
|
|
or warn "unable to stop SSH master: $?";
|
2013-05-10 02:38:05 +02:00
|
|
|
|
$sshStarted = 0;
|
2010-02-03 16:34:52 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-11 16:02:19 +02:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2010-02-04 03:05:22 +01:00
|
|
|
|
END { my $saved = $?; closeSSHConnection; $? = $saved; }
|
2010-02-03 16:34:52 +01:00
|
|
|
|
|
2014-07-11 16:02:19 +02:00
|
|
|
|
1;
|