about summary refs log tree commit diff
path: root/nixpkgs/pkgs/build-support/vm
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/pkgs/build-support/vm')
-rw-r--r--nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl180
-rw-r--r--nixpkgs/pkgs/build-support/vm/default.nix1241
-rw-r--r--nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl184
-rw-r--r--nixpkgs/pkgs/build-support/vm/test.nix58
4 files changed, 1663 insertions, 0 deletions
diff --git a/nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl b/nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl
new file mode 100644
index 000000000000..2d331e18dfeb
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/deb/deb-closure.pl
@@ -0,0 +1,180 @@
+use strict;
+use Dpkg::Control;
+use Dpkg::Deps;
+use File::Basename;
+
+my $packagesFile = shift @ARGV;
+my $urlPrefix = shift @ARGV;
+my @toplevelPkgs = @ARGV;
+
+
+my %packages;
+
+
+# Parse the Packages file.
+open PACKAGES, "<$packagesFile" or die;
+
+while (1) {
+    my $cdata = Dpkg::Control->new(type => CTRL_INFO_PKG);
+    last if not $cdata->parse(\*PACKAGES, $packagesFile);
+    die unless defined $cdata->{Package};
+    #print STDERR $cdata->{Package}, "\n";
+    $packages{$cdata->{Package}} = $cdata;
+}
+
+close PACKAGES;
+
+
+# Flatten a Dpkg::Deps dependency value into a list of package names.
+sub getDeps {
+    my $deps = shift;
+    #print "$deps\n";
+    if ($deps->isa('Dpkg::Deps::AND')) {
+        my @res = ();
+        foreach my $dep ($deps->get_deps()) {
+            push @res, getDeps($dep);
+        }
+        return @res;
+    } elsif ($deps->isa('Dpkg::Deps::OR')) {
+        # Arbitrarily pick the first alternative.
+        return getDeps(($deps->get_deps())[0]);
+    } elsif ($deps->isa('Dpkg::Deps::Simple')) {
+        return ($deps->{package});
+    } else {
+        die "unknown dep type";
+    }
+}
+
+
+# Process the "Provides" and "Replaces" fields to be able to resolve
+# virtual dependencies.
+my %provides;
+
+foreach my $cdata (sort {$a->{Package} cmp $b->{Package}} (values %packages)) {
+    if (defined $cdata->{Provides}) {
+        my @provides = getDeps(Dpkg::Deps::deps_parse($cdata->{Provides}));
+        foreach my $name (@provides) {
+            #die "conflicting provide: $name\n" if defined $provides{$name};
+            #warn "provide by $cdata->{Package} conflicts with package with the same name: $name\n";
+            next if defined $packages{$name};
+            $provides{$name} = $cdata->{Package};
+        }
+    }
+    # Treat "Replaces" like "Provides".
+    if (defined $cdata->{Replaces}) {
+        my @replaces = getDeps(Dpkg::Deps::deps_parse($cdata->{Replaces}));
+        foreach my $name (@replaces) {
+            next if defined $packages{$name};
+            $provides{$name} = $cdata->{Package};
+        }
+    }
+}
+
+
+# Determine the closure of a package.
+my %donePkgs;
+my %depsUsed;
+my @order = ();
+
+sub closePackage {
+    my $pkgName = shift;
+    print STDERR ">>> $pkgName\n";
+    my $cdata = $packages{$pkgName};
+
+    if (!defined $cdata) {
+        die "unknown (virtual) package $pkgName"
+            unless defined $provides{$pkgName};
+        print STDERR "virtual $pkgName: using $provides{$pkgName}\n";
+        $pkgName = $provides{$pkgName};
+        $cdata = $packages{$pkgName};
+    }
+
+    die "unknown package $pkgName" unless defined $cdata;
+    return if defined $donePkgs{$pkgName};
+    $donePkgs{$pkgName} = 1;
+
+    if (defined $cdata->{Provides}) {
+        foreach my $name (getDeps(Dpkg::Deps::deps_parse($cdata->{Provides}))) {
+            $provides{$name} = $cdata->{Package};
+        }
+    }
+
+    my @depNames = ();
+
+    if (defined $cdata->{Depends}) {
+        print STDERR "    $pkgName: $cdata->{Depends}\n";
+        my $deps = Dpkg::Deps::deps_parse($cdata->{Depends});
+        die unless defined $deps;
+        push @depNames, getDeps($deps);
+    }
+
+    if (defined $cdata->{'Pre-Depends'}) {
+        print STDERR "    $pkgName: $cdata->{'Pre-Depends'}\n";
+        my $deps = Dpkg::Deps::deps_parse($cdata->{'Pre-Depends'});
+        die unless defined $deps;
+        push @depNames, getDeps($deps);
+    }
+
+    foreach my $depName (@depNames) {
+        closePackage($depName);
+    }
+
+    push @order, $pkgName;
+    $depsUsed{$pkgName} = \@depNames;
+}
+
+foreach my $pkgName (@toplevelPkgs) {
+    closePackage $pkgName;
+}
+
+
+# Generate the output Nix expression.
+print "# This is a generated file.  Do not modify!\n";
+print "# Following are the Debian packages constituting the closure of: @toplevelPkgs\n\n";
+print "{fetchurl}:\n\n";
+print "[\n\n";
+
+# Output the packages in strongly connected components.
+my %done;
+my %forward;
+my $newComponent = 1;
+foreach my $pkgName (@order) {
+    $done{$pkgName} = 1;
+    my $cdata = $packages{$pkgName};
+    my @deps = @{$depsUsed{$pkgName}};
+    foreach my $dep (@deps) {
+        $dep = $provides{$dep} if defined $provides{$dep};
+        $forward{$dep} = 1 unless defined $done{$dep};
+    }
+    delete $forward{$pkgName};
+
+    print "  [\n\n" if $newComponent;
+    $newComponent = 0;
+
+    my $origName = basename $cdata->{Filename};
+    my $cleanedName = $origName;
+    $cleanedName =~ s/~//g;
+
+    print "    (fetchurl {\n";
+    print "      url = \"$urlPrefix/$cdata->{Filename}\";\n";
+    print "      sha256 = \"$cdata->{SHA256}\";\n";
+    print "      name = \"$cleanedName\";\n" if $cleanedName ne $origName;
+    print "    })\n";
+    print "\n";
+
+    if (keys %forward == 0) {
+        print "  ]\n\n";
+        $newComponent = 1;
+    }
+}
+
+foreach my $pkgName (@order) {
+    my $cdata = $packages{$pkgName};
+}
+
+print "]\n";
+
+if ($newComponent != 1) {
+    print STDERR "argh: ", keys %forward, "\n";
+    exit 1;
+}
diff --git a/nixpkgs/pkgs/build-support/vm/default.nix b/nixpkgs/pkgs/build-support/vm/default.nix
new file mode 100644
index 000000000000..871f81bb5d69
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/default.nix
@@ -0,0 +1,1241 @@
+{ lib
+, pkgs
+, kernel ? pkgs.linux
+, img ? pkgs.stdenv.hostPlatform.linux-kernel.target
+, storeDir ? builtins.storeDir
+, rootModules ?
+    [ "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_balloon" "virtio_rng" "ext4" "unix" "9p" "9pnet_virtio" "crc32c_generic" ]
+      ++ pkgs.lib.optional pkgs.stdenv.hostPlatform.isx86 "rtc_cmos"
+}:
+
+let
+  inherit (pkgs) bash bashInteractive busybox cpio coreutils e2fsprogs fetchurl kmod rpm
+    stdenv util-linux
+    buildPackages writeScript writeText runCommand;
+in
+rec {
+  qemu-common = import ../../../nixos/lib/qemu-common.nix { inherit lib pkgs; };
+
+  qemu = buildPackages.qemu_kvm;
+
+  modulesClosure = pkgs.makeModulesClosure {
+    inherit kernel rootModules;
+    firmware = kernel;
+  };
+
+
+  hd = "vda"; # either "sda" or "vda"
+
+  initrdUtils = runCommand "initrd-utils"
+    { nativeBuildInputs = [ buildPackages.nukeReferences ];
+      allowedReferences = [ "out" modulesClosure ]; # prevent accidents like glibc being included in the initrd
+    }
+    ''
+      mkdir -p $out/bin
+      mkdir -p $out/lib
+
+      # Copy what we need from Glibc.
+      cp -p \
+        ${pkgs.stdenv.cc.libc}/lib/ld-*.so.? \
+        ${pkgs.stdenv.cc.libc}/lib/libc.so.* \
+        ${pkgs.stdenv.cc.libc}/lib/libm.so.* \
+        ${pkgs.stdenv.cc.libc}/lib/libresolv.so.* \
+        ${pkgs.stdenv.cc.libc}/lib/libpthread.so.* \
+        ${pkgs.zstd.out}/lib/libzstd.so.* \
+        ${pkgs.xz.out}/lib/liblzma.so.* \
+        $out/lib
+
+      # Copy BusyBox.
+      cp -pd ${pkgs.busybox}/bin/* $out/bin
+      cp -pd ${pkgs.kmod}/bin/* $out/bin
+
+      # Run patchelf to make the programs refer to the copied libraries.
+      for i in $out/bin/* $out/lib/*; do if ! test -L $i; then nuke-refs $i; fi; done
+
+      for i in $out/bin/*; do
+          if [ -f "$i" -a ! -L "$i" ]; then
+              echo "patching $i..."
+              patchelf --set-interpreter $out/lib/ld-*.so.? --set-rpath $out/lib $i || true
+          fi
+      done
+
+      find $out/lib -type f \! -name 'ld*.so.?' | while read i; do
+        echo "patching $i..."
+        patchelf --set-rpath $out/lib $i
+      done
+    ''; # */
+
+
+  stage1Init = writeScript "vm-run-stage1" ''
+    #! ${initrdUtils}/bin/ash -e
+
+    export PATH=${initrdUtils}/bin
+
+    mkdir /etc
+    echo -n > /etc/fstab
+
+    mount -t proc none /proc
+    mount -t sysfs none /sys
+
+    echo 2 > /proc/sys/vm/panic_on_oom
+
+    for o in $(cat /proc/cmdline); do
+      case $o in
+        mountDisk=*)
+          mountDisk=''${mountDisk#mountDisk=}
+          ;;
+        command=*)
+          set -- $(IFS==; echo $o)
+          command=$2
+          ;;
+        out=*)
+          set -- $(IFS==; echo $o)
+          export out=$2
+          ;;
+      esac
+    done
+
+    echo "loading kernel modules..."
+    for i in $(cat ${modulesClosure}/insmod-list); do
+      insmod $i || echo "warning: unable to load $i"
+    done
+
+    mount -t devtmpfs devtmpfs /dev
+    ln -s /proc/self/fd /dev/fd
+    ln -s /proc/self/fd/0 /dev/stdin
+    ln -s /proc/self/fd/1 /dev/stdout
+    ln -s /proc/self/fd/2 /dev/stderr
+
+    ifconfig lo up
+
+    mkdir /fs
+
+    if test -z "$mountDisk"; then
+      mount -t tmpfs none /fs
+    elif [[ -e "$mountDisk" ]]; then
+      mount "$mountDisk" /fs
+    else
+      mount /dev/${hd} /fs
+    fi
+
+    mkdir -p /fs/dev
+    mount -o bind /dev /fs/dev
+
+    mkdir -p /fs/dev/shm /fs/dev/pts
+    mount -t tmpfs -o "mode=1777" none /fs/dev/shm
+    mount -t devpts none /fs/dev/pts
+
+    echo "mounting Nix store..."
+    mkdir -p /fs${storeDir}
+    mount -t 9p store /fs${storeDir} -o trans=virtio,version=9p2000.L,cache=loose,msize=131072
+
+    mkdir -p /fs/tmp /fs/run /fs/var
+    mount -t tmpfs -o "mode=1777" none /fs/tmp
+    mount -t tmpfs -o "mode=755" none /fs/run
+    ln -sfn /run /fs/var/run
+
+    echo "mounting host's temporary directory..."
+    mkdir -p /fs/tmp/xchg
+    mount -t 9p xchg /fs/tmp/xchg -o trans=virtio,version=9p2000.L,msize=131072
+
+    mkdir -p /fs/proc
+    mount -t proc none /fs/proc
+
+    mkdir -p /fs/sys
+    mount -t sysfs none /fs/sys
+
+    mkdir -p /fs/etc
+    ln -sf /proc/mounts /fs/etc/mtab
+    echo "127.0.0.1 localhost" > /fs/etc/hosts
+    # Ensures tools requiring /etc/passwd will work (e.g. nix)
+    if [ ! -e /fs/etc/passwd ]; then
+      echo "root:x:0:0:System administrator:/root:/bin/sh" > /fs/etc/passwd
+    fi
+
+    echo "starting stage 2 ($command)"
+    exec switch_root /fs $command $out
+  '';
+
+
+  initrd = pkgs.makeInitrd {
+    contents = [
+      { object = stage1Init;
+        symlink = "/init";
+      }
+    ];
+  };
+
+
+  stage2Init = writeScript "vm-run-stage2" ''
+    #! ${bash}/bin/sh
+    source /tmp/xchg/saved-env
+
+    # Set the system time from the hardware clock.  Works around an
+    # apparent KVM > 1.5.2 bug.
+    ${util-linux}/bin/hwclock -s
+
+    export NIX_STORE=${storeDir}
+    export NIX_BUILD_TOP=/tmp
+    export TMPDIR=/tmp
+    export PATH=/empty
+    out="$1"
+    cd "$NIX_BUILD_TOP"
+
+    if ! test -e /bin/sh; then
+      ${coreutils}/bin/mkdir -p /bin
+      ${coreutils}/bin/ln -s ${bash}/bin/sh /bin/sh
+    fi
+
+    # Set up automatic kernel module loading.
+    export MODULE_DIR=${kernel}/lib/modules/
+    ${coreutils}/bin/cat <<EOF > /run/modprobe
+    #! ${bash}/bin/sh
+    export MODULE_DIR=$MODULE_DIR
+    exec ${kmod}/bin/modprobe "\$@"
+    EOF
+    ${coreutils}/bin/chmod 755 /run/modprobe
+    echo /run/modprobe > /proc/sys/kernel/modprobe
+
+    # For debugging: if this is the second time this image is run,
+    # then don't start the build again, but instead drop the user into
+    # an interactive shell.
+    if test -n "$origBuilder" -a ! -e /.debug; then
+      exec < /dev/null
+      ${coreutils}/bin/touch /.debug
+      $origBuilder $origArgs
+      echo $? > /tmp/xchg/in-vm-exit
+
+      ${busybox}/bin/mount -o remount,ro dummy /
+
+      ${busybox}/bin/poweroff -f
+    else
+      export PATH=/bin:/usr/bin:${coreutils}/bin
+      echo "Starting interactive shell..."
+      echo "(To run the original builder: \$origBuilder \$origArgs)"
+      exec ${busybox}/bin/setsid ${bashInteractive}/bin/bash < /dev/${qemu-common.qemuSerialDevice} &> /dev/${qemu-common.qemuSerialDevice}
+    fi
+  '';
+
+
+  qemuCommandLinux = ''
+    ${qemu-common.qemuBinary qemu} \
+      -nographic -no-reboot \
+      -device virtio-rng-pci \
+      -virtfs local,path=${storeDir},security_model=none,mount_tag=store \
+      -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
+      ''${diskImage:+-drive file=$diskImage,if=virtio,cache=unsafe,werror=report} \
+      -kernel ${kernel}/${img} \
+      -initrd ${initrd}/initrd \
+      -append "console=${qemu-common.qemuSerialDevice} panic=1 command=${stage2Init} out=$out mountDisk=$mountDisk loglevel=4" \
+      $QEMU_OPTS
+  '';
+
+
+  vmRunCommand = qemuCommand: writeText "vm-run" ''
+    export > saved-env
+
+    PATH=${coreutils}/bin
+    mkdir xchg
+    mv saved-env xchg/
+
+    eval "$preVM"
+
+    if [ "$enableParallelBuilding" = 1 ]; then
+      if [ ''${NIX_BUILD_CORES:-0} = 0 ]; then
+        QEMU_OPTS+=" -smp cpus=$(nproc)"
+      else
+        QEMU_OPTS+=" -smp cpus=$NIX_BUILD_CORES"
+      fi
+    fi
+
+    # Write the command to start the VM to a file so that the user can
+    # debug inside the VM if the build fails (when Nix is called with
+    # the -K option to preserve the temporary build directory).
+    cat > ./run-vm <<EOF
+    #! ${bash}/bin/sh
+    ''${diskImage:+diskImage=$diskImage}
+    TMPDIR=$TMPDIR
+    cd $TMPDIR
+    ${qemuCommand}
+    EOF
+
+    mkdir -p -m 0700 $out
+
+    chmod +x ./run-vm
+    source ./run-vm
+
+    if ! test -e xchg/in-vm-exit; then
+      echo "Virtual machine didn't produce an exit code."
+      exit 1
+    fi
+
+    exitCode="$(cat xchg/in-vm-exit)"
+    if [ "$exitCode" != "0" ]; then
+      exit "$exitCode"
+    fi
+
+    eval "$postVM"
+  '';
+
+  /*
+    A bash script fragment that produces a disk image at `destination`.
+   */
+  createEmptyImage = {
+    # Disk image size in MiB
+    size,
+    # Name that will be written to ${destination}/nix-support/full-name
+    fullName,
+    # Where to write the image files, defaulting to $out
+    destination ? "$out"
+  }: ''
+    mkdir -p ${destination}
+    diskImage=${destination}/disk-image.qcow2
+    ${qemu}/bin/qemu-img create -f qcow2 $diskImage "${toString size}M"
+
+    mkdir ${destination}/nix-support
+    echo "${fullName}" > ${destination}/nix-support/full-name
+  '';
+
+
+  defaultCreateRootFS = ''
+    mkdir /mnt
+    ${e2fsprogs}/bin/mkfs.ext4 /dev/${hd}
+    ${util-linux}/bin/mount -t ext4 /dev/${hd} /mnt
+
+    if test -e /mnt/.debug; then
+      exec ${bash}/bin/sh
+    fi
+    touch /mnt/.debug
+
+    mkdir /mnt/proc /mnt/dev /mnt/sys
+  '';
+
+
+  /* Run a derivation in a Linux virtual machine (using Qemu/KVM).  By
+     default, there is no disk image; the root filesystem is a tmpfs,
+     and the nix store is shared with the host (via the 9P protocol).
+     Thus, any pure Nix derivation should run unmodified, e.g. the
+     call
+
+       runInLinuxVM patchelf
+
+     will build the derivation `patchelf' inside a VM.  The attribute
+     `preVM' can optionally contain a shell command to be evaluated
+     *before* the VM is started (i.e., on the host).  The attribute
+     `memSize' specifies the memory size of the VM in megabytes,
+     defaulting to 512.  The attribute `diskImage' can optionally
+     specify a file system image to be attached to /dev/sda.  (Note
+     that currently we expect the image to contain a filesystem, not a
+     full disk image with a partition table etc.)
+
+     If the build fails and Nix is run with the `-K' option, a script
+     `run-vm' will be left behind in the temporary build directory
+     that allows you to boot into the VM and debug it interactively. */
+
+  runInLinuxVM = drv: lib.overrideDerivation drv ({ memSize ? 512, QEMU_OPTS ? "", args, builder, ... }: {
+    requiredSystemFeatures = [ "kvm" ];
+    builder = "${bash}/bin/sh";
+    args = ["-e" (vmRunCommand qemuCommandLinux)];
+    origArgs = args;
+    origBuilder = builder;
+    QEMU_OPTS = "${QEMU_OPTS} -m ${toString memSize}";
+    passAsFile = []; # HACK fix - see https://github.com/NixOS/nixpkgs/issues/16742
+  });
+
+
+  extractFs = {file, fs ? null} :
+    runInLinuxVM (
+    stdenv.mkDerivation {
+      name = "extract-file";
+      buildInputs = [ util-linux ];
+      buildCommand = ''
+        ln -s ${kernel}/lib /lib
+        ${kmod}/bin/modprobe loop
+        ${kmod}/bin/modprobe ext4
+        ${kmod}/bin/modprobe hfs
+        ${kmod}/bin/modprobe hfsplus
+        ${kmod}/bin/modprobe squashfs
+        ${kmod}/bin/modprobe iso9660
+        ${kmod}/bin/modprobe ufs
+        ${kmod}/bin/modprobe cramfs
+
+        mkdir -p $out
+        mkdir -p tmp
+        mount -o loop,ro,ufstype=44bsd ${lib.optionalString (fs != null) "-t ${fs} "}${file} tmp ||
+          mount -o loop,ro ${lib.optionalString (fs != null) "-t ${fs} "}${file} tmp
+        cp -Rv tmp/* $out/ || exit 0
+      '';
+    });
+
+
+  extractMTDfs = {file, fs ? null} :
+    runInLinuxVM (
+    stdenv.mkDerivation {
+      name = "extract-file-mtd";
+      buildInputs = [ pkgs.util-linux pkgs.mtdutils ];
+      buildCommand = ''
+        ln -s ${kernel}/lib /lib
+        ${kmod}/bin/modprobe mtd
+        ${kmod}/bin/modprobe mtdram total_size=131072
+        ${kmod}/bin/modprobe mtdchar
+        ${kmod}/bin/modprobe mtdblock
+        ${kmod}/bin/modprobe jffs2
+        ${kmod}/bin/modprobe zlib
+
+        mkdir -p $out
+        mkdir -p tmp
+
+        dd if=${file} of=/dev/mtd0
+        mount ${lib.optionalString (fs != null) "-t ${fs} "}/dev/mtdblock0 tmp
+
+        cp -R tmp/* $out/
+      '';
+    });
+
+
+  /* Like runInLinuxVM, but run the build not using the stdenv from
+     the Nix store, but using the tools provided by /bin, /usr/bin
+     etc. from the specified filesystem image, which typically is a
+     filesystem containing a non-NixOS Linux distribution. */
+
+  runInLinuxImage = drv: runInLinuxVM (lib.overrideDerivation drv (attrs: {
+    mountDisk = attrs.mountDisk or true;
+
+    /* Mount `image' as the root FS, but use a temporary copy-on-write
+       image since we don't want to (and can't) write to `image'. */
+    preVM = ''
+      diskImage=$(pwd)/disk-image.qcow2
+      origImage=${attrs.diskImage}
+      if test -d "$origImage"; then origImage="$origImage/disk-image.qcow2"; fi
+      ${qemu}/bin/qemu-img create -F ${attrs.diskImageFormat} -b "$origImage" -f qcow2 $diskImage
+    '';
+
+    /* Inside the VM, run the stdenv setup script normally, but at the
+       very end set $PATH and $SHELL to the `native' paths for the
+       distribution inside the VM. */
+    postHook = ''
+      PATH=/usr/bin:/bin:/usr/sbin:/sbin
+      SHELL=/bin/sh
+      eval "$origPostHook"
+    '';
+
+    origPostHook = lib.optionalString (attrs ? postHook) attrs.postHook;
+
+    /* Don't run Nix-specific build steps like patchelf. */
+    fixupPhase = "true";
+  }));
+
+
+  /* Create a filesystem image of the specified size and fill it with
+     a set of RPM packages. */
+
+  fillDiskWithRPMs =
+    { size ? 4096, rpms, name, fullName, preInstall ? "", postInstall ? ""
+    , runScripts ? true, createRootFS ? defaultCreateRootFS
+    , QEMU_OPTS ? "", memSize ? 512
+    , unifiedSystemDir ? false
+    }:
+
+    runInLinuxVM (stdenv.mkDerivation {
+      inherit name preInstall postInstall rpms QEMU_OPTS memSize;
+      preVM = createEmptyImage {inherit size fullName;};
+
+      buildCommand = ''
+        ${createRootFS}
+
+        chroot=$(type -tP chroot)
+
+        # Make the Nix store available in /mnt, because that's where the RPMs live.
+        mkdir -p /mnt${storeDir}
+        ${util-linux}/bin/mount -o bind ${storeDir} /mnt${storeDir}
+
+        # Newer distributions like Fedora 18 require /lib etc. to be
+        # symlinked to /usr.
+        ${lib.optionalString unifiedSystemDir ''
+          mkdir -p /mnt/usr/bin /mnt/usr/sbin /mnt/usr/lib /mnt/usr/lib64
+          ln -s /usr/bin /mnt/bin
+          ln -s /usr/sbin /mnt/sbin
+          ln -s /usr/lib /mnt/lib
+          ln -s /usr/lib64 /mnt/lib64
+          ${util-linux}/bin/mount -t proc none /mnt/proc
+        ''}
+
+        echo "unpacking RPMs..."
+        set +o pipefail
+        for i in $rpms; do
+            echo "$i..."
+            ${rpm}/bin/rpm2cpio "$i" | chroot /mnt ${cpio}/bin/cpio -i --make-directories --unconditional
+        done
+
+        eval "$preInstall"
+
+        echo "initialising RPM DB..."
+        PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
+          ldconfig -v || true
+        PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
+          rpm --initdb
+
+        ${util-linux}/bin/mount -o bind /tmp /mnt/tmp
+
+        echo "installing RPMs..."
+        PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
+          rpm -iv --nosignature ${lib.optionalString (!runScripts) "--noscripts"} $rpms
+
+        echo "running post-install script..."
+        eval "$postInstall"
+
+        rm /mnt/.debug
+
+        ${util-linux}/bin/umount /mnt${storeDir} /mnt/tmp ${lib.optionalString unifiedSystemDir "/mnt/proc"}
+        ${util-linux}/bin/umount /mnt
+      '';
+
+      passthru = { inherit fullName; };
+    });
+
+
+  /* Generate a script that can be used to run an interactive session
+     in the given image. */
+
+  makeImageTestScript = image: writeScript "image-test" ''
+    #! ${bash}/bin/sh
+    if test -z "$1"; then
+      echo "Syntax: $0 <copy-on-write-temp-file>"
+      exit 1
+    fi
+    diskImage="$1"
+    if ! test -e "$diskImage"; then
+      ${qemu}/bin/qemu-img create -b ${image}/disk-image.qcow2 -f qcow2 -F qcow2 "$diskImage"
+    fi
+    export TMPDIR=$(mktemp -d)
+    export out=/dummy
+    export origBuilder=
+    export origArgs=
+    mkdir $TMPDIR/xchg
+    export > $TMPDIR/xchg/saved-env
+    mountDisk=1
+    ${qemuCommandLinux}
+  '';
+
+
+  /* Build RPM packages from the tarball `src' in the Linux
+     distribution installed in the filesystem `diskImage'.  The
+     tarball must contain an RPM specfile. */
+
+  buildRPM = attrs: runInLinuxImage (stdenv.mkDerivation ({
+    prePhases = [ "prepareImagePhase" "sysInfoPhase" ];
+    dontConfigure = true;
+
+    outDir = "rpms/${attrs.diskImage.name}";
+
+    prepareImagePhase = ''
+      if test -n "$extraRPMs"; then
+        for rpmdir in $extraRPMs ; do
+          rpm -iv $(ls $rpmdir/rpms/*/*.rpm | grep -v 'src\.rpm' | sort | head -1)
+        done
+      fi
+    '';
+
+    sysInfoPhase = ''
+      echo "System/kernel: $(uname -a)"
+      if test -e /etc/fedora-release; then echo "Fedora release: $(cat /etc/fedora-release)"; fi
+      if test -e /etc/SuSE-release; then echo "SUSE release: $(cat /etc/SuSE-release)"; fi
+      echo "installed RPM packages"
+      rpm -qa --qf "%{Name}-%{Version}-%{Release} (%{Arch}; %{Distribution}; %{Vendor})\n"
+    '';
+
+    buildPhase = ''
+      eval "$preBuild"
+
+      srcName="$(rpmspec --srpm -q --qf '%{source}' *.spec)"
+      cp "$src" "$srcName" # `ln' doesn't work always work: RPM requires that the file is owned by root
+
+      export HOME=/tmp/home
+      mkdir $HOME
+
+      rpmout=/tmp/rpmout
+      mkdir $rpmout $rpmout/SPECS $rpmout/BUILD $rpmout/RPMS $rpmout/SRPMS
+
+      echo "%_topdir $rpmout" >> $HOME/.rpmmacros
+
+      if [ `uname -m` = i686 ]; then extra="--target i686-linux"; fi
+      rpmbuild -vv $extra -ta "$srcName"
+
+      eval "$postBuild"
+    '';
+
+    installPhase = ''
+      eval "$preInstall"
+
+      mkdir -p $out/$outDir
+      find $rpmout -name "*.rpm" -exec cp {} $out/$outDir \;
+
+      for i in $out/$outDir/*.rpm; do
+        echo "Generated RPM/SRPM: $i"
+        rpm -qip $i
+      done
+
+      eval "$postInstall"
+    ''; # */
+  } // attrs));
+
+
+  /* Create a filesystem image of the specified size and fill it with
+     a set of Debian packages.  `debs' must be a list of list of
+     .deb files, namely, the Debian packages grouped together into
+     strongly connected components.  See deb/deb-closure.nix. */
+
+  fillDiskWithDebs =
+    { size ? 4096, debs, name, fullName, postInstall ? null, createRootFS ? defaultCreateRootFS
+    , QEMU_OPTS ? "", memSize ? 512, ... }@args:
+
+    runInLinuxVM (stdenv.mkDerivation ({
+      inherit name postInstall QEMU_OPTS memSize;
+
+      debs = (lib.intersperse "|" debs);
+
+      preVM = createEmptyImage {inherit size fullName;};
+
+      buildCommand = ''
+        ${createRootFS}
+
+        PATH=$PATH:${lib.makeBinPath [ pkgs.dpkg pkgs.glibc pkgs.xz ]}
+
+        # Unpack the .debs.  We do this to prevent pre-install scripts
+        # (which have lots of circular dependencies) from barfing.
+        echo "unpacking Debs..."
+
+        for deb in $debs; do
+          if test "$deb" != "|"; then
+            echo "$deb..."
+            dpkg-deb --extract "$deb" /mnt
+          fi
+        done
+
+        # Make the Nix store available in /mnt, because that's where the .debs live.
+        mkdir -p /mnt/inst${storeDir}
+        ${util-linux}/bin/mount -o bind ${storeDir} /mnt/inst${storeDir}
+        ${util-linux}/bin/mount -o bind /proc /mnt/proc
+        ${util-linux}/bin/mount -o bind /dev /mnt/dev
+
+        # Misc. files/directories assumed by various packages.
+        echo "initialising Dpkg DB..."
+        touch /mnt/etc/shells
+        touch /mnt/var/lib/dpkg/status
+        touch /mnt/var/lib/dpkg/available
+        touch /mnt/var/lib/dpkg/diversions
+
+        # Now install the .debs.  This is basically just to register
+        # them with dpkg and to make their pre/post-install scripts
+        # run.
+        echo "installing Debs..."
+
+        export DEBIAN_FRONTEND=noninteractive
+
+        oldIFS="$IFS"
+        IFS="|"
+        for component in $debs; do
+          IFS="$oldIFS"
+          echo
+          echo ">>> INSTALLING COMPONENT: $component"
+          debs=
+          for i in $component; do
+            debs="$debs /inst/$i";
+          done
+          chroot=$(type -tP chroot)
+
+          # Create a fake start-stop-daemon script, as done in debootstrap.
+          mv "/mnt/sbin/start-stop-daemon" "/mnt/sbin/start-stop-daemon.REAL"
+          echo "#!/bin/true" > "/mnt/sbin/start-stop-daemon"
+          chmod 755 "/mnt/sbin/start-stop-daemon"
+
+          PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
+            /usr/bin/dpkg --install --force-all $debs < /dev/null || true
+
+          # Move the real start-stop-daemon back into its place.
+          mv "/mnt/sbin/start-stop-daemon.REAL" "/mnt/sbin/start-stop-daemon"
+        done
+
+        echo "running post-install script..."
+        eval "$postInstall"
+
+        rm /mnt/.debug
+
+        ${util-linux}/bin/umount /mnt/inst${storeDir}
+        ${util-linux}/bin/umount /mnt/proc
+        ${util-linux}/bin/umount /mnt/dev
+        ${util-linux}/bin/umount /mnt
+      '';
+
+      passthru = { inherit fullName; };
+    } // args));
+
+
+  /* Generate a Nix expression containing fetchurl calls for the
+     closure of a set of top-level RPM packages from the
+     `primary.xml.gz' file of a Fedora or openSUSE distribution. */
+
+  rpmClosureGenerator =
+    {name, packagesLists, urlPrefixes, packages, archs ? []}:
+    assert (builtins.length packagesLists) == (builtins.length urlPrefixes);
+    runCommand "${name}.nix" {
+      nativeBuildInputs = [ buildPackages.perl buildPackages.perlPackages.XMLSimple ];
+      inherit archs;
+    } ''
+      ${lib.concatImapStrings (i: pl: ''
+        gunzip < ${pl} > ./packages_${toString i}.xml
+      '') packagesLists}
+      perl -w ${rpm/rpm-closure.pl} \
+        ${lib.concatImapStrings (i: pl: "./packages_${toString i}.xml ${pl.snd} " ) (lib.zipLists packagesLists urlPrefixes)} \
+        ${toString packages} > $out
+    '';
+
+
+  /* Helper function that combines rpmClosureGenerator and
+     fillDiskWithRPMs to generate a disk image from a set of package
+     names. */
+
+  makeImageFromRPMDist =
+    { name, fullName, size ? 4096
+    , urlPrefix ? "", urlPrefixes ? [urlPrefix]
+    , packagesList ? "", packagesLists ? [packagesList]
+    , packages, extraPackages ? []
+    , preInstall ? "", postInstall ? "", archs ? ["noarch" "i386"]
+    , runScripts ? true, createRootFS ? defaultCreateRootFS
+    , QEMU_OPTS ? "", memSize ? 512
+    , unifiedSystemDir ? false }:
+
+    fillDiskWithRPMs {
+      inherit name fullName size preInstall postInstall runScripts createRootFS unifiedSystemDir QEMU_OPTS memSize;
+      rpms = import (rpmClosureGenerator {
+        inherit name packagesLists urlPrefixes archs;
+        packages = packages ++ extraPackages;
+      }) { inherit fetchurl; };
+    };
+
+
+  /* Like `rpmClosureGenerator', but now for Debian/Ubuntu releases
+     (i.e. generate a closure from a Packages.bz2 file). */
+
+  debClosureGenerator =
+    {name, packagesLists, urlPrefix, packages}:
+
+    runCommand "${name}.nix"
+      { nativeBuildInputs = [ buildPackages.perl buildPackages.dpkg ]; } ''
+      for i in ${toString packagesLists}; do
+        echo "adding $i..."
+        case $i in
+          *.xz | *.lzma)
+            xz -d < $i >> ./Packages
+            ;;
+          *.bz2)
+            bunzip2 < $i >> ./Packages
+            ;;
+          *.gz)
+            gzip -dc < $i >> ./Packages
+            ;;
+        esac
+      done
+
+      perl -w ${deb/deb-closure.pl} \
+        ./Packages ${urlPrefix} ${toString packages} > $out
+    '';
+
+
+  /* Helper function that combines debClosureGenerator and
+     fillDiskWithDebs to generate a disk image from a set of package
+     names. */
+
+  makeImageFromDebDist =
+    { name, fullName, size ? 4096, urlPrefix
+    , packagesList ? "", packagesLists ? [packagesList]
+    , packages, extraPackages ? [], postInstall ? ""
+    , extraDebs ? [], createRootFS ? defaultCreateRootFS
+    , QEMU_OPTS ? "", memSize ? 512, ... }@args:
+
+    let
+      expr = debClosureGenerator {
+        inherit name packagesLists urlPrefix;
+        packages = packages ++ extraPackages;
+      };
+    in
+      (fillDiskWithDebs ({
+        inherit name fullName size postInstall createRootFS QEMU_OPTS memSize;
+        debs = import expr {inherit fetchurl;} ++ extraDebs;
+      } // args)) // {inherit expr;};
+
+
+  /* The set of supported RPM-based distributions. */
+
+  rpmDistros = {
+
+    # Note: no i386 release for Fedora >= 26
+    fedora26x86_64 =
+      let version = "26";
+      in {
+        name = "fedora-${version}-x86_64";
+        fullName = "Fedora ${version} (x86_64)";
+        packagesList = fetchurl rec {
+          url = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os/repodata/${sha256}-primary.xml.gz";
+          sha256 = "880055a50c05b20641530d09b23f64501a000b2f92fe252417c530178730a95e";
+        };
+        urlPrefix = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os";
+        archs = ["noarch" "x86_64"];
+        packages = commonFedoraPackages ++ [ "cronie" "util-linux" ];
+        unifiedSystemDir = true;
+      };
+
+    fedora27x86_64 =
+      let version = "27";
+      in {
+        name = "fedora-${version}-x86_64";
+        fullName = "Fedora ${version} (x86_64)";
+        packagesList = fetchurl rec {
+          url = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os/repodata/${sha256}-primary.xml.gz";
+          sha256 = "48986ce4583cd09825c6d437150314446f0f49fa1a1bd62dcfa1085295030fe9";
+        };
+        urlPrefix = "mirror://fedora/linux/releases/${version}/Everything/x86_64/os";
+        archs = ["noarch" "x86_64"];
+        packages = commonFedoraPackages ++ [ "cronie" "util-linux" ];
+        unifiedSystemDir = true;
+      };
+
+    centos6i386 =
+      let version = "6.9";
+      in rec {
+        name = "centos-${version}-i386";
+        fullName = "CentOS ${version} (i386)";
+        urlPrefix = "mirror://centos/${version}/os/i386";
+        packagesList = fetchurl rec {
+          url = "${urlPrefix}/repodata/${sha256}-primary.xml.gz";
+          sha256 = "b826a45082ef68340325c0855f3d2e5d5a4d0f77d28ba3b871791d6f14a97aeb";
+        };
+        archs = ["noarch" "i386"];
+        packages = commonCentOSPackages ++ [ "procps" ];
+      };
+
+    centos6x86_64 =
+      let version = "6.9";
+      in rec {
+        name = "centos-${version}-x86_64";
+        fullName = "CentOS ${version} (x86_64)";
+        urlPrefix = "mirror://centos/${version}/os/x86_64";
+        packagesList = fetchurl rec {
+          url = "${urlPrefix}/repodata/${sha256}-primary.xml.gz";
+          sha256 = "ed2b2d4ac98d774d4cd3e91467e1532f7e8b0275cfc91a0d214b532dcaf1e979";
+        };
+        archs = ["noarch" "x86_64"];
+        packages = commonCentOSPackages ++ [ "procps" ];
+      };
+
+    # Note: no i386 release for 7.x
+    centos7x86_64 =
+      let version = "7.4.1708";
+      in rec {
+        name = "centos-${version}-x86_64";
+        fullName = "CentOS ${version} (x86_64)";
+        urlPrefix = "mirror://centos/${version}/os/x86_64";
+        packagesList = fetchurl rec {
+          url = "${urlPrefix}/repodata/${sha256}-primary.xml.gz";
+          sha256 = "b686d3a0f337323e656d9387b9a76ce6808b26255fc3a138b1a87d3b1cb95ed5";
+        };
+        archs = ["noarch" "x86_64"];
+        packages = commonCentOSPackages ++ [ "procps-ng" ];
+      };
+  };
+
+
+  /* The set of supported Dpkg-based distributions. */
+
+  debDistros = {
+    ubuntu1404i386 = {
+      name = "ubuntu-14.04-trusty-i386";
+      fullName = "Ubuntu 14.04 Trusty (i386)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/trusty/main/binary-i386/Packages.bz2";
+            sha256 = "1d5y3v3v079gdq45hc07ja0bjlmzqfwdwwlq0brwxi8m75k3iz7x";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/trusty/universe/binary-i386/Packages.bz2";
+            sha256 = "03x9w92by320rfklrqhcl3qpwmnxds9c8ijl5zhcb21d6dcz5z1a";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu1404x86_64 = {
+      name = "ubuntu-14.04-trusty-amd64";
+      fullName = "Ubuntu 14.04 Trusty (amd64)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/trusty/main/binary-amd64/Packages.bz2";
+            sha256 = "1hhzbyqfr5i0swahwnl5gfp5l9p9hspywb1vpihr3b74p1z935bh";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/trusty/universe/binary-amd64/Packages.bz2";
+            sha256 = "04560ba8s4z4v5iawknagrkn9q1nzvpn081ycmqvhh73p3p3g1jm";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu1604i386 = {
+      name = "ubuntu-16.04-xenial-i386";
+      fullName = "Ubuntu 16.04 Xenial (i386)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/xenial/main/binary-i386/Packages.xz";
+            sha256 = "13r75sp4slqy8w32y5dnr7pp7p3cfvavyr1g7gwnlkyrq4zx4ahy";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/xenial/universe/binary-i386/Packages.xz";
+            sha256 = "14fid1rqm3sc0wlygcvn0yx5aljf51c2jpd4x0zxij4019316hsh";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu1604x86_64 = {
+      name = "ubuntu-16.04-xenial-amd64";
+      fullName = "Ubuntu 16.04 Xenial (amd64)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/xenial/main/binary-amd64/Packages.xz";
+            sha256 = "110qnkhjkkwm316fbig3aivm2595ydz6zskc4ld5cr8ngcrqm1bn";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/xenial/universe/binary-amd64/Packages.xz";
+            sha256 = "0mm7gj491yi6q4v0n4qkbsm94s59bvqir6fk60j73w7y4la8rg68";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu1804i386 = {
+      name = "ubuntu-18.04-bionic-i386";
+      fullName = "Ubuntu 18.04 Bionic (i386)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/bionic/main/binary-i386/Packages.xz";
+            sha256 = "0f0v4131kwf7m7f8j3288rlqdxk1k3vqy74b7fcfd6jz9j8d840i";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/bionic/universe/binary-i386/Packages.xz";
+            sha256 = "1v75c0dqr0wp0dqd4hnci92qqs4hll8frqdbpswadgxm5chn91bw";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu1804x86_64 = {
+      name = "ubuntu-18.04-bionic-amd64";
+      fullName = "Ubuntu 18.04 Bionic (amd64)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/bionic/main/binary-amd64/Packages.xz";
+            sha256 = "1ls81bjyvmfz6i919kszl7xks1ibrh1xqhsk6698ackndkm0wp39";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/bionic/universe/binary-amd64/Packages.xz";
+            sha256 = "1832nqpn4ap95b3sj870xqayrza9in4kih9jkmjax27pq6x15v1r";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu2004i386 = {
+      name = "ubuntu-20.04-focal-i386";
+      fullName = "Ubuntu 20.04 Focal (i386)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/focal/main/binary-i386/Packages.xz";
+            sha256 = "sha256-7RAYURoN3RKYQAHpwBS9TIV6vCmpURpphyMJQmV4wLc=";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/focal/universe/binary-i386/Packages.xz";
+            sha256 = "sha256-oA551xVE80volUPgkMyvzpQ1d+GhuZd4DAe7dXZnULM=";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu2004x86_64 = {
+      name = "ubuntu-20.04-focal-amd64";
+      fullName = "Ubuntu 20.04 Focal (amd64)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/focal/main/binary-amd64/Packages.xz";
+            sha256 = "sha256-d1eSH/j+7Zw5NKDJk21EG6SiOL7j6myMHfXLzUP8mGE=";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/focal/universe/binary-amd64/Packages.xz";
+            sha256 = "sha256-RqdG2seJvZU3rKVNsWgLnf9RwkgVMRE1A4IZnX2WudE=";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu2204i386 = {
+      name = "ubuntu-22.04-jammy-i386";
+      fullName = "Ubuntu 22.04 Jammy (i386)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/jammy/main/binary-i386/Packages.xz";
+            sha256 = "sha256-iZBmwT0ep4v+V3sayybbOgZBOFFZwPGpOKtmuLMMVPQ=";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/jammy/universe/binary-i386/Packages.xz";
+            sha256 = "sha256-DO2LdpZ9rDDBhWj2gvDWd0TJJVZHxKsYTKTi6GXjm1E=";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu2204x86_64 = {
+      name = "ubuntu-22.04-jammy-amd64";
+      fullName = "Ubuntu 22.04 Jammy (amd64)";
+      packagesLists =
+        [ (fetchurl {
+            url = "mirror://ubuntu/dists/jammy/main/binary-amd64/Packages.xz";
+            sha256 = "sha256-N8tX8VVMv6ccWinun/7hipqMF4K7BWjgh0t/9M6PnBE=";
+          })
+          (fetchurl {
+            url = "mirror://ubuntu/dists/jammy/universe/binary-amd64/Packages.xz";
+            sha256 = "sha256-0pyyTJP+xfQyVXBrzn60bUd5lSA52MaKwbsUpvNlXOI=";
+          })
+        ];
+      urlPrefix = "mirror://ubuntu";
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    debian10i386 = {
+      name = "debian-10.13-buster-i386";
+      fullName = "Debian 10.13 Buster (i386)";
+      packagesList = fetchurl {
+        url = "https://snapshot.debian.org/archive/debian/20221126T084953Z/dists/buster/main/binary-i386/Packages.xz";
+        hash = "sha256-n9JquhtZgxw3qr9BX0MQoY3ZTIHN0dit+iru3DC31UY=";
+      };
+      urlPrefix = "https://snapshot.debian.org/archive/debian/20221126T084953Z";
+      packages = commonDebianPackages;
+    };
+
+    debian10x86_64 = {
+      name = "debian-10.13-buster-amd64";
+      fullName = "Debian 10.13 Buster (amd64)";
+      packagesList = fetchurl {
+        url = "https://snapshot.debian.org/archive/debian/20221126T084953Z/dists/buster/main/binary-amd64/Packages.xz";
+        hash = "sha256-YukIIB3u87jgp9oudwklsxyKVKjSL618wFgDSXiFmjU=";
+      };
+      urlPrefix = "https://snapshot.debian.org/archive/debian/20221126T084953Z";
+      packages = commonDebianPackages;
+    };
+
+    debian11i386 = {
+      name = "debian-11.8-bullseye-i386";
+      fullName = "Debian 11.8 Bullseye (i386)";
+      packagesList = fetchurl {
+        url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bullseye/main/binary-i386/Packages.xz";
+        hash = "sha256-0bKSLLPhEC7FB5D1NA2jaQP0wTe/Qp1ddiA/NDVjRaI=";
+      };
+      urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
+      packages = commonDebianPackages;
+    };
+
+    debian11x86_64 = {
+      name = "debian-11.8-bullseye-amd64";
+      fullName = "Debian 11.8 Bullseye (amd64)";
+      packagesList = fetchurl {
+        url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bullseye/main/binary-amd64/Packages.xz";
+        hash = "sha256-CYPsGgQgJZkh3JmbcAQkYDWP193qrkOADOgrMETZIeo=";
+      };
+      urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
+      packages = commonDebianPackages;
+    };
+
+    debian12i386 = {
+      name = "debian-12.2-bookworm-i386";
+      fullName = "Debian 12.2 Bookworm (i386)";
+      packagesList = fetchurl {
+        url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bookworm/main/binary-i386/Packages.xz";
+        hash = "sha256-OeN9Q2HFM3GsPNhOa4VhM7qpwT66yUNwC+6Z8SbGEeQ=";
+      };
+      urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
+      packages = commonDebianPackages;
+    };
+
+    debian12x86_64 = {
+      name = "debian-12.2-bookworm-amd64";
+      fullName = "Debian 12.2 Bookworm (amd64)";
+      packagesList = fetchurl {
+        url = "https://snapshot.debian.org/archive/debian/20231124T031419Z/dists/bookworm/main/binary-amd64/Packages.xz";
+        hash = "sha256-SZDElRfe9BlBwDlajQB79Qdn08rv8whYoQDeVCveKVs=";
+      };
+      urlPrefix = "https://snapshot.debian.org/archive/debian/20231124T031419Z";
+      packages = commonDebianPackages;
+    };
+  };
+
+
+  /* Common packages for Fedora images. */
+  commonFedoraPackages = [
+    "autoconf"
+    "automake"
+    "basesystem"
+    "bzip2"
+    "curl"
+    "diffutils"
+    "fedora-release"
+    "findutils"
+    "gawk"
+    "gcc-c++"
+    "gzip"
+    "make"
+    "patch"
+    "perl"
+    "pkgconf-pkg-config"
+    "rpm"
+    "rpm-build"
+    "tar"
+    "unzip"
+  ];
+
+  commonCentOSPackages = [
+    "autoconf"
+    "automake"
+    "basesystem"
+    "bzip2"
+    "curl"
+    "diffutils"
+    "centos-release"
+    "findutils"
+    "gawk"
+    "gcc-c++"
+    "gzip"
+    "make"
+    "patch"
+    "perl"
+    "pkgconfig"
+    "rpm"
+    "rpm-build"
+    "tar"
+    "unzip"
+  ];
+
+  commonRHELPackages = [
+    "autoconf"
+    "automake"
+    "basesystem"
+    "bzip2"
+    "curl"
+    "diffutils"
+    "findutils"
+    "gawk"
+    "gcc-c++"
+    "gzip"
+    "make"
+    "patch"
+    "perl"
+    "pkgconfig"
+    "procps-ng"
+    "rpm"
+    "rpm-build"
+    "tar"
+    "unzip"
+  ];
+
+  /* Common packages for openSUSE images. */
+  commonOpenSUSEPackages = [
+    "aaa_base"
+    "autoconf"
+    "automake"
+    "bzip2"
+    "curl"
+    "diffutils"
+    "findutils"
+    "gawk"
+    "gcc-c++"
+    "gzip"
+    "make"
+    "patch"
+    "perl"
+    "pkg-config"
+    "rpm"
+    "tar"
+    "unzip"
+    "util-linux"
+    "gnu-getopt"
+  ];
+
+
+  /* Common packages for Debian/Ubuntu images. */
+  commonDebPackages = [
+    "base-passwd"
+    "dpkg"
+    "libc6-dev"
+    "perl"
+    "bash"
+    "dash"
+    "gzip"
+    "bzip2"
+    "tar"
+    "grep"
+    "mawk"
+    "sed"
+    "findutils"
+    "g++"
+    "make"
+    "curl"
+    "patch"
+    "locales"
+    "coreutils"
+    # Needed by checkinstall:
+    "util-linux"
+    "file"
+    "dpkg-dev"
+    "pkg-config"
+    # Needed because it provides /etc/login.defs, whose absence causes
+    # the "passwd" post-installs script to fail.
+    "login"
+    "passwd"
+  ];
+
+  commonDebianPackages = commonDebPackages ++ [ "sysvinit" "diff" ];
+
+
+  /* A set of functions that build the Linux distributions specified
+     in `rpmDistros' and `debDistros'.  For instance,
+     `diskImageFuns.ubuntu1004x86_64 { }' builds an Ubuntu 10.04 disk
+     image containing the default packages specified above.  Overrides
+     of the default image parameters can be given.  In particular,
+     `extraPackages' specifies the names of additional packages from
+     the distribution that should be included in the image; `packages'
+     allows the entire set of packages to be overridden; and `size'
+     sets the size of the disk in megabytes.  E.g.,
+     `diskImageFuns.ubuntu1004x86_64 { extraPackages = ["firefox"];
+     size = 8192; }' builds an 8 GiB image containing Firefox in
+     addition to the default packages. */
+  diskImageFuns =
+    (lib.mapAttrs (name: as: as2: makeImageFromRPMDist (as // as2)) rpmDistros) //
+    (lib.mapAttrs (name: as: as2: makeImageFromDebDist (as // as2)) debDistros);
+
+
+  /* Shorthand for `diskImageFuns.<attr> { extraPackages = ... }'. */
+  diskImageExtraFuns =
+    lib.mapAttrs (name: f: extraPackages: f { inherit extraPackages; }) diskImageFuns;
+
+
+  /* Default disk images generated from the `rpmDistros' and
+     `debDistros' sets. */
+  diskImages = lib.mapAttrs (name: f: f {}) diskImageFuns;
+
+}
diff --git a/nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl b/nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl
new file mode 100644
index 000000000000..6442cd91a957
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl
@@ -0,0 +1,184 @@
+use strict;
+use XML::Simple;
+use List::Util qw(min);
+
+my @packagesFiles = ();
+my @urlPrefixes = ();
+
+# rpm-closure.pl (<package-file> <url-prefix>)+ <toplevel-pkg>+
+
+while(-f $ARGV[0]) {
+    my $packagesFile = shift @ARGV;
+    my $urlPrefix = shift @ARGV;
+    push(@packagesFiles, $packagesFile);
+    push(@urlPrefixes, $urlPrefix);
+}
+
+
+sub rpmvercmp {
+    my ($version1, $version2) = @_;
+    my @vercmps1 = split /\./, $version1;
+    my @vercmps2 = split /\./, $version2;
+    my $l1 = scalar(@vercmps1);
+    my $l2 = scalar(@vercmps2);
+    my $l = min($l1, $l2);
+
+    for(my $i=0; $i<$l; $i++) {
+        my $v1 = $vercmps1[$i];
+        my $v2 = $vercmps2[$i];
+
+        if($v1 =~ /^[0-9]*$/ && $v2 =~ /^[0-9]*$/) {
+            if ( int($v1) > int($v2) ) {
+                return 1;
+            }
+            elsif ( int($v1) < int($v2) ) {
+                return -1;
+            }
+        } else {
+            if ( $v1 gt $v2 ) {
+                return 1;
+            }
+            elsif ( $v1 lt $v2 ) {
+                return -1;
+            }
+        }
+    }
+    if($l1 == $l2) {
+        return 0;
+    } elsif ($l1 > $l2) {
+        return 1;
+    } elsif ($l1 < $l2) {
+        return -1;
+    }
+}
+
+my @toplevelPkgs = @ARGV;
+
+my @archs = split ' ', ($ENV{'archs'} or "");
+
+my %pkgs;
+for (my $i = 0; $i < scalar(@packagesFiles); $i++) {
+    my $packagesFile = $packagesFiles[$i];
+    print STDERR "parsing packages in $packagesFile...\n";
+
+    my $xml = XMLin($packagesFile, ForceArray => ['package', 'rpm:entry', 'file'], KeyAttr => []) or die;
+
+    print STDERR "$packagesFile contains $xml->{packages} packages\n";
+
+    foreach my $pkg (@{$xml->{'package'}}) {
+        if (scalar @archs > 0) {
+            my $arch = $pkg->{arch};
+            my $found = 0;
+            foreach my $a (@archs) { $found = 1 if $arch eq $a; }
+            next if !$found;
+        }
+        if (defined $pkgs{$pkg->{name}}) {
+            my $earlierPkg = $pkgs{$pkg->{name}};
+            print STDERR "WARNING: duplicate occurrence of package $pkg->{name}\n";
+            #   <version epoch="0" ver="1.28.0" rel="2.el6"/>
+            my $cmp = rpmvercmp($pkg->{'version'}->{ver}, $earlierPkg->{'version'}->{ver});
+            if ($cmp > 0 || ($cmp == 0 && rpmvercmp($pkg->{'version'}->{rel}, $earlierPkg->{'version'}->{rel})>0)) {
+                print STDERR "WARNING: replaced package $pkg->{name} (".$earlierPkg->{'version'}->{ver}." ".$earlierPkg->{'version'}->{rel}.") with newer one (".$pkg->{'version'}->{ver}." ".$pkg->{'version'}->{rel}.")\n";
+                $pkg->{urlPrefix} = $urlPrefixes[$i];
+                $pkgs{$pkg->{name}} = $pkg;
+            }
+            next;
+        }
+        $pkg->{urlPrefix} = $urlPrefixes[$i];
+        $pkgs{$pkg->{name}} = $pkg;
+    }
+}
+
+my %provides;
+PKG: foreach my $pkgName (sort(keys %pkgs)) {
+    #print STDERR "looking at $pkgName\n";
+    my $pkg = $pkgs{$pkgName};
+
+    # Skip packages that conflict with a required package.
+    my $conflicts = $pkg->{format}->{'rpm:conflicts'}->{'rpm:entry'} // [];
+    foreach my $conflict (@{$conflicts}) {
+        next if $conflict->{flags} // "" eq "LT" || $conflict->{flags} // "" eq "LE";
+        #print STDERR "  $pkgName conflicts with $conflict->{name}\n";
+        if (grep { $_ eq $conflict->{name} } @toplevelPkgs) {
+            print STDERR "skipping package $pkgName because it conflicts with a required package\n";
+            next PKG;
+        }
+    }
+
+    my $provides = $pkg->{format}->{'rpm:provides'}->{'rpm:entry'} or die;
+    foreach my $req (@{$provides}) {
+        #print STDERR "  $pkgName provides $req->{name}\n";
+        #die "multiple provides for $req->{name}" if defined $provides{$req->{name}};
+        $provides{$req->{name}} = $pkgName;
+    }
+
+    if (defined $pkg->{format}->{file}) {
+        foreach my $file (@{$pkg->{format}->{file}}) {
+          #print STDERR "  provides file $file\n";
+          $provides{$file} = $pkgName;
+        }
+    }
+}
+
+
+my %donePkgs;
+my @needed = ();
+
+sub closePackage {
+    my $pkgName = shift;
+
+    return if defined $donePkgs{$pkgName};
+    $donePkgs{$pkgName} = 1;
+
+    print STDERR ">>> $pkgName\n";
+
+    my $pkg = $pkgs{$pkgName} or die "package $pkgName doesn't exist";
+
+    my $requires = $pkg->{format}->{'rpm:requires'}->{'rpm:entry'} || [];
+
+    my @deps = ();
+    foreach my $req (@{$requires}) {
+        next if $req->{name} =~ /^rpmlib\(/;
+        #print STDERR "  needs $req->{name}\n";
+        my $provider = $provides{$req->{name}};
+        if (!defined $provider) {
+            print STDERR "    WARNING: no provider for $req->{name}\n";
+            next;
+        }
+        #print STDERR "    satisfied by $provider\n";
+        push @deps, $provider;
+    }
+
+    closePackage($_) foreach @deps;
+
+    push @needed, $pkgName;
+}
+
+
+foreach my $pkgName (@toplevelPkgs) {
+    closePackage $pkgName;
+}
+
+
+# Generate the output Nix expression.
+print "# This is a generated file.  Do not modify!\n";
+print "# Following are the RPM packages constituting the closure of: @toplevelPkgs\n\n";
+print "{fetchurl}:\n\n";
+print "[\n\n";
+
+foreach my $pkgName (@needed) {
+    my $pkg = $pkgs{$pkgName};
+    print "  (fetchurl {\n";
+    print "    url = $pkg->{urlPrefix}/$pkg->{location}->{href};\n";
+    if ($pkg->{checksum}->{type} eq "sha") {
+        print "    sha1 = \"$pkg->{checksum}->{content}\";\n";
+    } elsif ($pkg->{checksum}->{type} eq "sha256") {
+        print "    sha256 = \"$pkg->{checksum}->{content}\";\n";
+    } else {
+        die "unsupported hash type";
+    }
+    print "  })\n";
+    print "\n";
+}
+
+print "]\n";
diff --git a/nixpkgs/pkgs/build-support/vm/test.nix b/nixpkgs/pkgs/build-support/vm/test.nix
new file mode 100644
index 000000000000..50dbfeb750be
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/test.nix
@@ -0,0 +1,58 @@
+let
+  pkgs = import ../../.. { };
+
+  inherit (pkgs)
+    hello
+    patchelf
+    pcmanfm
+    stdenv
+    ;
+
+  inherit (pkgs.vmTools)
+    buildRPM
+    diskImages
+    makeImageTestScript
+    runInLinuxImage
+    runInLinuxVM
+    ;
+in
+
+{
+
+
+  # Run the PatchELF derivation in a VM.
+  buildPatchelfInVM = runInLinuxVM patchelf;
+
+  buildHelloInVM = runInLinuxVM hello;
+
+  buildPcmanrmInVM = runInLinuxVM (pcmanfm.overrideAttrs (old: {
+    # goes out-of-memory with many cores
+    enableParallelBuilding = false;
+  }));
+
+  testRPMImage = makeImageTestScript diskImages.fedora27x86_64;
+
+
+  buildPatchelfRPM = buildRPM {
+    name = "patchelf-rpm";
+    src = patchelf.src;
+    diskImage = diskImages.fedora27x86_64;
+    diskImageFormat = "qcow2";
+  };
+
+
+  testUbuntuImage = makeImageTestScript diskImages.ubuntu1804i386;
+
+
+  buildInDebian = runInLinuxImage (stdenv.mkDerivation {
+    name = "deb-compile";
+    src = patchelf.src;
+    diskImage = diskImages.ubuntu1804i386;
+    diskImageFormat = "qcow2";
+    memSize = 512;
+    postHook = ''
+      dpkg-query --list
+    '';
+  });
+
+}