diff options
Diffstat (limited to 'third_party/nix/perl')
-rw-r--r-- | third_party/nix/perl/MANIFEST | 7 | ||||
-rw-r--r-- | third_party/nix/perl/Makefile | 7 | ||||
-rw-r--r-- | third_party/nix/perl/Makefile.config.in | 18 | ||||
-rw-r--r-- | third_party/nix/perl/configure.ac | 101 | ||||
-rw-r--r-- | third_party/nix/perl/lib/Nix/Config.pm.in | 34 | ||||
-rw-r--r-- | third_party/nix/perl/lib/Nix/CopyClosure.pm | 61 | ||||
-rw-r--r-- | third_party/nix/perl/lib/Nix/Manifest.pm | 325 | ||||
-rw-r--r-- | third_party/nix/perl/lib/Nix/SSH.pm | 110 | ||||
-rw-r--r-- | third_party/nix/perl/lib/Nix/Store.pm | 95 | ||||
-rw-r--r-- | third_party/nix/perl/lib/Nix/Store.xs | 346 | ||||
-rw-r--r-- | third_party/nix/perl/lib/Nix/Utils.pm | 47 | ||||
-rw-r--r-- | third_party/nix/perl/local.mk | 43 |
12 files changed, 1194 insertions, 0 deletions
diff --git a/third_party/nix/perl/MANIFEST b/third_party/nix/perl/MANIFEST new file mode 100644 index 000000000000..08897647c978 --- /dev/null +++ b/third_party/nix/perl/MANIFEST @@ -0,0 +1,7 @@ +Changes +Makefile.PL +MANIFEST +Nix.xs +README +t/Nix.t +lib/Nix.pm diff --git a/third_party/nix/perl/Makefile b/third_party/nix/perl/Makefile new file mode 100644 index 000000000000..f36f5d0e9d88 --- /dev/null +++ b/third_party/nix/perl/Makefile @@ -0,0 +1,7 @@ +makefiles = local.mk + +GLOBAL_CXXFLAGS += -g -Wall + +-include Makefile.config + +include mk/lib.mk diff --git a/third_party/nix/perl/Makefile.config.in b/third_party/nix/perl/Makefile.config.in new file mode 100644 index 000000000000..c87d4817e172 --- /dev/null +++ b/third_party/nix/perl/Makefile.config.in @@ -0,0 +1,18 @@ +CC = @CC@ +CFLAGS = @CFLAGS@ +CXX = @CXX@ +CXXFLAGS = @CXXFLAGS@ +HAVE_SODIUM = @HAVE_SODIUM@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +SODIUM_LIBS = @SODIUM_LIBS@ +NIX_CFLAGS = @NIX_CFLAGS@ +NIX_LIBS = @NIX_LIBS@ +nixbindir = @nixbindir@ +curl = @curl@ +nixlibexecdir = @nixlibexecdir@ +nixlocalstatedir = @nixlocalstatedir@ +perl = @perl@ +perllibdir = @perllibdir@ +nixstoredir = @nixstoredir@ +nixsysconfdir = @nixsysconfdir@ diff --git a/third_party/nix/perl/configure.ac b/third_party/nix/perl/configure.ac new file mode 100644 index 000000000000..e8e3610a860d --- /dev/null +++ b/third_party/nix/perl/configure.ac @@ -0,0 +1,101 @@ +AC_INIT(nix-perl, m4_esyscmd([bash -c "echo -n $(cat ../.version)$VERSION_SUFFIX"])) +AC_CONFIG_SRCDIR(MANIFEST) +AC_CONFIG_AUX_DIR(../config) + +# Set default flags for nix (as per AC_PROG_CC/CXX docs), +# while still allowing the user to override them from the command line. +: ${CFLAGS="-O3"} +: ${CXXFLAGS="-O3"} +AC_PROG_CC +AC_PROG_CXX +AX_CXX_COMPILE_STDCXX_11 + +# Use 64-bit file system calls so that we can support files > 2 GiB. +AC_SYS_LARGEFILE + +AC_DEFUN([NEED_PROG], +[ +AC_PATH_PROG($1, $2) +if test -z "$$1"; then + AC_MSG_ERROR([$2 is required]) +fi +]) + +NEED_PROG(perl, perl) +NEED_PROG(curl, curl) +NEED_PROG(bzip2, bzip2) +NEED_PROG(xz, xz) + +# Test that Perl has the open/fork feature (Perl 5.8.0 and beyond). +AC_MSG_CHECKING([whether Perl is recent enough]) +if ! $perl -e 'open(FOO, "-|", "true"); while (<FOO>) { print; }; close FOO or die;'; then + AC_MSG_RESULT(no) + AC_MSG_ERROR([Your Perl version is too old. Nix requires Perl 5.8.0 or newer.]) +fi +AC_MSG_RESULT(yes) + + +# Figure out where to install Perl modules. +AC_MSG_CHECKING([for the Perl installation prefix]) +perlversion=$($perl -e 'use Config; print $Config{version};') +perlarchname=$($perl -e 'use Config; print $Config{archname};') +AC_SUBST(perllibdir, [${libdir}/perl5/site_perl/$perlversion/$perlarchname]) +AC_MSG_RESULT($perllibdir) + +# Look for libsodium, an optional dependency. +PKG_CHECK_MODULES([SODIUM], [libsodium], + [AC_DEFINE([HAVE_SODIUM], [1], [Whether to use libsodium for cryptography.]) + CXXFLAGS="$SODIUM_CFLAGS $CXXFLAGS" + have_sodium=1], [have_sodium=]) +AC_SUBST(HAVE_SODIUM, [$have_sodium]) + +# Check for the required Perl dependencies (DBI and DBD::SQLite). +perlFlags="-I$perllibdir" + +AC_ARG_WITH(dbi, AC_HELP_STRING([--with-dbi=PATH], + [prefix of the Perl DBI library]), + perlFlags="$perlFlags -I$withval") + +AC_ARG_WITH(dbd-sqlite, AC_HELP_STRING([--with-dbd-sqlite=PATH], + [prefix of the Perl DBD::SQLite library]), + perlFlags="$perlFlags -I$withval") + +AC_MSG_CHECKING([whether DBD::SQLite works]) +if ! $perl $perlFlags -e 'use DBI; use DBD::SQLite;' 2>&5; then + AC_MSG_RESULT(no) + AC_MSG_FAILURE([The Perl modules DBI and/or DBD::SQLite are missing.]) +fi +AC_MSG_RESULT(yes) + +AC_SUBST(perlFlags) + +PKG_CHECK_MODULES([NIX], [nix-store]) + +NEED_PROG([NIX_INSTANTIATE_PROGRAM], [nix-instantiate]) + +# Get nix configure values +nixbindir=$("$NIX_INSTANTIATE_PROGRAM" --eval '<nix/config.nix>' -A nixBinDir | tr -d \") +nixlibexecdir=$("$NIX_INSTANTIATE_PROGRAM" --eval '<nix/config.nix>' -A nixLibexecDir | tr -d \") +nixlocalstatedir=$("$NIX_INSTANTIATE_PROGRAM" --eval '<nix/config.nix>' -A nixLocalstateDir | tr -d \") +nixsysconfdir=$("$NIX_INSTANTIATE_PROGRAM" --eval '<nix/config.nix>' -A nixSysconfDir | tr -d \") +nixstoredir=$("$NIX_INSTANTIATE_PROGRAM" --eval '<nix/config.nix>' -A nixStoreDir | tr -d \") +AC_SUBST(nixbindir) +AC_SUBST(nixlibexecdir) +AC_SUBST(nixlocalstatedir) +AC_SUBST(nixsysconfdir) +AC_SUBST(nixstoredir) + +# Expand all variables in config.status. +test "$prefix" = NONE && prefix=$ac_default_prefix +test "$exec_prefix" = NONE && exec_prefix='${prefix}' +for name in $ac_subst_vars; do + declare $name="$(eval echo "${!name}")" + declare $name="$(eval echo "${!name}")" + declare $name="$(eval echo "${!name}")" +done + +rm -f Makefile.config +ln -sfn ../mk mk + +AC_CONFIG_FILES([]) +AC_OUTPUT diff --git a/third_party/nix/perl/lib/Nix/Config.pm.in b/third_party/nix/perl/lib/Nix/Config.pm.in new file mode 100644 index 000000000000..67a20c3f41d7 --- /dev/null +++ b/third_party/nix/perl/lib/Nix/Config.pm.in @@ -0,0 +1,34 @@ +package Nix::Config; + +use MIME::Base64; + +$version = "@PACKAGE_VERSION@"; + +$binDir = $ENV{"NIX_BIN_DIR"} || "@nixbindir@"; +$libexecDir = $ENV{"NIX_LIBEXEC_DIR"} || "@nixlibexecdir@"; +$stateDir = $ENV{"NIX_STATE_DIR"} || "@nixlocalstatedir@/nix"; +$logDir = $ENV{"NIX_LOG_DIR"} || "@nixlocalstatedir@/log/nix"; +$confDir = $ENV{"NIX_CONF_DIR"} || "@nixsysconfdir@/nix"; +$storeDir = $ENV{"NIX_STORE_DIR"} || "@nixstoredir@"; + +$bzip2 = "@bzip2@"; +$xz = "@xz@"; +$curl = "@curl@"; + +$useBindings = 1; + +%config = (); + +sub readConfig { + my $config = "$confDir/nix.conf"; + return unless -f $config; + + open CONFIG, "<$config" or die "cannot open '$config'"; + while (<CONFIG>) { + /^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next; + $config{$1} = $2; + } + close CONFIG; +} + +return 1; diff --git a/third_party/nix/perl/lib/Nix/CopyClosure.pm b/third_party/nix/perl/lib/Nix/CopyClosure.pm new file mode 100644 index 000000000000..902ee1a1bc9f --- /dev/null +++ b/third_party/nix/perl/lib/Nix/CopyClosure.pm @@ -0,0 +1,61 @@ +package Nix::CopyClosure; + +use utf8; +use strict; +use Nix::Config; +use Nix::Store; +use Nix::SSH; +use List::Util qw(sum); +use IPC::Open2; + + +sub copyToOpen { + my ($from, $to, $sshHost, $storePaths, $includeOutputs, $dryRun, $useSubstitutes) = @_; + + $useSubstitutes = 0 if $dryRun || !defined $useSubstitutes; + + # Get the closure of this path. + my @closure = reverse(topoSortPaths(computeFSClosure(0, $includeOutputs, + map { followLinksToStorePath $_ } @{$storePaths}))); + + # 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<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; + $present{$_} = 1 foreach readStrings($from); + + my @missing = grep { !$present{$_} } @closure; + return if !@missing; + + my $missingSize = 0; + $missingSize += (queryPathInfo($_, 1))[3] foreach @missing; + + printf STDERR "copying %d missing paths (%.2f MiB) to '$sshHost'...\n", + scalar(@missing), $missingSize / (1024**2); + return if $dryRun; + + # Send the "import paths" command. + syswrite($to, pack("L<x4", 4)) or die; + exportPaths(fileno($to), @missing); + readInt($from) == 1 or die "remote machine '$sshHost' failed to import closure\n"; +} + + +sub copyTo { + my ($sshHost, $storePaths, $includeOutputs, $dryRun, $useSubstitutes) = @_; + + # Connect to the remote host. + my ($from, $to) = connectToRemoteNix($sshHost, []); + + copyToOpen($from, $to, $sshHost, $storePaths, $includeOutputs, $dryRun, $useSubstitutes); + + close $to; +} + + +1; diff --git a/third_party/nix/perl/lib/Nix/Manifest.pm b/third_party/nix/perl/lib/Nix/Manifest.pm new file mode 100644 index 000000000000..6438398e1766 --- /dev/null +++ b/third_party/nix/perl/lib/Nix/Manifest.pm @@ -0,0 +1,325 @@ +package Nix::Manifest; + +use utf8; +use strict; +use DBI; +use DBD::SQLite; +use Cwd; +use File::stat; +use File::Path; +use Fcntl ':flock'; +use MIME::Base64; +use Nix::Config; +use Nix::Store; + +our @ISA = qw(Exporter); +our @EXPORT = qw(readManifest writeManifest addPatch parseNARInfo fingerprintPath); + + +sub addNAR { + my ($narFiles, $storePath, $info) = @_; + + $$narFiles{$storePath} = [] + unless defined $$narFiles{$storePath}; + + my $narFileList = $$narFiles{$storePath}; + + my $found = 0; + foreach my $narFile (@{$narFileList}) { + $found = 1 if $narFile->{url} eq $info->{url}; + } + + push @{$narFileList}, $info if !$found; +} + + +sub addPatch { + my ($patches, $storePath, $patch) = @_; + + $$patches{$storePath} = [] + unless defined $$patches{$storePath}; + + my $patchList = $$patches{$storePath}; + + my $found = 0; + foreach my $patch2 (@{$patchList}) { + $found = 1 if + $patch2->{url} eq $patch->{url} && + $patch2->{basePath} eq $patch->{basePath}; + } + + push @{$patchList}, $patch if !$found; + + return !$found; +} + + +sub readManifest_ { + my ($manifest, $addNAR, $addPatch) = @_; + + # Decompress the manifest if necessary. + if ($manifest =~ /\.bz2$/) { + open MANIFEST, "$Nix::Config::bzip2 -d < $manifest |" + or die "cannot decompress '$manifest': $!"; + } else { + open MANIFEST, "<$manifest" + or die "cannot open '$manifest': $!"; + } + + my $inside = 0; + my $type; + + my $manifestVersion = 2; + + my ($storePath, $url, $hash, $size, $basePath, $baseHash, $patchType); + my ($narHash, $narSize, $references, $deriver, $copyFrom, $system, $compressionType); + + while (<MANIFEST>) { + chomp; + s/\#.*$//g; + next if (/^$/); + + if (!$inside) { + + if (/^\s*(\w*)\s*\{$/) { + $type = $1; + $type = "narfile" if $type eq ""; + $inside = 1; + undef $storePath; + undef $url; + undef $hash; + undef $size; + undef $narHash; + undef $narSize; + undef $basePath; + undef $baseHash; + undef $patchType; + undef $system; + $references = ""; + $deriver = ""; + $compressionType = "bzip2"; + } + + } else { + + if (/^\}$/) { + $inside = 0; + + if ($type eq "narfile") { + &$addNAR($storePath, + { url => $url, hash => $hash, size => $size + , narHash => $narHash, narSize => $narSize + , references => $references + , deriver => $deriver + , system => $system + , compressionType => $compressionType + }); + } + + elsif ($type eq "patch") { + &$addPatch($storePath, + { url => $url, hash => $hash, size => $size + , basePath => $basePath, baseHash => $baseHash + , narHash => $narHash, narSize => $narSize + , patchType => $patchType + }); + } + + } + + elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { $storePath = $1; } + elsif (/^\s*CopyFrom:\s*(\/\S+)\s*$/) { $copyFrom = $1; } + elsif (/^\s*Hash:\s*(\S+)\s*$/) { $hash = $1; } + elsif (/^\s*URL:\s*(\S+)\s*$/) { $url = $1; } + elsif (/^\s*Compression:\s*(\S+)\s*$/) { $compressionType = $1; } + elsif (/^\s*Size:\s*(\d+)\s*$/) { $size = $1; } + elsif (/^\s*BasePath:\s*(\/\S+)\s*$/) { $basePath = $1; } + elsif (/^\s*BaseHash:\s*(\S+)\s*$/) { $baseHash = $1; } + elsif (/^\s*Type:\s*(\S+)\s*$/) { $patchType = $1; } + elsif (/^\s*NarHash:\s*(\S+)\s*$/) { $narHash = $1; } + elsif (/^\s*NarSize:\s*(\d+)\s*$/) { $narSize = $1; } + elsif (/^\s*References:\s*(.*)\s*$/) { $references = $1; } + elsif (/^\s*Deriver:\s*(\S+)\s*$/) { $deriver = $1; } + elsif (/^\s*ManifestVersion:\s*(\d+)\s*$/) { $manifestVersion = $1; } + elsif (/^\s*System:\s*(\S+)\s*$/) { $system = $1; } + + # Compatibility; + elsif (/^\s*NarURL:\s*(\S+)\s*$/) { $url = $1; } + elsif (/^\s*MD5:\s*(\S+)\s*$/) { $hash = "md5:$1"; } + + } + } + + close MANIFEST; + + return $manifestVersion; +} + + +sub readManifest { + my ($manifest, $narFiles, $patches) = @_; + readManifest_($manifest, + sub { addNAR($narFiles, @_); }, + sub { addPatch($patches, @_); } ); +} + + +sub writeManifest { + my ($manifest, $narFiles, $patches, $noCompress) = @_; + + open MANIFEST, ">$manifest.tmp"; # !!! check exclusive + + print MANIFEST "version {\n"; + print MANIFEST " ManifestVersion: 3\n"; + print MANIFEST "}\n"; + + foreach my $storePath (sort (keys %{$narFiles})) { + my $narFileList = $$narFiles{$storePath}; + foreach my $narFile (@{$narFileList}) { + print MANIFEST "{\n"; + print MANIFEST " StorePath: $storePath\n"; + print MANIFEST " NarURL: $narFile->{url}\n"; + print MANIFEST " Compression: $narFile->{compressionType}\n"; + print MANIFEST " Hash: $narFile->{hash}\n" if defined $narFile->{hash}; + print MANIFEST " Size: $narFile->{size}\n" if defined $narFile->{size}; + print MANIFEST " NarHash: $narFile->{narHash}\n"; + print MANIFEST " NarSize: $narFile->{narSize}\n" if $narFile->{narSize}; + print MANIFEST " References: $narFile->{references}\n" + if defined $narFile->{references} && $narFile->{references} ne ""; + print MANIFEST " Deriver: $narFile->{deriver}\n" + if defined $narFile->{deriver} && $narFile->{deriver} ne ""; + print MANIFEST " System: $narFile->{system}\n" if defined $narFile->{system}; + print MANIFEST "}\n"; + } + } + + foreach my $storePath (sort (keys %{$patches})) { + my $patchList = $$patches{$storePath}; + foreach my $patch (@{$patchList}) { + print MANIFEST "patch {\n"; + print MANIFEST " StorePath: $storePath\n"; + print MANIFEST " NarURL: $patch->{url}\n"; + print MANIFEST " Hash: $patch->{hash}\n"; + print MANIFEST " Size: $patch->{size}\n"; + print MANIFEST " NarHash: $patch->{narHash}\n"; + print MANIFEST " NarSize: $patch->{narSize}\n" if $patch->{narSize}; + print MANIFEST " BasePath: $patch->{basePath}\n"; + print MANIFEST " BaseHash: $patch->{baseHash}\n"; + print MANIFEST " Type: $patch->{patchType}\n"; + print MANIFEST "}\n"; + } + } + + + close MANIFEST; + + rename("$manifest.tmp", $manifest) + or die "cannot rename $manifest.tmp: $!"; + + + # Create a bzipped manifest. + unless (defined $noCompress) { + system("$Nix::Config::bzip2 < $manifest > $manifest.bz2.tmp") == 0 + or die "cannot compress manifest"; + + rename("$manifest.bz2.tmp", "$manifest.bz2") + or die "cannot rename $manifest.bz2.tmp: $!"; + } +} + + +# Return a fingerprint of a store path to be used in binary cache +# signatures. It contains the store path, the base-32 SHA-256 hash of +# the contents of the path, and the references. +sub fingerprintPath { + my ($storePath, $narHash, $narSize, $references) = @_; + die if substr($storePath, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir; + die if substr($narHash, 0, 7) ne "sha256:"; + # Convert hash from base-16 to base-32, if necessary. + $narHash = "sha256:" . convertHash("sha256", substr($narHash, 7), 1) + if length($narHash) == 71; + die if length($narHash) != 59; + foreach my $ref (@{$references}) { + die if substr($ref, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir; + } + return "1;" . $storePath . ";" . $narHash . ";" . $narSize . ";" . join(",", @{$references}); +} + + +# Parse a NAR info file. +sub parseNARInfo { + my ($storePath, $content, $requireValidSig, $location) = @_; + + my ($storePath2, $url, $fileHash, $fileSize, $narHash, $narSize, $deriver, $system, $sig); + my $compression = "bzip2"; + my @refs; + + foreach my $line (split "\n", $content) { + return undef unless $line =~ /^(.*): (.*)$/; + if ($1 eq "StorePath") { $storePath2 = $2; } + elsif ($1 eq "URL") { $url = $2; } + elsif ($1 eq "Compression") { $compression = $2; } + elsif ($1 eq "FileHash") { $fileHash = $2; } + elsif ($1 eq "FileSize") { $fileSize = int($2); } + elsif ($1 eq "NarHash") { $narHash = $2; } + elsif ($1 eq "NarSize") { $narSize = int($2); } + elsif ($1 eq "References") { @refs = split / /, $2; } + elsif ($1 eq "Deriver") { $deriver = $2; } + elsif ($1 eq "System") { $system = $2; } + elsif ($1 eq "Sig") { $sig = $2; } + } + + return undef if $storePath ne $storePath2 || !defined $url || !defined $narHash; + + my $res = + { url => $url + , compression => $compression + , fileHash => $fileHash + , fileSize => $fileSize + , narHash => $narHash + , narSize => $narSize + , refs => [ @refs ] + , deriver => $deriver + , system => $system + }; + + if ($requireValidSig) { + # FIXME: might be useful to support multiple signatures per .narinfo. + + if (!defined $sig) { + warn "NAR info file '$location' lacks a signature; ignoring\n"; + return undef; + } + my ($keyName, $sig64) = split ":", $sig; + return undef unless defined $keyName && defined $sig64; + + my $publicKey = $Nix::Config::binaryCachePublicKeys{$keyName}; + if (!defined $publicKey) { + warn "NAR info file '$location' is signed by unknown key '$keyName'; ignoring\n"; + return undef; + } + + my $fingerprint; + eval { + $fingerprint = fingerprintPath( + $storePath, $narHash, $narSize, + [ map { "$Nix::Config::storeDir/$_" } @refs ]); + }; + if ($@) { + warn "cannot compute fingerprint of '$location'; ignoring\n"; + return undef; + } + + if (!checkSignature($publicKey, decode_base64($sig64), $fingerprint)) { + warn "NAR info file '$location' has an incorrect signature; ignoring\n"; + return undef; + } + + $res->{signedBy} = $keyName; + } + + return $res; +} + + +return 1; diff --git a/third_party/nix/perl/lib/Nix/SSH.pm b/third_party/nix/perl/lib/Nix/SSH.pm new file mode 100644 index 000000000000..490ba0ea991e --- /dev/null +++ b/third_party/nix/perl/lib/Nix/SSH.pm @@ -0,0 +1,110 @@ +package Nix::SSH; + +use utf8; +use strict; +use File::Temp qw(tempdir); +use IPC::Open2; + +our @ISA = qw(Exporter); +our @EXPORT = qw( + @globalSshOpts + readN readInt readString readStrings + writeInt writeString writeStrings + connectToRemoteNix +); + + +our @globalSshOpts = split ' ', ($ENV{"NIX_SSHOPTS"} or ""); + + +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 readString { + my ($from) = @_; + my $len = readInt($from); + my $s = readN($len, $from); + readN(8 - $len % 8, $from) if $len % 8; # skip padding + return $s; +} + + +sub readStrings { + my ($from) = @_; + my $n = readInt($from); + my @res; + push @res, readString($from) while $n--; + return @res; +} + + +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, $extraFlags) = @_; + + $extraFlags ||= ""; + + # Start ‘nix-store --serve’ on the remote host. + my ($from, $to); + # FIXME: don't start a shell, start ssh directly. + my $pid = open2($from, $to, "exec ssh -x -a $sshHost @globalSshOpts @{$sshOpts} nix-store --serve --write $extraFlags"); + + # Do the handshake. + my $magic; + eval { + my $SERVE_MAGIC_1 = 0x390c9deb; # FIXME + my $clientVersion = 0x200; + syswrite($to, pack("L<x4L<x4", $SERVE_MAGIC_1, $clientVersion)) or die; + $magic = readInt($from); + }; + die "unable to connect to '$sshHost'\n" if $@; + die "did not get valid handshake from remote host\n" if $magic != 0x5452eecb; + + my $serverVersion = readInt($from); + die "unsupported server version\n" if $serverVersion < 0x200 || $serverVersion >= 0x300; + + return ($from, $to, $pid); +} + + +1; diff --git a/third_party/nix/perl/lib/Nix/Store.pm b/third_party/nix/perl/lib/Nix/Store.pm new file mode 100644 index 000000000000..d226264d4df3 --- /dev/null +++ b/third_party/nix/perl/lib/Nix/Store.pm @@ -0,0 +1,95 @@ +package Nix::Store; + +use strict; +use warnings; +use Nix::Config; + +require Exporter; + +our @ISA = qw(Exporter); + +our %EXPORT_TAGS = ( 'all' => [ qw( ) ] ); + +our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); + +our @EXPORT = qw( + setVerbosity + isValidPath queryReferences queryPathInfo queryDeriver queryPathHash + queryPathFromHashPart + topoSortPaths computeFSClosure followLinksToStorePath exportPaths importPaths + hashPath hashFile hashString convertHash + signString checkSignature + addToStore makeFixedOutputPath + derivationFromPath + addTempRoot +); + +our $VERSION = '0.15'; + +sub backtick { + open(RES, "-|", @_) or die; + local $/; + my $res = <RES> || ""; + close RES or die; + return $res; +} + +if ($Nix::Config::useBindings) { + require XSLoader; + XSLoader::load('Nix::Store', $VERSION); +} else { + + # Provide slow fallbacks of some functions on platforms that don't + # support the Perl bindings. + + use File::Temp; + use Fcntl qw/F_SETFD/; + + *hashFile = sub { + my ($algo, $base32, $path) = @_; + my $res = backtick("$Nix::Config::binDir/nix-hash", "--flat", $path, "--type", $algo, $base32 ? "--base32" : ()); + chomp $res; + return $res; + }; + + *hashPath = sub { + my ($algo, $base32, $path) = @_; + my $res = backtick("$Nix::Config::binDir/nix-hash", $path, "--type", $algo, $base32 ? "--base32" : ()); + chomp $res; + return $res; + }; + + *hashString = sub { + my ($algo, $base32, $s) = @_; + my $fh = File::Temp->new(); + print $fh $s; + my $res = backtick("$Nix::Config::binDir/nix-hash", $fh->filename, "--type", $algo, $base32 ? "--base32" : ()); + chomp $res; + return $res; + }; + + *addToStore = sub { + my ($srcPath, $recursive, $algo) = @_; + die "not implemented" if $recursive || $algo ne "sha256"; + my $res = backtick("$Nix::Config::binDir/nix-store", "--add", $srcPath); + chomp $res; + return $res; + }; + + *isValidPath = sub { + my ($path) = @_; + my $res = backtick("$Nix::Config::binDir/nix-store", "--check-validity", "--print-invalid", $path); + chomp $res; + return $res ne $path; + }; + + *queryPathHash = sub { + my ($path) = @_; + my $res = backtick("$Nix::Config::binDir/nix-store", "--query", "--hash", $path); + chomp $res; + return $res; + }; +} + +1; +__END__ diff --git a/third_party/nix/perl/lib/Nix/Store.xs b/third_party/nix/perl/lib/Nix/Store.xs new file mode 100644 index 000000000000..ce553bb53ebc --- /dev/null +++ b/third_party/nix/perl/lib/Nix/Store.xs @@ -0,0 +1,346 @@ +#include "config.h" + +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +/* Prevent a clash between some Perl and libstdc++ macros. */ +#undef do_open +#undef do_close + +#include "derivations.hh" +#include "globals.hh" +#include "store-api.hh" +#include "util.hh" +#include "crypto.hh" + +#if HAVE_SODIUM +#include <sodium.h> +#endif + + +using namespace nix; + + +static ref<Store> store() +{ + static std::shared_ptr<Store> _store; + if (!_store) { + try { + loadConfFile(); + settings.lockCPU = false; + _store = openStore(); + } catch (Error & e) { + croak("%s", e.what()); + } + } + return ref<Store>(_store); +} + + +MODULE = Nix::Store PACKAGE = Nix::Store +PROTOTYPES: ENABLE + + +#undef dNOOP // Hack to work around "error: declaration of 'Perl___notused' has a different language linkage" error message on clang. +#define dNOOP + + +void init() + CODE: + store(); + + +void setVerbosity(int level) + CODE: + verbosity = (Verbosity) level; + + +int isValidPath(char * path) + CODE: + try { + RETVAL = store()->isValidPath(path); + } catch (Error & e) { + croak("%s", e.what()); + } + OUTPUT: + RETVAL + + +SV * queryReferences(char * path) + PPCODE: + try { + PathSet paths = store()->queryPathInfo(path)->references; + for (PathSet::iterator i = paths.begin(); i != paths.end(); ++i) + XPUSHs(sv_2mortal(newSVpv(i->c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * queryPathHash(char * path) + PPCODE: + try { + auto s = store()->queryPathInfo(path)->narHash.to_string(); + XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * queryDeriver(char * path) + PPCODE: + try { + auto deriver = store()->queryPathInfo(path)->deriver; + if (deriver == "") XSRETURN_UNDEF; + XPUSHs(sv_2mortal(newSVpv(deriver.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * queryPathInfo(char * path, int base32) + PPCODE: + try { + auto info = store()->queryPathInfo(path); + if (info->deriver == "") + XPUSHs(&PL_sv_undef); + else + XPUSHs(sv_2mortal(newSVpv(info->deriver.c_str(), 0))); + auto s = info->narHash.to_string(base32 ? Base32 : Base16); + XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0))); + mXPUSHi(info->registrationTime); + mXPUSHi(info->narSize); + AV * arr = newAV(); + for (PathSet::iterator i = info->references.begin(); i != info->references.end(); ++i) + av_push(arr, newSVpv(i->c_str(), 0)); + XPUSHs(sv_2mortal(newRV((SV *) arr))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * queryPathFromHashPart(char * hashPart) + PPCODE: + try { + Path path = store()->queryPathFromHashPart(hashPart); + XPUSHs(sv_2mortal(newSVpv(path.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * computeFSClosure(int flipDirection, int includeOutputs, ...) + PPCODE: + try { + PathSet paths; + for (int n = 2; n < items; ++n) + store()->computeFSClosure(SvPV_nolen(ST(n)), paths, flipDirection, includeOutputs); + for (PathSet::iterator i = paths.begin(); i != paths.end(); ++i) + XPUSHs(sv_2mortal(newSVpv(i->c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * topoSortPaths(...) + PPCODE: + try { + PathSet paths; + for (int n = 0; n < items; ++n) paths.insert(SvPV_nolen(ST(n))); + Paths sorted = store()->topoSortPaths(paths); + for (Paths::iterator i = sorted.begin(); i != sorted.end(); ++i) + XPUSHs(sv_2mortal(newSVpv(i->c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * followLinksToStorePath(char * path) + CODE: + try { + RETVAL = newSVpv(store()->followLinksToStorePath(path).c_str(), 0); + } catch (Error & e) { + croak("%s", e.what()); + } + OUTPUT: + RETVAL + + +void exportPaths(int fd, ...) + PPCODE: + try { + Paths paths; + for (int n = 1; n < items; ++n) paths.push_back(SvPV_nolen(ST(n))); + FdSink sink(fd); + store()->exportPaths(paths, sink); + } catch (Error & e) { + croak("%s", e.what()); + } + + +void importPaths(int fd, int dontCheckSigs) + PPCODE: + try { + FdSource source(fd); + store()->importPaths(source, nullptr, dontCheckSigs ? NoCheckSigs : CheckSigs); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * hashPath(char * algo, int base32, char * path) + PPCODE: + try { + Hash h = hashPath(parseHashType(algo), path).first; + auto s = h.to_string(base32 ? Base32 : Base16, false); + XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * hashFile(char * algo, int base32, char * path) + PPCODE: + try { + Hash h = hashFile(parseHashType(algo), path); + auto s = h.to_string(base32 ? Base32 : Base16, false); + XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * hashString(char * algo, int base32, char * s) + PPCODE: + try { + Hash h = hashString(parseHashType(algo), s); + auto s = h.to_string(base32 ? Base32 : Base16, false); + XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * convertHash(char * algo, char * s, int toBase32) + PPCODE: + try { + Hash h(s, parseHashType(algo)); + string s = h.to_string(toBase32 ? Base32 : Base16, false); + XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * signString(char * secretKey_, char * msg) + PPCODE: + try { +#if HAVE_SODIUM + auto sig = SecretKey(secretKey_).signDetached(msg); + XPUSHs(sv_2mortal(newSVpv(sig.c_str(), sig.size()))); +#else + throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); +#endif + } catch (Error & e) { + croak("%s", e.what()); + } + + +int checkSignature(SV * publicKey_, SV * sig_, char * msg) + CODE: + try { +#if HAVE_SODIUM + STRLEN publicKeyLen; + unsigned char * publicKey = (unsigned char *) SvPV(publicKey_, publicKeyLen); + if (publicKeyLen != crypto_sign_PUBLICKEYBYTES) + throw Error("public key is not valid"); + + STRLEN sigLen; + unsigned char * sig = (unsigned char *) SvPV(sig_, sigLen); + if (sigLen != crypto_sign_BYTES) + throw Error("signature is not valid"); + + RETVAL = crypto_sign_verify_detached(sig, (unsigned char *) msg, strlen(msg), publicKey) == 0; +#else + throw Error("Nix was not compiled with libsodium, required for signed binary cache support"); +#endif + } catch (Error & e) { + croak("%s", e.what()); + } + OUTPUT: + RETVAL + + +SV * addToStore(char * srcPath, int recursive, char * algo) + PPCODE: + try { + Path path = store()->addToStore(baseNameOf(srcPath), srcPath, recursive, parseHashType(algo)); + XPUSHs(sv_2mortal(newSVpv(path.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * makeFixedOutputPath(int recursive, char * algo, char * hash, char * name) + PPCODE: + try { + Hash h(hash, parseHashType(algo)); + Path path = store()->makeFixedOutputPath(recursive, h, name); + XPUSHs(sv_2mortal(newSVpv(path.c_str(), 0))); + } catch (Error & e) { + croak("%s", e.what()); + } + + +SV * derivationFromPath(char * drvPath) + PREINIT: + HV *hash; + CODE: + try { + Derivation drv = store()->derivationFromPath(drvPath); + hash = newHV(); + + HV * outputs = newHV(); + for (DerivationOutputs::iterator i = drv.outputs.begin(); i != drv.outputs.end(); ++i) + hv_store(outputs, i->first.c_str(), i->first.size(), newSVpv(i->second.path.c_str(), 0), 0); + hv_stores(hash, "outputs", newRV((SV *) outputs)); + + AV * inputDrvs = newAV(); + for (DerivationInputs::iterator i = drv.inputDrvs.begin(); i != drv.inputDrvs.end(); ++i) + av_push(inputDrvs, newSVpv(i->first.c_str(), 0)); // !!! ignores i->second + hv_stores(hash, "inputDrvs", newRV((SV *) inputDrvs)); + + AV * inputSrcs = newAV(); + for (PathSet::iterator i = drv.inputSrcs.begin(); i != drv.inputSrcs.end(); ++i) + av_push(inputSrcs, newSVpv(i->c_str(), 0)); + hv_stores(hash, "inputSrcs", newRV((SV *) inputSrcs)); + + hv_stores(hash, "platform", newSVpv(drv.platform.c_str(), 0)); + hv_stores(hash, "builder", newSVpv(drv.builder.c_str(), 0)); + + AV * args = newAV(); + for (Strings::iterator i = drv.args.begin(); i != drv.args.end(); ++i) + av_push(args, newSVpv(i->c_str(), 0)); + hv_stores(hash, "args", newRV((SV *) args)); + + HV * env = newHV(); + for (StringPairs::iterator i = drv.env.begin(); i != drv.env.end(); ++i) + hv_store(env, i->first.c_str(), i->first.size(), newSVpv(i->second.c_str(), 0), 0); + hv_stores(hash, "env", newRV((SV *) env)); + + RETVAL = newRV_noinc((SV *)hash); + } catch (Error & e) { + croak("%s", e.what()); + } + OUTPUT: + RETVAL + + +void addTempRoot(char * storePath) + PPCODE: + try { + store()->addTempRoot(storePath); + } catch (Error & e) { + croak("%s", e.what()); + } diff --git a/third_party/nix/perl/lib/Nix/Utils.pm b/third_party/nix/perl/lib/Nix/Utils.pm new file mode 100644 index 000000000000..44955a70698c --- /dev/null +++ b/third_party/nix/perl/lib/Nix/Utils.pm @@ -0,0 +1,47 @@ +package Nix::Utils; + +use utf8; +use File::Temp qw(tempdir); + +our @ISA = qw(Exporter); +our @EXPORT = qw(checkURL uniq writeFile readFile mkTempDir); + +$urlRE = "(?: [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*]+ )"; + +sub checkURL { + my ($url) = @_; + die "invalid URL '$url'\n" unless $url =~ /^ $urlRE $ /x; +} + +sub uniq { + my %seen; + my @res; + foreach my $name (@_) { + next if $seen{$name}; + $seen{$name} = 1; + push @res, $name; + } + return @res; +} + +sub writeFile { + my ($fn, $s) = @_; + open TMP, ">$fn" or die "cannot create file '$fn': $!"; + print TMP "$s" or die; + close TMP or die; +} + +sub readFile { + local $/ = undef; + my ($fn) = @_; + open TMP, "<$fn" or die "cannot open file '$fn': $!"; + my $s = <TMP>; + close TMP or die; + return $s; +} + +sub mkTempDir { + my ($name) = @_; + return tempdir("$name.XXXXXX", CLEANUP => 1, DIR => $ENV{"TMPDIR"} // $ENV{"XDG_RUNTIME_DIR"} // "/tmp") + || die "cannot create a temporary directory"; +} diff --git a/third_party/nix/perl/local.mk b/third_party/nix/perl/local.mk new file mode 100644 index 000000000000..b13d4c0d6396 --- /dev/null +++ b/third_party/nix/perl/local.mk @@ -0,0 +1,43 @@ +nix_perl_sources := \ + lib/Nix/Store.pm \ + lib/Nix/Manifest.pm \ + lib/Nix/SSH.pm \ + lib/Nix/CopyClosure.pm \ + lib/Nix/Config.pm.in \ + lib/Nix/Utils.pm + +nix_perl_modules := $(nix_perl_sources:.in=) + +$(foreach x, $(nix_perl_modules), $(eval $(call install-data-in, $(x), $(perllibdir)/Nix))) + +lib/Nix/Store.cc: lib/Nix/Store.xs + $(trace-gen) xsubpp $^ -output $@ + +libraries += Store + +Store_DIR := lib/Nix + +Store_SOURCES := $(Store_DIR)/Store.cc + +Store_CXXFLAGS = \ + $(NIX_CFLAGS) \ + -I$(shell perl -e 'use Config; print $$Config{archlibexp};')/CORE \ + -D_FILE_OFFSET_BITS=64 \ + -Wno-unknown-warning-option -Wno-unused-variable -Wno-literal-suffix \ + -Wno-reserved-user-defined-literal -Wno-duplicate-decl-specifier -Wno-pointer-bool-conversion + +Store_LDFLAGS := $(SODIUM_LIBS) $(NIX_LIBS) + +ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) + archlib = $(shell perl -E 'use Config; print $$Config{archlib};') + libperl = $(shell perl -E 'use Config; print $$Config{libperl};') + Store_LDFLAGS += $(shell find ${archlib} -name ${libperl}) +endif + +Store_ALLOW_UNDEFINED = 1 + +Store_FORCE_INSTALL = 1 + +Store_INSTALL_DIR = $(perllibdir)/auto/Nix/Store + +clean-files += lib/Nix/Config.pm lib/Nix/Store.cc Makefile.config |