--- a/coverage.sh
+++ b/coverage.sh
@@ -9,10 +9,17 @@ mirrordir="./shared/debian"
 # we use -f because the file might not exist
 rm -f shared/cover_db.img
 
-# prepare image for cover_db
-guestfish -N shared/cover_db.img=disk:100M -- mkfs vfat /dev/sda
+: "${HAVE_QEMU:=yes}"
 
-cp mmdebstrap shared
+if [ "$HAVE_QEMU" = "yes" ]; then
+	# prepare image for cover_db
+	guestfish -N shared/cover_db.img=disk:100M -- mkfs vfat /dev/sda
+fi
+
+# only copy if necessary
+if [ ! -e shared/mmdebstrap ] || [ mmdebstrap -nt shared/mmdebstrap ]; then
+	cp -a mmdebstrap shared
+fi
 
 starttime=
 total=54
@@ -41,6 +48,15 @@ SOURCE_DATE_EPOCH=$(date --date="$(grep-
 # for traditional sort order that uses native byte values
 export LC_ALL=C
 
+: "${HAVE_UNSHARE:=yes}"
+: "${HAVE_PROOT:=yes}"
+: "${HAVE_BINFMT:=yes}"
+
+defaultmode="auto"
+if [ "$HAVE_UNSHARE" != "yes" ]; then
+	defaultmode="root"
+fi
+
 # by default, use the mmdebstrap executable in the current directory together
 # with perl Devel::Cover but allow to overwrite this
 : "${CMD:=perl -MDevel::Cover=-silent,-nogcov ./mmdebstrap}"
@@ -53,6 +69,12 @@ for dist in stable testing unstable; do
 			continue
 		fi
 		print_header "mode=root,variant=$variant: check against debootstrap $dist"
+
+		if [ ! -e "shared/cache/debian-$dist-$variant.tar" ]; then
+			echo "shared/cache/debian-$dist-$variant.tar does not exist. Skipping..."
+			continue
+		fi
+
 		cat << END > shared/test.sh
 #!/bin/sh
 set -eu
@@ -135,8 +157,11 @@ diff --no-dereference --brief --recursiv
 #
 # we cannot use this (yet) because it cannot copy with paths that have [ or @ in them
 #fmtree -c -p /tmp/debian-$dist-debootstrap -k flags,gid,link,mode,size,time,uid | sudo fmtree -p /tmp/debian-$dist-mm
+
+rm /tmp/debian-$dist-mm.tar
+rm -r /tmp/debian-$dist-debootstrap /tmp/debian-$dist-mm
 END
-		./run_qemu.sh
+		./run_qemu.sh SUDO
 	done
 done
 
@@ -147,10 +172,11 @@ set -eu
 export LC_ALL=C
 $CMD --mode=root --variant=apt unstable /tmp/debian-unstable $mirror
 tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort > tar1.txt
+rm -r /tmp/debian-unstable
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
-print_header "mode=unshare,variant=apt: test progress bars on fake tty"
+print_header "mode=root,variant=apt: test progress bars on fake tty"
 cat << END > shared/test.sh
 #!/bin/sh
 set -eu
@@ -158,8 +184,9 @@ export LC_ALL=C
 script -qfc "$CMD --mode=root --variant=apt unstable /tmp/unstable-chroot.tar $mirror" /dev/null
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
 print_header "mode=root,variant=apt: existing empty directory"
 cat << END > shared/test.sh
@@ -170,8 +197,9 @@ mkdir /tmp/debian-unstable
 $CMD --mode=root --variant=apt unstable /tmp/debian-unstable $mirror
 tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm -r /tmp/debian-unstable
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
 print_header "mode=unshare,variant=apt: create tarball"
 cat << END > shared/test.sh
@@ -183,19 +211,29 @@ sysctl -w kernel.unprivileged_userns_clo
 runuser -u user -- $CMD --mode=unshare --variant=apt unstable /tmp/unstable-chroot.tar $mirror
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+if [ "$HAVE_QEMU" = "yes" ]; then
+	./run_qemu.sh
+else
+	echo "HAVE_QEMU != yes -- Skipping test..."
+fi
 
-print_header "mode=auto,variant=apt: read from stdin, write to stdout"
+print_header "mode=$defaultmode,variant=apt: read from stdin, write to stdout"
 cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-echo "deb $mirror unstable main" | $CMD --variant=apt > /tmp/unstable-chroot.tar
+echo "deb $mirror unstable main" | $CMD --mode=$defaultmode --variant=apt > /tmp/unstable-chroot.tar
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+if [ "$defaultmode" = "root" ]; then
+	./run_qemu.sh SUDO
+else
+	./run_qemu.sh
+fi
 
 print_header "mode=auto,variant=apt: default mirror"
 cat << END > shared/test.sh
@@ -203,11 +241,16 @@ cat << END > shared/test.sh
 set -eu
 export LC_ALL=C
 echo "127.0.0.1 deb.debian.org" >> /etc/hosts
-$CMD --variant=apt unstable /tmp/unstable-chroot.tar
+$CMD --mode=$defaultmode --variant=apt unstable /tmp/unstable-chroot.tar
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+if [ "$defaultmode" = "root" ]; then
+	./run_qemu.sh SUDO
+else
+	./run_qemu.sh
+fi
 
 print_header "mode=auto,variant=apt: pass distribution but implicitly write to stdout"
 cat << END > shared/test.sh
@@ -215,33 +258,48 @@ cat << END > shared/test.sh
 set -eu
 export LC_ALL=C
 echo "127.0.0.1 deb.debian.org" >> /etc/hosts
-$CMD --variant=apt unstable > /tmp/unstable-chroot.tar
+$CMD --mode=$defaultmode --variant=apt unstable > /tmp/unstable-chroot.tar
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+if [ "$HAVE_QEMU" = "yes" ]; then
+	./run_qemu.sh
+else
+	echo "HAVE_QEMU != yes -- Skipping test..."
+fi
 
 print_header "mode=auto,variant=apt: mirror is -"
 cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-echo "deb $mirror unstable main" | $CMD --variant=apt unstable /tmp/unstable-chroot.tar -
+echo "deb $mirror unstable main" | $CMD --mode=$defaultmode --variant=apt unstable /tmp/unstable-chroot.tar -
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+if [ "$defaultmode" = "root" ]; then
+	./run_qemu.sh SUDO
+else
+	./run_qemu.sh
+fi
 
 print_header "mode=auto,variant=apt: mirror is deb..."
 cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-$CMD --variant=apt unstable /tmp/unstable-chroot.tar "deb $mirror unstable main"
+$CMD --mode=$defaultmode --variant=apt unstable /tmp/unstable-chroot.tar "deb $mirror unstable main"
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+if [ "$defaultmode" = "root" ]; then
+	./run_qemu.sh SUDO
+else
+	./run_qemu.sh
+fi
 
 print_header "mode=auto,variant=apt: mirror is real file"
 cat << END > shared/test.sh
@@ -249,22 +307,32 @@ cat << END > shared/test.sh
 set -eu
 export LC_ALL=C
 echo "deb $mirror unstable main" > /tmp/sources.list
-$CMD --variant=apt unstable /tmp/unstable-chroot.tar /tmp/sources.list
+$CMD --mode=$defaultmode --variant=apt unstable /tmp/unstable-chroot.tar /tmp/sources.list
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar /tmp/sources.list
 END
-./run_qemu.sh
+if [ "$defaultmode" = "root" ]; then
+	./run_qemu.sh SUDO
+else
+	./run_qemu.sh
+fi
 
 print_header "mode=auto,variant=apt: no mirror but data on stdin"
 cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-echo "deb $mirror unstable main" | $CMD --variant=apt unstable /tmp/unstable-chroot.tar
+echo "deb $mirror unstable main" | $CMD --mode=$defaultmode --variant=apt unstable /tmp/unstable-chroot.tar
 tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+if [ "$defaultmode" = "root" ]; then
+	./run_qemu.sh SUDO
+else
+	./run_qemu.sh
+fi
 
 print_header "mode=root,variant=apt: add foreign architecture"
 cat << END > shared/test.sh
@@ -276,8 +344,9 @@ $CMD --mode=root --variant=apt --archite
 rm /tmp/debian-unstable/var/lib/dpkg/arch
 tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm -r /tmp/debian-unstable
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
 print_header "mode=root,variant=apt: test --aptopt"
 cat << END > shared/test.sh
@@ -289,8 +358,9 @@ echo "Acquire::Check-Valid-Until false;"
 rm /tmp/debian-unstable/etc/apt/apt.conf.d/99mmdebstrap
 tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm -r /tmp/debian-unstable
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
 print_header "mode=root,variant=apt: test --dpkgopt"
 cat << END > shared/test.sh
@@ -302,8 +372,9 @@ echo "path-exclude=/usr/share/doc/*" | c
 rm /tmp/debian-unstable/etc/dpkg/dpkg.cfg.d/99mmdebstrap
 tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort > tar2.txt
 grep -v '^./usr/share/doc/.' tar1.txt | diff -u - tar2.txt
+rm -r /tmp/debian-unstable
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
 print_header "mode=root,variant=apt: test --include"
 cat << END > shared/test.sh
@@ -319,8 +390,9 @@ rm /tmp/debian-unstable/var/lib/dpkg/inf
 rm /tmp/debian-unstable/var/lib/dpkg/info/doc-debian.md5sums
 tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort > tar2.txt
 diff -u tar1.txt tar2.txt
+rm -r /tmp/debian-unstable
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
 # test all variants
 
@@ -332,8 +404,9 @@ set -eu
 export LC_ALL=C
 $CMD --mode=root --variant=$variant unstable /tmp/unstable-chroot.tar $mirror
 tar -tf /tmp/unstable-chroot.tar | sort > "$variant.txt"
+rm /tmp/unstable-chroot.tar
 END
-	./run_qemu.sh
+	./run_qemu.sh SUDO
 	# check if the other modes produce the same result in each variant
 	for mode in unshare fakechroot proot; do
 		# fontconfig doesn't install reproducibly because differences
@@ -353,18 +426,29 @@ END
 				;;
 		esac
 		print_header "mode=$mode,variant=$variant: create tarball"
+		if [ "$mode" = "unshare" ] && [ "$HAVE_UNSHARE" != "yes" ]; then
+			echo "HAVE_UNSHARE != yes -- Skipping test..."
+			continue
+		fi
+		if [ "$mode" = "proot" ] && [ "$HAVE_PROOT" != "yes" ]; then
+			echo "HAVE_PROOT != yes -- Skipping test..."
+			continue
+		fi
 		cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-adduser --gecos user --disabled-password user
+[ "\$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1 && adduser --gecos user --disabled-password user
 [ "$mode" = unshare ] && sysctl -w kernel.unprivileged_userns_clone=1
-runuser -u user -- $CMD --mode=$mode --variant=$variant unstable /tmp/unstable-chroot.tar $mirror
+prefix=
+[ "\$(id -u)" -eq 0 ] && prefix="runuser -u user --"
+\$prefix $CMD --mode=$mode --variant=$variant unstable /tmp/unstable-chroot.tar $mirror
 # in fakechroot mode, we use a fake ldconfig, so we have to
 # artificially add some files
 { tar -tf /tmp/unstable-chroot.tar;
   [ "$mode" = "fakechroot" ] && printf "./etc/ld.so.cache\n./var/cache/ldconfig/\n";
 } | sort | diff -u "./$variant.txt" -
+rm /tmp/unstable-chroot.tar
 END
 		./run_qemu.sh
 		# Devel::Cover doesn't survive mmdebstrap re-exec-ing itself
@@ -376,11 +460,14 @@ END
 #!/bin/sh
 set -eu
 export LC_ALL=C
-adduser --gecos user --disabled-password user
-runuser -u user -- fakechroot fakeroot $CMD --mode=$mode --variant=$variant unstable /tmp/unstable-chroot.tar $mirror
+[ "\$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1 && adduser --gecos user --disabled-password user
+prefix=
+[ "\$(id -u)" -eq 0 ] && prefix="runuser -u user --"
+\$prefix fakechroot fakeroot $CMD --mode=$mode --variant=$variant unstable /tmp/unstable-chroot.tar $mirror
 { tar -tf /tmp/unstable-chroot.tar;
   printf "./etc/ld.so.cache\n./var/cache/ldconfig/\n";
 } | sort | diff -u "./$variant.txt" -
+rm /tmp/unstable-chroot.tar
 END
 			./run_qemu.sh
 		fi
@@ -432,23 +519,25 @@ done
 
 # test extract variant also with chrootless mode
 for mode in root unshare fakechroot proot chrootless; do
-	prefix=
-	if [ "$mode" = "fakechroot" ]; then
-		# Devel::Cover doesn't survive mmdebstrap re-exec-ing itself
-		# with fakechroot, thus, we explicitly run mmdebstrap with
-		# fakechroot from the start
-		prefix="runuser -u user -- fakechroot fakeroot"
-	elif [ "$mode" != "root" ]; then
-		prefix="runuser -u user --"
-	fi
 	print_header "mode=$mode,variant=extract: unpack doc-debian"
+	if [ "$mode" = "unshare" ] && [ "$HAVE_UNSHARE" != "yes" ]; then
+		echo "HAVE_UNSHARE != yes -- Skipping test..."
+		continue
+	fi
+	if [ "$mode" = "proot" ] && [ "$HAVE_PROOT" != "yes" ]; then
+		echo "HAVE_PROOT != yes -- Skipping test..."
+		continue
+	fi
 	cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-adduser --gecos user --disabled-password user
+[ "\$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1 && adduser --gecos user --disabled-password user
 [ "$mode" = unshare ] && sysctl -w kernel.unprivileged_userns_clone=1
-$prefix $CMD --mode=$mode --variant=extract --include=doc-debian unstable /tmp/debian-unstable $mirror
+prefix=
+[ "\$(id -u)" -eq 0 ] && [ "$mode" != "root" ] && prefix="runuser -u user --"
+[ "$mode" = "fakechroot" ] && prefix="\$prefix fakechroot fakeroot"
+\$prefix $CMD --mode=$mode --variant=extract --include=doc-debian unstable /tmp/debian-unstable $mirror
 # delete contents of doc-debian
 rm /tmp/debian-unstable/usr/share/doc-base/debian-*
 rm -r /tmp/debian-unstable/usr/share/doc/debian
@@ -482,7 +571,11 @@ rm -f /tmp/debian-unstable/dev/zero
 # the rest should be empty directories that we can rmdir recursively
 find /tmp/debian-unstable -depth -print0 | xargs -0 rmdir
 END
-	./run_qemu.sh
+	if [ "$mode" = "root" ]; then
+		./run_qemu.sh SUDO
+	else
+		./run_qemu.sh
+	fi
 done
 
 print_header "mode=chrootless,variant=custom: install doc-debian"
@@ -490,8 +583,10 @@ cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-adduser --gecos user --disabled-password user
-runuser -u user -- $CMD --mode=chrootless --variant=custom --include=doc-debian unstable /tmp/debian-unstable $mirror
+[ "\$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1 && adduser --gecos user --disabled-password user
+prefix=
+[ "\$(id -u)" -eq 0 ] && prefix="runuser -u user --"
+\$prefix $CMD --mode=chrootless --variant=custom --include=doc-debian unstable /tmp/debian-unstable $mirror
 # delete contents of doc-debian
 rm /tmp/debian-unstable/usr/share/doc-base/debian-*
 rm -r /tmp/debian-unstable/usr/share/doc/debian
@@ -533,30 +628,37 @@ set -eu
 export LC_ALL=C
 $CMD --mode=root --variant=essential unstable /tmp/unstable-chroot.tar $mirror
 tar -tf /tmp/unstable-chroot.tar | sort > tar1.txt
+rm /tmp/unstable-chroot.tar
 END
-./run_qemu.sh
+./run_qemu.sh SUDO
 
 # FIXME: once fakechroot and proot are fixed, we can switch to variant=apt
 # FIXME: cannot test fakechroot or proot because of
 #        https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=909637
 for mode in root unshare fakechroot proot; do
-	prefix=
-	if [ "$mode" = "fakechroot" ]; then
-		# Devel::Cover doesn't survive mmdebstrap re-exec-ing itself
-		# with fakechroot, thus, we explicitly run mmdebstrap with
-		# fakechroot from the start
-		prefix="runuser -u user -- fakechroot fakeroot"
-	elif [ "$mode" != "root" ]; then
-		prefix="runuser -u user --"
-	fi
 	print_header "mode=$mode,variant=essential: create armhf tarball"
+	if [ "$HAVE_BINFMT" != "yes" ]; then
+		echo "HAVE_BINFMT != yes -- Skipping test..."
+		continue
+	fi
+	if [ "$mode" = "unshare" ] && [ "$HAVE_UNSHARE" != "yes" ]; then
+		echo "HAVE_UNSHARE != yes -- Skipping test..."
+		continue
+	fi
+	if [ "$mode" = "proot" ] && [ "$HAVE_PROOT" != "yes" ]; then
+		echo "HAVE_PROOT != yes -- Skipping test..."
+		continue
+	fi
 	cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
-adduser --gecos user --disabled-password user
+[ "\$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1 && adduser --gecos user --disabled-password user
 [ "$mode" = unshare ] && sysctl -w kernel.unprivileged_userns_clone=1
-$prefix $CMD --mode=$mode --variant=essential --architectures=armhf unstable /tmp/unstable-chroot.tar $mirror
+prefix=
+[ "\$(id -u)" -eq 0 ] && [ "$mode" != "root" ] && prefix="runuser -u user --"
+[ "$mode" = "fakechroot" ] && prefix="\$prefix fakechroot fakeroot"
+\$prefix $CMD --mode=$mode --variant=essential --architectures=armhf unstable /tmp/unstable-chroot.tar $mirror
 # we ignore differences between architectures by ignoring some files
 # and renaming others
 # in fakechroot mode, we use a fake ldconfig, so we have to
@@ -581,8 +683,13 @@ $prefix $CMD --mode=$mode --variant=esse
 	| grep -v '^./usr/share/man/man8/x86_64.8.gz$';
 	[ "$mode" = "proot" ] && printf "./etc/ld.so.preload\n";
 } | sort | diff -u - tar2.txt
+rm /tmp/unstable-chroot.tar
 END
-	./run_qemu.sh
+	if [ "$mode" = "root" ]; then
+		./run_qemu.sh SUDO
+	else
+		./run_qemu.sh
+	fi
 done
 
 # test if auto mode picks the right mode
@@ -591,18 +698,22 @@ done
 
 # test tty output
 
-guestfish add-ro shared/cover_db.img : run : mount /dev/sda / : tar-out / - \
-       | tar -C shared/cover_db --extract
+if [ "$HAVE_QEMU" = "yes" ]; then
+	guestfish add-ro shared/cover_db.img : run : mount /dev/sda / : tar-out / - \
+		| tar -C shared/cover_db --extract
+fi
 
-cover -nogcov -report html_basic shared/cover_db
-mkdir -p report
-for f in common.js coverage.html cover.css css.js mmdebstrap--branch.html mmdebstrap--condition.html mmdebstrap.html mmdebstrap--subroutine.html standardista-table-sorting.js; do
-	cp -a shared/cover_db/$f report
-done
-cover -delete shared/cover_db
+if [ -e shared/cover_db/runs ]; then
+	cover -nogcov -report html_basic shared/cover_db
+	mkdir -p report
+	for f in common.js coverage.html cover.css css.js mmdebstrap--branch.html mmdebstrap--condition.html mmdebstrap.html mmdebstrap--subroutine.html standardista-table-sorting.js; do
+		cp -a shared/cover_db/$f report
+	done
+	cover -delete shared/cover_db
 
-echo
-echo open file://$(pwd)/report/coverage.html in a browser
-echo
+	echo
+	echo open file://$(pwd)/report/coverage.html in a browser
+	echo
+fi
 
 rm shared/tar1.txt shared/tar2.txt
--- a/mmdebstrap
+++ b/mmdebstrap
@@ -87,8 +87,12 @@ sub get_tar_compress_options($) {
     return ();
 }
 
-sub test_unshare() {
+sub test_unshare($) {
+    my $verbose = shift;
     if ($EFFECTIVE_USER_ID == 0) {
+	if ($verbose) {
+	    print STDERR "E: cannot use unshare mode when executing as root\n";
+	}
 	return 0;
     }
     # arguments to syscalls have to be stored in their own variable or
@@ -99,9 +103,12 @@ sub test_unshare() {
     my $pid = fork() // die "fork() failed: $!";
     if ($pid == 0) {
 	my $ret = syscall &SYS_unshare, $unshare_flags;
-	if (($ret >> 8) == 0) {
+	if ($ret == 0) {
 	    exit 0;
 	} else {
+	    if ($verbose) {
+		print STDERR "E: unshare syscall failed: $!\n";
+	    }
 	    exit 1;
 	}
     }
@@ -113,10 +120,24 @@ sub test_unshare() {
     # executed without parameters
     system "newuidmap 2>/dev/null";
     if (($? >> 8) != 1) {
+	if ($verbose) {
+	    if (($? >> 8) == 127) {
+		print STDERR "E: cannot find newuidmap\n";
+	    } else {
+		print STDERR "E: newuidmap returned unknown exit status\n";
+	    }
+	}
 	return 0;
     }
     system "newgidmap 2>/dev/null";
     if (($? >> 8) != 1) {
+	if ($verbose) {
+	    if (($? >> 8) == 127) {
+		print STDERR "E: cannot find newgidmap\n";
+	    } else {
+		print STDERR "E: newgidmap returned unknown exit status\n";
+	    }
+	}
 	return 0;
     }
     return 1;
@@ -747,7 +768,11 @@ sub setup {
     }
 
     # setting PATH for chroot, ldconfig, start-stop-daemon...
-    $ENV{"PATH"} = "/usr/sbin:/usr/bin:/sbin:/bin";
+    if (defined $ENV{PATH} && $ENV{PATH} ne "") {
+	$ENV{PATH} = "$ENV{PATH}:/usr/sbin:/usr/bin:/sbin:/bin";
+    } else {
+	$ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin";
+    }
 
     my %pkgs_to_install;
     if (defined $options->{include}) {
@@ -989,6 +1014,11 @@ sub setup {
 
 	    # copy qemu-user-static binary into chroot or setup proot with --qemu
 	    if (defined $options->{qemu}) {
+		# FIXME: check for
+		#  - binfmt_misc kernel module loaded
+		#  - binfmt_misc filesystem in /proc/filesystems
+		#  - binfmt_misc mounted on /proc/sys/fs/binfmt_misc
+		#  - /usr/sbin/update-binfmts --display qemu-arm
 		if ($options->{mode} eq 'proot') {
 		    push @chrootcmd, "--qemu=qemu-$options->{qemu}";
 		} elsif ($options->{mode} eq 'fakechroot') {
@@ -1383,7 +1413,7 @@ sub main() {
     if ($options->{mode} eq 'auto') {
 	if ($EFFECTIVE_USER_ID == 0) {
 	    $options->{mode} = 'root';
-	} elsif (test_unshare()) {
+	} elsif (test_unshare(0)) {
 	    $options->{mode} = 'unshare';
 	} elsif (system('proot --version>/dev/null') == 0) {
 	    $options->{mode} = 'proot';
@@ -1423,26 +1453,7 @@ sub main() {
 	    exec 'fakechroot', 'fakeroot', $PROGRAM_NAME, @ARGVORIG;
 	}
     } elsif ($options->{mode} eq 'unshare') {
-	if (!test_unshare()) {
-	    if ($EFFECTIVE_USER_ID == 0) {
-		print STDERR "I: cannot use unshare mode when executing as root\n";
-	    }
-	    system "newuidmap 2>/dev/null";
-	    if (($? >> 8) != 1) {
-		if (($? >> 8) == 127) {
-		    print STDERR "I: cannot find newuidmap\n";
-		} else {
-		    print STDERR "I: newuidmap returned unknown exit status\n";
-		}
-	    }
-	    system "newgidmap 2>/dev/null";
-	    if (($? >> 8) != 1) {
-		if (($? >> 8) == 127) {
-		    print STDERR "I: cannot find newgidmap\n";
-		} else {
-		    print STDERR "I: newgidmap returned unknown exit status\n";
-		}
-	    }
+	if (!test_unshare(1)) {
 	    my $procfile = '/proc/sys/kernel/unprivileged_userns_clone';
 	    open(my $fh, '<', $procfile) or die "failed to open $procfile: $!";
 	    chomp(my $content = do { local $/; <$fh> });
--- a/make_mirror.sh
+++ b/make_mirror.sh
@@ -13,6 +13,8 @@ if [ "$arch1" = "$arch2" ]; then
 fi
 components=main
 
+: "${HAVE_QEMU:=yes}"
+
 if [ -e "$mirrordir/dists/unstable/Release" ]; then
 	http_code=$(curl --output /dev/null --silent --location --head --time-cond "$mirrordir/dists/unstable/Release" --write-out '%{http_code}' "$mirror/dists/unstable/Release")
 	case "$http_code" in
@@ -155,16 +157,21 @@ done
 # now fill the cache with new content
 mkdir -p "$cachedir"
 
-# We must not use any --dpkgopt here because any dpkg options still
-# leak into the chroot with chrootless mode.
-# We do not use our own package cache here because
-#   - it doesn't (and shouldn't) contain the extra packages
-#   - it doesn't matter if the base system is from a different mirror timestamp
-./mmdebstrap --variant=apt --architectures=amd64,armhf --mode=unshare \
-	--include=linux-image-amd64,systemd-sysv,perl,arch-test,fakechroot,fakeroot,mount,uidmap,proot,qemu-user-static,binfmt-support,qemu-user,dpkg-dev,mini-httpd,libdevel-cover-perl,debootstrap,libfakechroot:armhf,libfakeroot:armhf \
-	unstable debian-unstable.tar
+if [ "$HAVE_QEMU" = "yes" ]; then
+	# We must not use any --dpkgopt here because any dpkg options still
+	# leak into the chroot with chrootless mode.
+	# We do not use our own package cache here because
+	#   - it doesn't (and shouldn't) contain the extra packages
+	#   - it doesn't matter if the base system is from a different mirror timestamp
+	# Write tarfile via redirection because in the Debian package autopkgtest
+	# ./mmdebstrap is a suid binary and we do not want the tarfile being owned
+	# by root.
+	tmpdir="$(mktemp -d)"
+	./mmdebstrap --variant=apt --architectures=amd64,armhf --mode=unshare \
+		--include=linux-image-amd64,systemd-sysv,perl,arch-test,fakechroot,fakeroot,mount,uidmap,proot,qemu-user-static,binfmt-support,qemu-user,dpkg-dev,mini-httpd,libdevel-cover-perl,debootstrap,libfakechroot:armhf,libfakeroot:armhf \
+		unstable > "$tmpdir/debian-unstable.tar"
 
-cat << END > extlinux.conf
+	cat << END > "$tmpdir/extlinux.conf"
 default linux
 timeout 0
 
@@ -173,7 +180,7 @@ kernel /vmlinuz
 append initrd=/initrd.img root=/dev/sda1 rw console=ttyS0,115200
 serial 0 115200
 END
-cat << END > mmdebstrap.service
+	cat << END > "$tmpdir/mmdebstrap.service"
 [Unit]
 Description=mmdebstrap worker script
 
@@ -184,68 +191,77 @@ ExecStart=/worker.sh
 [Install]
 WantedBy=multi-user.target
 END
-# here is something crazy:
-# as we run mmdebstrap, the process ends up being run by different users with
-# different privileges (real or fake). But for being able to collect
-# Devel::Cover data, they must all share a single directory. The only way that
-# I found to make this work is to mount the database directory with a
-# filesystem that doesn't support ownership information at all and a umask that
-# gives read/write access to everybody.
-# https://github.com/pjcj/Devel--Cover/issues/223
-cat << 'END' > worker.sh
+	# here is something crazy:
+	# as we run mmdebstrap, the process ends up being run by different users with
+	# different privileges (real or fake). But for being able to collect
+	# Devel::Cover data, they must all share a single directory. The only way that
+	# I found to make this work is to mount the database directory with a
+	# filesystem that doesn't support ownership information at all and a umask that
+	# gives read/write access to everybody.
+	# https://github.com/pjcj/Devel--Cover/issues/223
+	cat << 'END' > "$tmpdir/worker.sh"
 #!/bin/sh
 mount -t 9p -o trans=virtio,access=any mmdebstrap /mnt
 (
 	cd /mnt;
-	mkdir -p cover_db
-	mount -o loop,umask=000 cover_db.img cover_db
+	if [ -e cover_db.img ]; then
+		mkdir -p cover_db
+		mount -o loop,umask=000 cover_db.img cover_db
+	fi
 	sh ./test.sh
 	ret=$?
-	df -h cover_db
-	umount cover_db
+	if [ -e cover_db.img ]; then
+		df -h cover_db
+		umount cover_db
+	fi
 	echo $ret
 ) > /mnt/result.txt 2>&1
 umount /mnt
 systemctl poweroff
 END
-chmod +x worker.sh
-cat << 'END' > mini-httpd
+	chmod +x "$tmpdir/worker.sh"
+	cat << 'END' > "$tmpdir/mini-httpd"
 START=1
 DAEMON_OPTS="-h 127.0.0.1 -p 80 -u nobody -dd /mnt -i /var/run/mini-httpd.pid -T UTF-8"
 END
-cat << 'END' > hosts
+	cat << 'END' > "$tmpdir/hosts"
 127.0.0.1 localhost
 END
-guestfish -N debian-unstable.img=disk:2G -- \
-	part-disk /dev/sda mbr : \
-	part-set-bootable /dev/sda 1 true : \
-	mkfs ext2 /dev/sda1 : \
-	mount /dev/sda1 / : \
-	tar-in debian-unstable.tar / : \
-	extlinux / : \
-	copy-in extlinux.conf / : \
-	mkdir-p /etc/systemd/system/multi-user.target.wants : \
-	ln-s ../mmdebstrap.service /etc/systemd/system/multi-user.target.wants/mmdebstrap.service : \
-	copy-in mmdebstrap.service /etc/systemd/system/ : \
-	copy-in worker.sh / : \
-	copy-in mini-httpd /etc/default : \
-	copy-in hosts /etc/ :
-rm extlinux.conf worker.sh mini-httpd hosts debian-unstable.tar mmdebstrap.service
-qemu-img convert -O qcow2 debian-unstable.img "$cachedir/debian-unstable.qcow"
-rm debian-unstable.img
+	#libguestfs-test-tool
+	#export LIBGUESTFS_DEBUG=1 LIBGUESTFS_TRACE=1
+	guestfish -N "$tmpdir/debian-unstable.img"=disk:2G -- \
+		part-disk /dev/sda mbr : \
+		part-set-bootable /dev/sda 1 true : \
+		mkfs ext2 /dev/sda1 : \
+		mount /dev/sda1 / : \
+		tar-in "$tmpdir/debian-unstable.tar" / : \
+		extlinux / : \
+		copy-in "$tmpdir/extlinux.conf" / : \
+		mkdir-p /etc/systemd/system/multi-user.target.wants : \
+		ln-s ../mmdebstrap.service /etc/systemd/system/multi-user.target.wants/mmdebstrap.service : \
+		copy-in "$tmpdir/mmdebstrap.service" /etc/systemd/system/ : \
+		copy-in "$tmpdir/worker.sh" / : \
+		copy-in "$tmpdir/mini-httpd" /etc/default : \
+		copy-in "$tmpdir/hosts" /etc/ :
+	rm "$tmpdir/extlinux.conf" "$tmpdir/worker.sh" "$tmpdir/mini-httpd" "$tmpdir/hosts" "$tmpdir/debian-unstable.tar" "$tmpdir/mmdebstrap.service"
+	qemu-img convert -O qcow2 "$tmpdir/debian-unstable.img" "$cachedir/debian-unstable.qcow"
+	rm "$tmpdir/debian-unstable.img"
+	rmdir "$tmpdir"
+fi
 
 mirror="http://127.0.0.1/debian"
 SOURCE_DATE_EPOCH=$(date --date="$(grep-dctrl -s Date -n '' "$mirrordir/dists/unstable/Release")" +%s)
 for dist in stable testing unstable; do
 	for variant in minbase buildd -; do
-		echo running debootstrap --merged-usr --variant=$variant $dist ./debian-$dist-debootstrap "http://localhost:8000/"
+		echo running debootstrap --merged-usr --variant=$variant $dist /tmp/debian-$dist-debootstrap $mirror
 		cat << END > shared/test.sh
 #!/bin/sh
 set -eu
 export LC_ALL=C
 debootstrap --merged-usr --variant=$variant $dist /tmp/debian-$dist-debootstrap $mirror
 tar --sort=name --mtime=@$SOURCE_DATE_EPOCH --clamp-mtime --numeric-owner --one-file-system -C /tmp/debian-$dist-debootstrap -c . > "cache/debian-$dist-$variant.tar"
+rm -r /tmp/debian-$dist-debootstrap
 END
-		./run_qemu.sh
+		./run_qemu.sh SUDO
 	done
 done
--- a/run_qemu.sh
+++ b/run_qemu.sh
@@ -1,17 +1,58 @@
 #!/bin/sh
 
+set -eu
+
 cachedir="./shared/cache"
 
-qemu-img create -f qcow2 -b "$cachedir/debian-unstable.qcow" debian-unstable-overlay.qcow
-qemu-system-x86_64 -enable-kvm -m 512M -nographic \
-	-monitor unix:/tmp/monitor,server,nowait \
-	-serial unix:/tmp/ttyS0,server,nowait \
-	-serial unix:/tmp/ttyS1,server,nowait \
-	-virtfs local,id=mmdebstrap,path="$(pwd)/shared",security_model=none,mount_tag=mmdebstrap \
-	-drive file=debian-unstable-overlay.qcow,cache=unsafe,index=0
-head --lines=-1 shared/result.txt
-if [ "$(tail --lines=1 shared/result.txt)" -ne 0 ]; then
-	echo "test.sh failed"
-	exit 1
+: "${HAVE_QEMU:=yes}"
+
+if [ "$HAVE_QEMU" = "yes" ]; then
+	tmpdir="$(mktemp -d)"
+	# the path to debian-unstable.qcow must be absolute or otherwise qemu will
+	# look for the path relative to debian-unstable-overlay.qcow
+	qemu-img create -f qcow2 -b "$(realpath $cachedir)/debian-unstable.qcow" "$tmpdir/debian-unstable-overlay.qcow"
+	KVM=
+	if [ -e /dev/kvm ]; then
+		KVM="-enable-kvm"
+	fi
+	qemu-system-x86_64 $KVM -m 512M -nographic \
+		-monitor unix:/tmp/monitor,server,nowait \
+		-serial unix:/tmp/ttyS0,server,nowait \
+		-serial unix:/tmp/ttyS1,server,nowait \
+		-virtfs local,id=mmdebstrap,path="$(pwd)/shared",security_model=none,mount_tag=mmdebstrap \
+		-drive file="$tmpdir/debian-unstable-overlay.qcow",cache=unsafe,index=0
+	head --lines=-1 shared/result.txt
+	if [ "$(tail --lines=1 shared/result.txt)" -ne 0 ]; then
+		echo "test.sh failed"
+		exit 1
+	fi
+	rm "$tmpdir/debian-unstable-overlay.qcow" shared/result.txt
+	rmdir "$tmpdir"
+else
+	SUDO=
+	while [ "$#" -gt 0 ]; do
+		key="$1"
+		case "$key" in
+			SUDO)
+				SUDO=sudo
+				;;
+			*)
+				echo "Unknown argument: $key"
+				exit 1
+				;;
+		esac
+		shift
+	done
+
+	# subshell so that we can cd without effecting the rest
+	(
+		set +e
+		cd ./shared;
+		$SUDO sh ./test.sh;
+		echo $?;
+	) 2>&1 | tee shared/result.txt | head --lines=-1
+	if [ "$(tail --lines=1 shared/result.txt)" -ne 0 ]; then
+		echo "test.sh failed"
+		exit 1
+	fi
 fi
-rm debian-unstable-overlay.qcow shared/result.txt
--- a/README.md
+++ b/README.md
@@ -93,14 +93,10 @@ There is no `--second-stage` option.
 Tests
 =====
 
-The script `test.sh` compares the output of mmdebstrap with debootstrap in
-several scenarios. Since debootstrap needs superuser privileges, `test.sh`
-needs `sudo` to run.
-
 The script `coverage.sh` runs mmdebstrap in all kind of scenarios to execute
 all code paths of the script. It verifies its output in each scenario and
-displays the results gathered with Devel::Cover. Due to a whacky setup and to
-also test root mode, this script needs superuser privileges.
+displays the results gathered with Devel::Cover. It also compares the output of
+mmdebstrap with debootstrap in several scenarios.
 
 Bugs
 ====
