about summary refs log tree commit diff
path: root/nixpkgs/pkgs/build-support/vm
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2019-01-07 02:18:36 +0000
committerAlyssa Ross <hi@alyssa.is>2019-01-07 02:18:47 +0000
commit36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2 (patch)
treeb3faaf573407b32aa645237a4d16b82778a39a92 /nixpkgs/pkgs/build-support/vm
parent4e31070265257dc67d120c27e0f75c2344fdfa9a (diff)
parentabf060725d7614bd3b9f96764262dfbc2f9c2199 (diff)
downloadnixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.gz
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.bz2
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.lz
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.xz
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.tar.zst
nixlib-36f56d99fa0a0765c9f1de4a5f17a9b05830c3f2.zip
Add 'nixpkgs/' from commit 'abf060725d7614bd3b9f96764262dfbc2f9c2199'
git-subtree-dir: nixpkgs
git-subtree-mainline: 4e31070265257dc67d120c27e0f75c2344fdfa9a
git-subtree-split: abf060725d7614bd3b9f96764262dfbc2f9c2199
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.nix1167
-rw-r--r--nixpkgs/pkgs/build-support/vm/rpm/rpm-closure.pl184
-rw-r--r--nixpkgs/pkgs/build-support/vm/test.nix39
-rw-r--r--nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix83
-rw-r--r--nixpkgs/pkgs/build-support/vm/windows/controller/default.nix262
-rw-r--r--nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix56
-rw-r--r--nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py78
-rw-r--r--nixpkgs/pkgs/build-support/vm/windows/default.nix45
-rw-r--r--nixpkgs/pkgs/build-support/vm/windows/install/default.nix74
-rw-r--r--nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix123
11 files changed, 2291 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..bed397d6f07e
--- /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 (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..7880d98e6b6a
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/default.nix
@@ -0,0 +1,1167 @@
+{ pkgs
+, kernel ? pkgs.linux
+, img ? pkgs.stdenv.hostPlatform.platform.kernelTarget
+, 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.isi686 || pkgs.stdenv.isx86_64) "rtc_cmos"
+}:
+
+with pkgs;
+with import ../../../nixos/lib/qemu-flags.nix { inherit pkgs; };
+
+rec {
+
+  qemu = pkgs.qemu_kvm;
+
+  modulesClosure = makeModulesClosure {
+    inherit kernel rootModules;
+    firmware = kernel;
+  };
+
+
+  hd = "vda"; # either "sda" or "vda"
+
+  initrdUtils = runCommand "initrd-utils"
+    { buildInputs = [ 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.glibc.out}/lib/ld-linux*.so.? $out/lib
+      cp -p ${pkgs.stdenv.glibc.out}/lib/libc.so.* $out/lib
+      cp -p ${pkgs.stdenv.glibc.out}/lib/libm.so.* $out/lib
+      cp -p ${pkgs.stdenv.glibc.out}/lib/libresolv.so.* $out/lib
+
+      # Copy BusyBox.
+      cp -pd ${pkgs.busybox}/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-linux*.so.? --set-rpath $out/lib $i || true
+          fi
+      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=1)
+          mountDisk=1
+          ;;
+        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
+
+    ifconfig lo up
+
+    mkdir /fs
+
+    if test -z "$mountDisk"; then
+      mount -t tmpfs none /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
+
+    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,cache=loose
+
+    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
+
+    echo "starting stage 2 ($command)"
+    exec switch_root /fs $command $out
+  '';
+
+
+  initrd = 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.
+    ${pkgs.utillinux}/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=${linux}/lib/modules/
+    ${coreutils}/bin/cat <<EOF > /run/modprobe
+    #! /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/${qemuSerialDevice} &> /dev/${qemuSerialDevice}
+    fi
+  '';
+
+
+  qemuCommandLinux = ''
+    ${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=${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"
+  '';
+
+
+  createEmptyImage = {size, fullName}: ''
+    mkdir $out
+    diskImage=$out/disk-image.qcow2
+    ${qemu}/bin/qemu-img create -f qcow2 $diskImage "${toString size}M"
+
+    mkdir $out/nix-support
+    echo "${fullName}" > $out/nix-support/full-name
+  '';
+
+
+  defaultCreateRootFS = ''
+    mkdir /mnt
+    ${e2fsprogs}/bin/mkfs.ext4 /dev/${hd}
+    ${utillinux}/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} :
+    with pkgs; runInLinuxVM (
+    stdenv.mkDerivation {
+      name = "extract-file";
+      buildInputs = [ utillinux ];
+      buildCommand = ''
+        ln -s ${linux}/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} :
+    with pkgs; runInLinuxVM (
+    stdenv.mkDerivation {
+      name = "extract-file-mtd";
+      buildInputs = [ utillinux mtdutils ];
+      buildCommand = ''
+        ln -s ${linux}/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 = 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 -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 = if attrs ? postHook then attrs.postHook else "";
+
+    /* 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}
+        ${utillinux}/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
+          ${utillinux}/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 --extract-over-symlinks
+        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
+
+        ${utillinux}/bin/mount -o bind /tmp /mnt/tmp
+
+        echo "installing RPMs..."
+        PATH=/usr/bin:/bin:/usr/sbin:/sbin $chroot /mnt \
+          rpm -iv --nosignature ${if runScripts then "" else "--noscripts"} $rpms
+
+        echo "running post-install script..."
+        eval "$postInstall"
+
+        rm /mnt/.debug
+
+        ${utillinux}/bin/umount /mnt${storeDir} /mnt/tmp ${lib.optionalString unifiedSystemDir "/mnt/proc"}
+        ${utillinux}/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 "$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 ({
+    phases = "prepareImagePhase sysInfoPhase buildPhase installPhase";
+
+    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
+      header "installed RPM packages"
+      rpm -qa --qf "%{Name}-%{Version}-%{Release} (%{Arch}; %{Distribution}; %{Vendor})\n"
+      stopNest
+    '';
+
+    buildPhase = ''
+      eval "$preBuild"
+
+      # Hacky: RPM looks for <basename>.spec inside the tarball, so
+      # strip off the hash.
+      srcName="$(stripHash "$src")"
+      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
+        header "Generated RPM/SRPM: $i"
+        rpm -qip $i
+        stopNest
+      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 }:
+
+    runInLinuxVM (stdenv.mkDerivation {
+      inherit name postInstall QEMU_OPTS memSize;
+
+      debs = (lib.intersperse "|" debs);
+
+      preVM = createEmptyImage {inherit size fullName;};
+
+      buildCommand = ''
+        ${createRootFS}
+
+        PATH=$PATH:${stdenv.lib.makeBinPath [ dpkg dpkg glibc lzma ]}
+
+        # 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}
+        ${utillinux}/bin/mount -o bind ${storeDir} /mnt/inst${storeDir}
+        ${utillinux}/bin/mount -o bind /proc /mnt/proc
+        ${utillinux}/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"
+        ln -sf dash /mnt/bin/sh
+
+        rm /mnt/.debug
+
+        ${utillinux}/bin/umount /mnt/inst${storeDir}
+        ${utillinux}/bin/umount /mnt/proc
+        ${utillinux}/bin/umount /mnt/dev
+        ${utillinux}/bin/umount /mnt
+      '';
+
+      passthru = { inherit fullName; };
+    });
+
+
+  /* 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" {buildInputs = [perl 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" { buildInputs = [ perl 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
+
+      # Work around this bug: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=452279
+      sed -i ./Packages -e s/x86_64-linux-gnu/x86-64-linux-gnu/g
+
+      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 ? []
+    , QEMU_OPTS ? "", memSize ? 512 }:
+
+    let
+      expr = debClosureGenerator {
+        inherit name packagesLists urlPrefix;
+        packages = packages ++ extraPackages;
+      };
+    in
+      (fillDiskWithDebs {
+        inherit name fullName size postInstall QEMU_OPTS memSize;
+        debs = import expr {inherit fetchurl;} ++ extraDebs;
+      }) // {inherit expr;};
+
+
+  /* The set of supported RPM-based distributions. */
+
+  rpmDistros = {
+
+    # Note: no i386 release for Fedora >= 26
+    fedora26x86_64 =
+      let version = "26";
+      in rec {
+        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 rec {
+        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)";
+        # N.B. Switch to vault.centos.org when the next release comes out
+        # urlPrefix = "http://vault.centos.org/${version}/os/i386";
+        urlPrefix = "http://mirror.centos.org/centos-6/${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)";
+        # N.B. Switch to vault.centos.org when the next release comes out
+        # urlPrefix = "http://vault.centos.org/${version}/os/x86_64";
+        urlPrefix = "http://mirror.centos.org/centos-6/${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)";
+        # N.B. Switch to vault.centos.org when the next release comes out
+        # urlPrefix = "http://vault.centos.org/${version}/os/x86_64";
+        urlPrefix = "http://mirror.centos.org/centos-7/${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 = rec {
+
+    # Interestingly, the SHA-256 hashes provided by Ubuntu in
+    # http://nl.archive.ubuntu.com/ubuntu/dists/{gutsy,hardy}/Release are
+    # wrong, but the SHA-1 and MD5 hashes are correct.  Intrepid is fine.
+
+    ubuntu1204i386 = {
+      name = "ubuntu-12.04-precise-i386";
+      fullName = "Ubuntu 12.04 Precise (i386)";
+      packagesLists =
+        [ (fetchurl {
+            url = mirror://ubuntu/dists/precise/main/binary-i386/Packages.bz2;
+            sha256 = "18ns9h4qhvjfcip9z55grzi371racxavgqkp6b5kfkdq2wwwax2d";
+          })
+          (fetchurl {
+            url = mirror://ubuntu/dists/precise/universe/binary-i386/Packages.bz2;
+            sha256 = "085lkzbnzkc74kfdmwdc32sfqyfz8dr0rbiifk8kx9jih3xjw2jk";
+          })
+        ];
+      urlPrefix = mirror://ubuntu;
+      packages = commonDebPackages ++ [ "diffutils" ];
+    };
+
+    ubuntu1204x86_64 = {
+      name = "ubuntu-12.04-precise-amd64";
+      fullName = "Ubuntu 12.04 Precise (amd64)";
+      packagesLists =
+        [ (fetchurl {
+            url = mirror://ubuntu/dists/precise/main/binary-amd64/Packages.bz2;
+            sha256 = "1aabpn0hdih6cbabyn87yvhccqj44q9k03mqmjsb920iqlckl3fc";
+          })
+          (fetchurl {
+            url = mirror://ubuntu/dists/precise/universe/binary-amd64/Packages.bz2;
+            sha256 = "0x4hz5aplximgb7gnpvrhkw8m7a40s80rkm5b8hil0afblwlg4vr";
+          })
+        ];
+      urlPrefix = mirror://ubuntu;
+      packages = commonDebPackages ++ [ "diffutils" ];
+    };
+
+    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" ];
+    };
+
+    ubuntu1710i386 = {
+      name = "ubuntu-17.10-artful-i386";
+      fullName = "Ubuntu 17.10 Artful (i386)";
+      packagesLists =
+        [ (fetchurl {
+            url = mirror://ubuntu/dists/artful/main/binary-i386/Packages.xz;
+            sha256 = "18yrj4kqdzm39q0527m97h5ing58hkm9yq9iyj636zh2rclym3c8";
+          })
+          (fetchurl {
+            url = mirror://ubuntu/dists/artful/universe/binary-i386/Packages.xz;
+            sha256 = "1v0njw2w80xfmxi7by76cs8hyxlla5h3gqajlpdw5srjgx2qrm2g";
+          })
+        ];
+      urlPrefix = mirror://ubuntu;
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    ubuntu1710x86_64 = {
+      name = "ubuntu-17.10-artful-amd64";
+      fullName = "Ubuntu 17.10 Artful (amd64)";
+      packagesLists =
+        [ (fetchurl {
+            url = mirror://ubuntu/dists/artful/main/binary-amd64/Packages.xz;
+            sha256 = "104g57j1l3vi8wb5f7rgjvjhf82ccs0vwhc59jfc4ynd51z7fqjk";
+          })
+          (fetchurl {
+            url = mirror://ubuntu/dists/artful/universe/binary-amd64/Packages.xz;
+            sha256 = "1qzs95wfy9inaskfx9cf1l5yd3aaqwzy72zzi9xyvkxi75k5gcn4";
+          })
+        ];
+      urlPrefix = mirror://ubuntu;
+      packages = commonDebPackages ++ [ "diffutils" "libc-bin" ];
+    };
+
+    debian8i386 = {
+      name = "debian-8.11-jessie-i386";
+      fullName = "Debian 8.11 Jessie (i386)";
+      packagesList = fetchurl {
+        url = mirror://debian/dists/jessie/main/binary-i386/Packages.xz;
+        sha256 = "0adblarhx50yga900il6m25ng0csa81i3wid1dxxmydbdmri7v7d";
+      };
+      urlPrefix = mirror://debian;
+      packages = commonDebianPackages;
+    };
+
+    debian8x86_64 = {
+      name = "debian-8.11-jessie-amd64";
+      fullName = "Debian 8.11 Jessie (amd64)";
+      packagesList = fetchurl {
+        url = mirror://debian/dists/jessie/main/binary-amd64/Packages.xz;
+        sha256 = "09y1mv4kqllhxpk1ibjsyl5jig5bp0qxw6pp4sn56rglrpygmn5x";
+      };
+      urlPrefix = mirror://debian;
+      packages = commonDebianPackages;
+    };
+
+    debian9i386 = {
+      name = "debian-9.4-stretch-i386";
+      fullName = "Debian 9.4 Stretch (i386)";
+      packagesList = fetchurl {
+        url = http://snapshot.debian.org/archive/debian/20180912T154744Z/dists/stretch/main/binary-i386/Packages.xz;
+        sha256 = "0flvn8zn7vk04p10ndf3aq0mdr8k2ic01g51aq4lsllkv8lmwzyh";
+      };
+      urlPrefix = mirror://debian;
+      packages = commonDebianPackages;
+    };
+
+    debian9x86_64 = {
+      name = "debian-9.4-stretch-amd64";
+      fullName = "Debian 9.4 Stretch (amd64)";
+      packagesList = fetchurl {
+        url = http://snapshot.debian.org/archive/debian/20180912T154744Z/dists/stretch/main/binary-amd64/Packages.xz;
+        sha256 = "11vnn9bba2jabixvabfbw9zparl326c88xn99di7pbr5xsnl15jm";
+      };
+      urlPrefix = mirror://debian;
+      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" "mktemp" ];
+
+
+  /* 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 overriden; 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;
+
+} // import ./windows pkgs
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..d0d85fce3662
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/test.nix
@@ -0,0 +1,39 @@
+with import ../../.. {};
+with vmTools;
+
+rec {
+
+
+  # Run the PatchELF derivation in a VM.
+  buildPatchelfInVM = runInLinuxVM patchelf;
+
+  buildHelloInVM = runInLinuxVM hello;
+
+  buildPanInVM = runInLinuxVM pan;
+
+
+  testRPMImage = makeImageTestScript diskImages.fedora16x86_64;
+
+
+  buildPatchelfRPM = buildRPM {
+    name = "patchelf-rpm";
+    src = patchelf.src;
+    diskImage = diskImages.fedora16x86_64;
+  };
+
+
+  testUbuntuImage = makeImageTestScript diskImages.ubuntu810i386;
+
+
+  buildInDebian = runInLinuxImage (stdenv.mkDerivation {
+    name = "deb-compile";
+    src = patchelf.src;
+    diskImage = diskImages.ubuntu1204i386;
+    memSize = 512;
+    phases = "sysInfoPhase unpackPhase patchPhase configurePhase buildPhase checkPhase installPhase fixupPhase distPhase";
+    sysInfoPhase = ''
+      dpkg-query --list
+    '';
+  });
+
+}
diff --git a/nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix b/nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix
new file mode 100644
index 000000000000..3b06d8f47490
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/windows/bootstrap.nix
@@ -0,0 +1,83 @@
+{ stdenv, fetchurl, vmTools, writeScript, writeText, runCommand, makeInitrd
+, python, perl, coreutils, dosfstools, gzip, mtools, netcat-gnu, openssh, qemu
+, samba, socat, vde2, cdrkit, pathsFromGraph, gnugrep
+}:
+
+{ isoFile, productKey, arch ? null }:
+
+with stdenv.lib;
+
+let
+  controller = import ./controller {
+    inherit stdenv writeScript vmTools makeInitrd;
+    inherit samba vde2 openssh socat netcat-gnu coreutils gzip gnugrep;
+  };
+
+  mkCygwinImage = import ./cygwin-iso {
+    inherit stdenv fetchurl runCommand python perl cdrkit pathsFromGraph;
+    arch = let
+      defaultArch = if stdenv.is64bit then "x86_64" else "i686";
+    in if arch == null then defaultArch else arch;
+  };
+
+  installer = import ./install {
+    inherit controller mkCygwinImage;
+    inherit stdenv runCommand openssh qemu writeText dosfstools mtools;
+  };
+in rec {
+  installedVM = installer {
+    inherit isoFile productKey;
+  };
+
+  runInVM = img: attrs: controller (attrs // {
+    inherit (installedVM) sshKey;
+    qemuArgs = attrs.qemuArgs or [] ++ [
+      "-boot order=c"
+      "-drive file=${img},index=0,media=disk"
+    ];
+  });
+
+  runAndSuspend = let
+    drives = {
+      s = {
+        source = "nixstore";
+        target = "/nix/store";
+      };
+      x = {
+        source = "xchg";
+        target = "/tmp/xchg";
+      };
+    };
+
+    genDriveCmds = letter: { source, target }: [
+      "net use ${letter}: '\\\\192.168.0.2\\${source}' /persistent:yes"
+      "mkdir -p '${target}'"
+      "mount -o bind '/cygdrive/${letter}' '${target}'"
+      "echo '/cygdrive/${letter} ${target} none bind 0 0' >> /etc/fstab"
+    ];
+  in runInVM "winvm.img" {
+    command = concatStringsSep " && " ([
+      "net config server /autodisconnect:-1"
+    ] ++ concatLists (mapAttrsToList genDriveCmds drives));
+    suspendTo = "state.gz";
+  };
+
+  suspendedVM = stdenv.mkDerivation {
+    name = "cygwin-suspended-vm";
+    buildCommand = ''
+      ${qemu}/bin/qemu-img create \
+        -b "${installedVM}/disk.img" \
+        -f qcow2 winvm.img
+      ${runAndSuspend}
+      mkdir -p "$out"
+      cp winvm.img "$out/disk.img"
+      cp state.gz "$out/state.gz"
+    '';
+  };
+
+  resumeAndRun = command: runInVM "${suspendedVM}/disk.img" {
+    resumeFrom = "${suspendedVM}/state.gz";
+    qemuArgs = singleton "-snapshot";
+    inherit command;
+  };
+}
diff --git a/nixpkgs/pkgs/build-support/vm/windows/controller/default.nix b/nixpkgs/pkgs/build-support/vm/windows/controller/default.nix
new file mode 100644
index 000000000000..e000308bed8f
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/windows/controller/default.nix
@@ -0,0 +1,262 @@
+{ stdenv, writeScript, vmTools, makeInitrd
+, samba, vde2, openssh, socat, netcat-gnu, coreutils, gnugrep, gzip
+}:
+
+{ sshKey
+, qemuArgs ? []
+, command ? "sync"
+, suspendTo ? null
+, resumeFrom ? null
+, installMode ? false
+}:
+
+with stdenv.lib;
+
+let
+  preInitScript = writeScript "preinit.sh" ''
+    #!${vmTools.initrdUtils}/bin/ash -e
+    export PATH=${vmTools.initrdUtils}/bin
+    mount -t proc none /proc
+    mount -t sysfs none /sys
+    for arg in $(cat /proc/cmdline); do
+      if [ "x''${arg#command=}" != "x$arg" ]; then
+        command="''${arg#command=}"
+      fi
+    done
+
+    for i in $(cat ${modulesClosure}/insmod-list); do
+      insmod $i
+    done
+
+    mkdir -p /dev /fs
+
+    mount -t tmpfs none /dev
+    mknod /dev/null    c 1 3
+    mknod /dev/zero    c 1 5
+    mknod /dev/random  c 1 8
+    mknod /dev/urandom c 1 9
+    mknod /dev/tty     c 5 0
+
+    ifconfig lo up
+    ifconfig eth0 up 192.168.0.2
+
+    mount -t tmpfs none /fs
+    mkdir -p /fs/nix/store /fs/xchg /fs/dev /fs/sys /fs/proc /fs/etc /fs/tmp
+
+    mount -o bind /dev /fs/dev
+    mount -t sysfs none /fs/sys
+    mount -t proc none /fs/proc
+
+    mount -t 9p \
+      -o trans=virtio,version=9p2000.L,cache=loose \
+      store /fs/nix/store
+
+    mount -t 9p \
+      -o trans=virtio,version=9p2000.L,cache=loose \
+      xchg /fs/xchg
+
+    echo root:x:0:0::/root:/bin/false > /fs/etc/passwd
+
+    set +e
+    chroot /fs $command $out
+    echo $? > /fs/xchg/in-vm-exit
+
+    poweroff -f
+  '';
+
+  initrd = makeInitrd {
+    contents = singleton {
+      object = preInitScript;
+      symlink = "/init";
+    };
+  };
+
+  loopForever = "while :; do ${coreutils}/bin/sleep 1; done";
+
+  initScript = writeScript "init.sh" (''
+    #!${stdenv.shell}
+    ${coreutils}/bin/cp -L "${sshKey}" /ssh.key
+    ${coreutils}/bin/chmod 600 /ssh.key
+  '' + (if installMode then ''
+    echo -n "Waiting for Windows installation to finish..."
+    while ! ${netcat-gnu}/bin/netcat -z 192.168.0.1 22; do
+      echo -n .
+      # Print a dot every 10 seconds only to shorten line length.
+      ${coreutils}/bin/sleep 10
+    done
+    ${coreutils}/bin/touch /xchg/waiting_done
+    echo " success."
+    # Loop forever, because this VM is going to be killed.
+    ${loopForever}
+  '' else ''
+    ${coreutils}/bin/mkdir -p /etc/samba /etc/samba/private \
+                              /var/lib/samba /var/log /var/run
+    ${coreutils}/bin/cat > /etc/samba/smb.conf <<CONFIG
+    [global]
+    security = user
+    map to guest = Bad User
+    guest account = root
+    workgroup = cygwin
+    netbios name = controller
+    server string = %h
+    log level = 1
+    max log size = 1000
+    log file = /var/log/samba.log
+
+    [nixstore]
+    path = /nix/store
+    writable = yes
+    guest ok = yes
+
+    [xchg]
+    path = /xchg
+    writable = yes
+    guest ok = yes
+    CONFIG
+
+    ${samba}/sbin/nmbd -D
+    ${samba}/sbin/smbd -D
+
+    echo -n "Waiting for Windows VM to become available..."
+    while ! ${netcat-gnu}/bin/netcat -z 192.168.0.1 22; do
+      echo -n .
+      ${coreutils}/bin/sleep 1
+    done
+    ${coreutils}/bin/touch /xchg/waiting_done
+    echo " success."
+
+    ${openssh}/bin/ssh \
+      -o UserKnownHostsFile=/dev/null \
+      -o StrictHostKeyChecking=no \
+      -i /ssh.key \
+      -l Administrator \
+      192.168.0.1 -- ${lib.escapeShellArg command}
+  '') + optionalString (suspendTo != null) ''
+    ${coreutils}/bin/touch /xchg/suspend_now
+    ${loopForever}
+  '');
+
+  kernelAppend = concatStringsSep " " [
+    "panic=1"
+    "loglevel=4"
+    "console=tty1"
+    "console=ttyS0"
+    "command=${initScript}"
+  ];
+
+  controllerQemuArgs = concatStringsSep " " (maybeKvm64 ++ [
+    "-pidfile $CTRLVM_PIDFILE"
+    "-nographic"
+    "-no-reboot"
+    "-virtfs local,path=/nix/store,security_model=none,mount_tag=store"
+    "-virtfs local,path=$XCHG_DIR,security_model=none,mount_tag=xchg"
+    "-kernel ${modulesClosure.kernel}/bzImage"
+    "-initrd ${initrd}/initrd"
+    "-append \"${kernelAppend}\""
+    "-net nic,vlan=0,macaddr=52:54:00:12:01:02,model=virtio"
+    "-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
+  ]);
+
+  maybeKvm64 = optional (stdenv.hostPlatform.system == "x86_64-linux") "-cpu kvm64";
+
+  cygwinQemuArgs = concatStringsSep " " (maybeKvm64 ++ [
+    "-monitor unix:$MONITOR_SOCKET,server,nowait"
+    "-pidfile $WINVM_PIDFILE"
+    "-nographic"
+    "-net nic,vlan=0,macaddr=52:54:00:12:01:01"
+    "-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
+    "-rtc base=2010-01-01,clock=vm"
+  ] ++ qemuArgs ++ optionals (resumeFrom != null) [
+    "-incoming 'exec: ${gzip}/bin/gzip -c -d \"${resumeFrom}\"'"
+  ]);
+
+  modulesClosure = overrideDerivation vmTools.modulesClosure (o: {
+    rootModules = o.rootModules ++ singleton "virtio_net";
+  });
+
+  preVM = ''
+    (set; declare -p) > saved-env
+    XCHG_DIR="$(${coreutils}/bin/mktemp -d nix-vm.XXXXXXXXXX --tmpdir)"
+    ${coreutils}/bin/mv saved-env "$XCHG_DIR/"
+
+    eval "$preVM"
+
+    QEMU_VDE_SOCKET="$(pwd)/vde.ctl"
+    MONITOR_SOCKET="$(pwd)/monitor"
+    WINVM_PIDFILE="$(pwd)/winvm.pid"
+    CTRLVM_PIDFILE="$(pwd)/ctrlvm.pid"
+    ${vde2}/bin/vde_switch -s "$QEMU_VDE_SOCKET" --dirmode 0700 &
+    echo 'alive?' | ${socat}/bin/socat - \
+      UNIX-CONNECT:$QEMU_VDE_SOCKET/ctl,retry=20
+  '';
+
+  vmExec = ''
+    ${vmTools.qemuProg} ${controllerQemuArgs} &
+    ${vmTools.qemuProg} ${cygwinQemuArgs} &
+    echo -n "Waiting for VMs to start up..."
+    timeout=60
+    while ! test -e "$WINVM_PIDFILE" -a -e "$CTRLVM_PIDFILE"; do
+      timeout=$(($timeout - 1))
+      echo -n .
+      if test $timeout -le 0; then
+        echo " timed out."
+        exit 1
+      fi
+      ${coreutils}/bin/sleep 1
+    done
+    echo " done."
+  '';
+
+  checkDropOut = ''
+    if ! test -e "$XCHG_DIR/waiting_done" &&
+       ! kill -0 $(< "$WINVM_PIDFILE"); then
+      echo "Windows VM has dropped out early, bailing out!" >&2
+      exit 1
+    fi
+  '';
+
+  toMonitor = "${socat}/bin/socat - UNIX-CONNECT:$MONITOR_SOCKET";
+
+  postVM = if suspendTo != null then ''
+    while ! test -e "$XCHG_DIR/suspend_now"; do
+      ${checkDropOut}
+      ${coreutils}/bin/sleep 1
+    done
+    ${toMonitor} <<CMD
+    stop
+    migrate_set_speed 4095m
+    migrate "exec:${gzip}/bin/gzip -c > '${suspendTo}'"
+    CMD
+    echo -n "Waiting for memory dump to finish..."
+    while ! echo info migrate | ${toMonitor} | \
+          ${gnugrep}/bin/grep -qi '^migration *status: *complete'; do
+      ${coreutils}/bin/sleep 1
+      echo -n .
+    done
+    echo " done."
+    echo quit | ${toMonitor}
+    wait $(< "$WINVM_PIDFILE")
+    eval "$postVM"
+    exit 0
+  '' else if installMode then ''
+    wait $(< "$WINVM_PIDFILE")
+    eval "$postVM"
+    exit 0
+  '' else ''
+    while kill -0 $(< "$CTRLVM_PIDFILE"); do
+      ${checkDropOut}
+    done
+    if ! test -e "$XCHG_DIR/in-vm-exit"; then
+      echo "Virtual machine didn't produce an exit code."
+      exit 1
+    fi
+    eval "$postVM"
+    exit $(< "$XCHG_DIR/in-vm-exit")
+  '';
+
+in writeScript "run-cygwin-vm.sh" ''
+  #!${stdenv.shell} -e
+  ${preVM}
+  ${vmExec}
+  ${postVM}
+''
diff --git a/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix
new file mode 100644
index 000000000000..76cd41a75bc7
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/default.nix
@@ -0,0 +1,56 @@
+{ stdenv, fetchurl, runCommand, python, perl, xorriso, pathsFromGraph
+, arch ? "x86_64"
+}:
+
+{ packages ? []
+, mirror ? "http://ftp.gwdg.de/pub/linux/sources.redhat.com/cygwin"
+, extraContents ? []
+}:
+
+let
+  cygPkgList = if arch == "x86_64" then fetchurl {
+    url = "${mirror}/x86_64/setup.ini";
+    sha256 = "0arrxvxbl85l82iy648snx5cl952w791p45p0dfg1xpiaf96cbkj";
+  } else fetchurl {
+    url = "${mirror}/x86/setup.ini";
+    sha256 = "1fayx34868vd5h2nah7chiw65sl3i9qzrwvs7lrlv2h8k412vb69";
+  };
+
+  cygwinCross = (import ../../../../.. {
+    localSystem = stdenv.hostPlatform;
+    crossSystem = {
+      libc = "msvcrt";
+      platform = {};
+      inherit arch;
+      config = "${arch}-w64-mingw32";
+    };
+  }).windows.cygwinSetup;
+
+  makeCygwinClosure = { packages, packageList }: let
+    expr = import (runCommand "cygwin.nix" { buildInputs = [ python ]; } ''
+      python ${./mkclosure.py} "${packages}" ${toString packageList} > "$out"
+    '');
+    gen = { url, hash }: {
+      source = fetchurl {
+        url = "${mirror}/${url}";
+        sha512 = hash;
+      };
+      target = url;
+    };
+  in map gen expr;
+
+in import ../../../../../nixos/lib/make-iso9660-image.nix {
+  inherit stdenv perl xorriso pathsFromGraph;
+  syslinux = null;
+  contents = [
+    { source = "${cygwinCross}/bin/setup.exe";
+      target = "setup.exe";
+    }
+    { source = cygPkgList;
+      target = "setup.ini";
+    }
+  ] ++ makeCygwinClosure {
+    packages = cygPkgList;
+    packageList = packages;
+  } ++ extraContents;
+}
diff --git a/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py
new file mode 100644
index 000000000000..4c0d67c43bac
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/windows/cygwin-iso/mkclosure.py
@@ -0,0 +1,78 @@
+# Ugliest Python code I've ever written. -- aszlig
+import sys
+
+def get_plist(path):
+    in_pack = False
+    in_str = False
+    current_key = None
+    buf = ""
+    packages = {}
+    package_name = None
+    package_attrs = {}
+    with open(path, 'r') as setup:
+        for line in setup:
+            if in_str and line.rstrip().endswith('"'):
+                package_attrs[current_key] = buf + line.rstrip()[:-1]
+                in_str = False
+                continue
+            elif in_str:
+                buf += line
+                continue
+
+            if line.startswith('@'):
+                in_pack = True
+                package_name = line[1:].strip()
+                package_attrs = {}
+            elif in_pack and ':' in line:
+                key, value = line.split(':', 1)
+                if value.lstrip().startswith('"'):
+                    if value.lstrip()[1:].rstrip().endswith('"'):
+                        value = value.strip().strip('"')
+                    else:
+                        in_str = True
+                        current_key = key.strip().lower()
+                        buf = value.lstrip()[1:]
+                        continue
+                package_attrs[key.strip().lower()] = value.strip()
+            elif in_pack:
+                in_pack = False
+                packages[package_name] = package_attrs
+    return packages
+
+def main():
+    packages = get_plist(sys.argv[1])
+    to_include = set()
+
+    def traverse(package):
+        to_include.add(package)
+        attrs = packages.get(package, {})
+        deps = attrs.get('requires', '').split()
+        for new_dep in set(deps) - to_include:
+            traverse(new_dep)
+
+    map(traverse, sys.argv[2:])
+
+    sys.stdout.write('[\n')
+    for package, attrs in packages.iteritems():
+        if package not in to_include:
+            cats = [c.lower() for c in attrs.get('category', '').split()]
+            if 'base' not in cats:
+                continue
+
+        install_line = attrs.get('install')
+        if install_line is None:
+            continue
+
+        url, size, hash = install_line.split(' ', 2)
+
+        pack = [
+            '  {',
+            '    url = "{0}";'.format(url),
+            '    hash = "{0}";'.format(hash),
+            '  }',
+        ];
+        sys.stdout.write('\n'.join(pack) + '\n')
+    sys.stdout.write(']\n')
+
+if __name__ == '__main__':
+    main()
diff --git a/nixpkgs/pkgs/build-support/vm/windows/default.nix b/nixpkgs/pkgs/build-support/vm/windows/default.nix
new file mode 100644
index 000000000000..e5ff13f0da9d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/windows/default.nix
@@ -0,0 +1,45 @@
+#note: the hardcoded /bin/sh is required for the VM's cygwin shell
+pkgs:
+
+let
+  bootstrapper = import ./bootstrap.nix {
+    inherit (pkgs) stdenv vmTools writeScript writeText runCommand makeInitrd;
+    inherit (pkgs) coreutils dosfstools gzip mtools netcat-gnu openssh qemu samba;
+    inherit (pkgs) socat vde2 fetchurl python perl cdrkit pathsFromGraph;
+    inherit (pkgs) gnugrep;
+  };
+
+  builder = ''
+    source /tmp/xchg/saved-env 2> /dev/null || true
+    export NIX_STORE=/nix/store
+    export NIX_BUILD_TOP=/tmp
+    export TMPDIR=/tmp
+    export PATH=/empty
+    cd "$NIX_BUILD_TOP"
+    exec $origBuilder $origArgs
+  '';
+
+in {
+  runInWindowsVM = drv: let
+  in pkgs.lib.overrideDerivation drv (attrs: let
+    bootstrap = bootstrapper attrs.windowsImage;
+  in {
+    requiredSystemFeatures = [ "kvm" ];
+    builder = "${pkgs.stdenv.shell}";
+    args = ["-e" (bootstrap.resumeAndRun builder)];
+    windowsImage = bootstrap.suspendedVM;
+    origArgs = attrs.args;
+    origBuilder = if attrs.builder == attrs.stdenv.shell
+                  then "/bin/sh"
+                  else attrs.builder;
+
+    postHook = ''
+      PATH=/usr/bin:/bin:/usr/sbin:/sbin
+      SHELL=/bin/sh
+      eval "$origPostHook"
+    '';
+
+    origPostHook = attrs.postHook or "";
+    fixupPhase = ":";
+  });
+}
diff --git a/nixpkgs/pkgs/build-support/vm/windows/install/default.nix b/nixpkgs/pkgs/build-support/vm/windows/install/default.nix
new file mode 100644
index 000000000000..fe8e8f61de02
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/windows/install/default.nix
@@ -0,0 +1,74 @@
+{ stdenv, runCommand, openssh, qemu, controller, mkCygwinImage
+, writeText, dosfstools, mtools
+}:
+
+{ isoFile
+, productKey
+}:
+
+let
+  bootstrapAfterLogin = runCommand "bootstrap.sh" {} ''
+    cat > "$out" <<EOF
+    mkdir -p ~/.ssh
+    cat > ~/.ssh/authorized_keys <<PUBKEY
+    $(cat "${cygwinSshKey}/key.pub")
+    PUBKEY
+    ssh-host-config -y -c 'binmode ntsec' -w dummy
+    cygrunsrv -S sshd
+    shutdown -s 5
+    EOF
+  '';
+
+  cygwinSshKey = stdenv.mkDerivation {
+    name = "snakeoil-ssh-cygwin";
+    buildCommand = ''
+      mkdir -p "$out"
+      ${openssh}/bin/ssh-keygen -t ecdsa -f "$out/key" -N ""
+    '';
+  };
+
+  sshKey = "${cygwinSshKey}/key";
+
+  packages = [ "openssh" "shutdown" ];
+
+  floppyCreator = import ./unattended-image.nix {
+    inherit stdenv writeText dosfstools mtools;
+  };
+
+  instfloppy = floppyCreator {
+    cygwinPackages = packages;
+    inherit productKey;
+  };
+
+  cygiso = mkCygwinImage {
+    inherit packages;
+    extraContents = stdenv.lib.singleton {
+      source = bootstrapAfterLogin;
+      target = "bootstrap.sh";
+    };
+  };
+
+  installController = controller {
+    inherit sshKey;
+    installMode = true;
+    qemuArgs = [
+      "-boot order=c,once=d"
+      "-drive file=${instfloppy},readonly,index=0,if=floppy"
+      "-drive file=winvm.img,index=0,media=disk"
+      "-drive file=${isoFile},index=1,media=cdrom"
+      "-drive file=${cygiso}/iso/cd.iso,index=2,media=cdrom"
+    ];
+  };
+
+in stdenv.mkDerivation {
+  name = "cygwin-base-vm";
+  buildCommand = ''
+    ${qemu}/bin/qemu-img create -f qcow2 winvm.img 2G
+    ${installController}
+    mkdir -p "$out"
+    cp winvm.img "$out/disk.img"
+  '';
+  passthru = {
+    inherit sshKey;
+  };
+}
diff --git a/nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix b/nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix
new file mode 100644
index 000000000000..5b1ff84cf44d
--- /dev/null
+++ b/nixpkgs/pkgs/build-support/vm/windows/install/unattended-image.nix
@@ -0,0 +1,123 @@
+{ stdenv, writeText, dosfstools, mtools }:
+
+{ productKey
+, shExecAfterwards ? "E:\\bootstrap.sh"
+, cygwinRoot ? "C:\\cygwin"
+, cygwinSetup ? "E:\\setup.exe"
+, cygwinRepository ? "E:\\"
+, cygwinPackages ? [ "openssh" ]
+}:
+
+let
+  afterSetup = [
+    cygwinSetup
+    "-L -n -q"
+    "-l ${cygwinRepository}"
+    "-R ${cygwinRoot}"
+    "-C base"
+  ] ++ map (p: "-P ${p}") cygwinPackages;
+
+  winXpUnattended = writeText "winnt.sif" ''
+    [Data]
+    AutoPartition = 1
+    AutomaticUpdates = 0
+    MsDosInitiated = 0
+    UnattendedInstall = Yes
+
+    [Unattended]
+    DUDisable = Yes
+    DriverSigningPolicy = Ignore
+    Hibernation = No
+    OemPreinstall = No
+    OemSkipEula = Yes
+    Repartition = Yes
+    TargetPath = \WINDOWS
+    UnattendMode = FullUnattended
+    UnattendSwitch = Yes
+    WaitForReboot = No
+
+    [GuiUnattended]
+    AdminPassword = "nopasswd"
+    AutoLogon = Yes
+    AutoLogonCount = 1
+    OEMSkipRegional = 1
+    OemSkipWelcome = 1
+    ServerWelcome = No
+    TimeZone = 85
+
+    [UserData]
+    ComputerName = "cygwin"
+    FullName = "cygwin"
+    OrgName = ""
+    ProductKey = "${productKey}"
+
+    [Networking]
+    InstallDefaultComponents = Yes
+
+    [Identification]
+    JoinWorkgroup = cygwin
+
+    [NetAdapters]
+    PrimaryAdapter = params.PrimaryAdapter
+
+    [params.PrimaryAdapter]
+    InfID = *
+
+    [params.MS_MSClient]
+
+    [NetProtocols]
+    MS_TCPIP = params.MS_TCPIP
+
+    [params.MS_TCPIP]
+    AdapterSections=params.MS_TCPIP.PrimaryAdapter
+
+    [params.MS_TCPIP.PrimaryAdapter]
+    DHCP = No
+    IPAddress = 192.168.0.1
+    SpecificTo = PrimaryAdapter
+    SubnetMask = 255.255.255.0
+    WINS = No
+
+    ; Turn off all components
+    [Components]
+    ${stdenv.lib.concatMapStrings (comp: "${comp} = Off\n") [
+      "AccessOpt" "Appsrv_console" "Aspnet" "BitsServerExtensionsISAPI"
+      "BitsServerExtensionsManager" "Calc" "Certsrv" "Certsrv_client"
+      "Certsrv_server" "Charmap" "Chat" "Clipbook" "Cluster" "Complusnetwork"
+      "Deskpaper" "Dialer" "Dtcnetwork" "Fax" "Fp_extensions" "Fp_vdir_deploy"
+      "Freecell" "Hearts" "Hypertrm" "IEAccess" "IEHardenAdmin" "IEHardenUser"
+      "Iis_asp" "Iis_common" "Iis_ftp" "Iis_inetmgr" "Iis_internetdataconnector"
+      "Iis_nntp" "Iis_serversideincludes" "Iis_smtp" "Iis_webdav" "Iis_www"
+      "Indexsrv_system" "Inetprint" "Licenseserver" "Media_clips" "Media_utopia"
+      "Minesweeper" "Mousepoint" "Msmq_ADIntegrated" "Msmq_Core"
+      "Msmq_HTTPSupport" "Msmq_LocalStorage" "Msmq_MQDSService"
+      "Msmq_RoutingSupport" "Msmq_TriggersService" "Msnexplr" "Mswordpad"
+      "Netcis" "Netoc" "OEAccess" "Objectpkg" "Paint" "Pinball" "Pop3Admin"
+      "Pop3Service" "Pop3Srv" "Rec" "Reminst" "Rootautoupdate" "Rstorage" "SCW"
+      "Sakit_web" "Solitaire" "Spider" "TSWebClient" "Templates"
+      "TerminalServer" "UDDIAdmin" "UDDIDatabase" "UDDIWeb" "Vol" "WMAccess"
+      "WMPOCM" "WbemMSI" "Wms" "Wms_admin_asp" "Wms_admin_mmc" "Wms_isapi"
+      "Wms_server" "Zonegames"
+    ]}
+
+    [WindowsFirewall]
+    Profiles = WindowsFirewall.TurnOffFirewall
+
+    [WindowsFirewall.TurnOffFirewall]
+    Mode = 0
+
+    [SetupParams]
+    UserExecute = "${stdenv.lib.concatStringsSep " " afterSetup}"
+
+    [GuiRunOnce]
+    Command0 = "${cygwinRoot}\bin\bash -l ${shExecAfterwards}"
+  '';
+
+in stdenv.mkDerivation {
+  name = "unattended-floppy.img";
+  buildCommand = ''
+    dd if=/dev/zero of="$out" count=1440 bs=1024
+    ${dosfstools}/sbin/mkfs.msdos "$out"
+    ${mtools}/bin/mcopy -i "$out" "${winXpUnattended}" ::winnt.sif
+  '';
+}