Compare commits

..

19 commits

Author SHA1 Message Date
7a057e37dd
release 1.2.3 2022-11-16 14:06:50 +01:00
5bd3da0aef
tests/create-tarball-dry-run: fix MODE->VARIANT 2022-11-16 14:02:58 +01:00
d442f436de
shellcheck everything 2022-11-16 13:59:38 +01:00
889c02419e
update for perltidy 20220613 2022-11-15 14:48:01 +01:00
a156d93314
run_qemu.sh: bump timeout to 40 minutes so that it works on an ARM Cortex-A53 at 1.5 GHz 2022-11-15 14:47:59 +01:00
4ccd799b50
tests/include-deb-file: create a dummy binary package to make sure apt doesn't download the package from the mirror 2022-11-15 14:47:42 +01:00
24c5a45202
make_mirror.sh: switch from extlinux to grub-efi to support arm64 2022-11-15 14:47:38 +01:00
2e8eaeb18b
mmdebstrap-autopkgtest-build-qemu: fix i386 grub target i386-efi -> i386-pc 2022-11-14 14:35:12 +01:00
420080648e
Revert "add another --dpkgopt example"
This reverts commit 40b6155967.

dpkg does not support the {foo,bar,baz} type of glob

Closes: #28
2022-11-14 14:35:12 +01:00
be156e7a14
tests/chrootless: skip if libpam-runtime (<= 1.5.2-5) 2022-11-14 14:35:12 +01:00
ea146ad108
add undocumented --chrooted-*-hook calling pivot_root in unshare mode 2022-11-14 14:35:12 +01:00
449fb248e2
Instead of mounting and unmounting for each run_chroot() call, do it once before the extract hook and unmount after the customize hooks 2022-11-14 14:31:06 +01:00
eb54f6a23a
Instead of re-execing mmdebstrap under /bin/sh, use Text::ParseWords::shellwords
- saves a few PIDs
 - saves a bit of time because useless exec and fork is avoided
 - allows to run in pivoted chroots without mmdebstrap
2022-11-14 14:31:05 +01:00
d2238c891b
coverage.py: add --skip option 2022-11-14 14:31:05 +01:00
bf33a614c3
add mini-mmdebstrap in shell to the man page 2022-11-14 14:31:05 +01:00
d15be6abbf
tests/check-against-debootstrap-dist: add more restrictions for remaining hacks 2022-11-14 14:31:05 +01:00
67902e06e9
tests/dev-ptmx: needs adduser inside the chroot 2022-11-07 16:11:47 +01:00
d9ca7c21ff
make failure to remove /dev/ptmx a warning and not an error 2022-11-07 16:10:55 +01:00
d29f9195d7
coverage.txt: add more requirements found by running tests on salsa ci and debci 2022-11-07 16:10:13 +01:00
33 changed files with 571 additions and 373 deletions

View file

@ -1,3 +1,9 @@
1.2.3 (2022-11-16)
------------------
- use Text::ParseWords::shellwords instead of spawning a new shell
- mount and unmount once, instead for each run_chroot() call
1.2.2 (2022-10-27) 1.2.2 (2022-10-27)
------------------ ------------------

View file

@ -172,6 +172,9 @@ def main():
metavar="format", metavar="format",
help=f"only run tests with this format (Default = {default_format})", help=f"only run tests with this format (Default = {default_format})",
) )
parser.add_argument(
"--skip", metavar="test", action="append", help="skip this test"
)
args = parser.parse_args() args = parser.parse_args()
# copy over files from git or as distributed # copy over files from git or as distributed
@ -295,6 +298,21 @@ def main():
line = line.replace("{{ FORMAT }}", fmt) line = line.replace("{{ FORMAT }}", fmt)
line = line.replace("{{ HOSTARCH }}", hostarch) line = line.replace("{{ HOSTARCH }}", hostarch)
fout.write(line) fout.write(line)
# ignore:
# SC2016 Expressions don't expand in single quotes, use double quotes for that.
# SC2050 This expression is constant. Did you forget the $ on a variable?
# SC2194 This word is constant. Did you forget the $ on a variable?
shellcheck = subprocess.run(
[
"shellcheck",
"--exclude=SC2050,SC2194,SC2016",
"-f",
"gcc",
"shared/test.sh",
],
check=False,
stdout=subprocess.PIPE,
).stdout.decode()
argv = None argv = None
match test: match test:
case "qemu": case "qemu":
@ -310,6 +328,9 @@ def main():
print(f"skipped because of {reason}", file=sys.stderr) print(f"skipped because of {reason}", file=sys.stderr)
continue continue
print(separator, file=sys.stderr) print(separator, file=sys.stderr)
if args.skip and name in args.skip:
print(f"skipping because of --skip={name}", file=sys.stderr)
continue
if args.dist and args.dist != dist: if args.dist and args.dist != dist:
print(f"skipping because of --dist={args.dist}", file=sys.stderr) print(f"skipping because of --dist={args.dist}", file=sys.stderr)
continue continue
@ -330,7 +351,9 @@ def main():
proc.wait() proc.wait()
break break
print(separator, file=sys.stderr) print(separator, file=sys.stderr)
if proc.returncode != 0: if proc.returncode != 0 or shellcheck != "":
if shellcheck != "":
print(shellcheck)
failed.append( failed.append(
format_failed( format_failed(
i + 1, len(tests), name, dist, mode, variant, fmt, config_dict i + 1, len(tests), name, dist, mode, variant, fmt, config_dict

View file

@ -14,7 +14,7 @@ if [ -e ./mmdebstrap ]; then
fi fi
rm "$TMPFILE" rm "$TMPFILE"
if [ $(sed -e '/^__END__$/,$d' ./mmdebstrap | wc --max-line-length) -gt 79 ]; then if [ "$(sed -e '/^__END__$/,$d' ./mmdebstrap | wc --max-line-length)" -gt 79 ]; then
echo "exceeded maximum line length of 79 characters" >&2 echo "exceeded maximum line length of 79 characters" >&2
exit 1 exit 1
fi fi
@ -25,6 +25,8 @@ fi
[ -e ./tarfilter ] && black --check ./tarfilter [ -e ./tarfilter ] && black --check ./tarfilter
[ -e ./coverage.py ] && black --check ./coverage.py [ -e ./coverage.py ] && black --check ./coverage.py
shellcheck --exclude=SC2016 coverage.sh make_mirror.sh run_null.sh run_qemu.sh gpgvnoexpkeysig hooks/*/*.sh
mirrordir="./shared/cache/debian" mirrordir="./shared/cache/debian"
if [ ! -e "$mirrordir" ]; then if [ ! -e "$mirrordir" ]; then
@ -75,11 +77,6 @@ export LC_ALL=C.UTF-8
: "${HAVE_PROOT:=yes}" : "${HAVE_PROOT:=yes}"
: "${HAVE_BINFMT:=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 # by default, use the mmdebstrap executable in the current directory together
# with perl Devel::Cover but allow to overwrite this # with perl Devel::Cover but allow to overwrite this
: "${CMD:=perl -MDevel::Cover=-silent,-nogcov ./mmdebstrap}" : "${CMD:=perl -MDevel::Cover=-silent,-nogcov ./mmdebstrap}"
@ -103,14 +100,14 @@ cover -delete cover_db >&2
END END
if [ "$HAVE_QEMU" = "yes" ]; then if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh ./run_qemu.sh
elif [ "$mode" = "root" ]; then elif [ "$HAVE_UNSHARE" != "yes" ]; then
./run_null.sh SUDO ./run_null.sh SUDO
else else
./run_null.sh ./run_null.sh
fi fi
echo echo
echo open file://$(pwd)/shared/report/coverage.html in a browser echo "open file://$(pwd)/shared/report/coverage.html in a browser"
echo echo
fi fi

View file

@ -291,12 +291,14 @@ Variants: custom
Test: chrootless Test: chrootless
Variants: essential Variants: essential
Modes: chrootless Modes: chrootless
Needs-Root: true
Skip-If: Skip-If:
dist in ["oldstable", "stable"] dist in ["oldstable", "stable"]
Test: chrootless-fakeroot Test: chrootless-fakeroot
Variants: essential Variants: essential
Modes: chrootless Modes: chrootless
Needs-QEMU: true
Skip-If: Skip-If:
dist in ["oldstable", "stable"] dist in ["oldstable", "stable"]
@ -344,3 +346,7 @@ Test: error-if-stdout-is-tty
Test: variant-custom-timeout Test: variant-custom-timeout
Test: include-deb-file Test: include-deb-file
Test: pivot_root
Modes: root unshare
Needs-QEMU: true

View file

@ -13,7 +13,7 @@ if [ -e "$rootdir/var/lib/dpkg/arch" ]; then
else else
chrootarch=$(dpkg --print-architecture) chrootarch=$(dpkg --print-architecture)
fi fi
libdir="/usr/lib/$(dpkg-architecture -a $chrootarch -q DEB_HOST_MULTIARCH)" libdir="/usr/lib/$(dpkg-architecture -a "$chrootarch" -q DEB_HOST_MULTIARCH)"
# if eatmydata was actually installed properly, then we are not removing # if eatmydata was actually installed properly, then we are not removing
# anything here # anything here

View file

@ -14,8 +14,10 @@ else
chrootarch=$(dpkg --print-architecture) chrootarch=$(dpkg --print-architecture)
fi fi
eval $(apt-config shell trusted Dir::Etc::trusted/f) trusted=
eval $(apt-config shell trustedparts Dir::Etc::trustedparts/d) eval "$(apt-config shell trusted Dir::Etc::trusted/f)"
trustedparts=
eval "$(apt-config shell trustedparts Dir::Etc::trustedparts/d)"
tmpfile=$(mktemp --tmpdir="$rootdir/tmp") tmpfile=$(mktemp --tmpdir="$rootdir/tmp")
cat << END > "$tmpfile" cat << END > "$tmpfile"
Apt::Architecture "$chrootarch"; Apt::Architecture "$chrootarch";
@ -30,7 +32,7 @@ END
tmpdir=$(mktemp --directory --tmpdir="$rootdir/tmp") tmpdir=$(mktemp --directory --tmpdir="$rootdir/tmp")
env --chdir="$tmpdir" APT_CONFIG="$tmpfile" apt-get download --print-uris eatmydata libeatmydata1 \ env --chdir="$tmpdir" APT_CONFIG="$tmpfile" apt-get download --print-uris eatmydata libeatmydata1 \
| sed -ne "s/^'\([^']\+\)'\s\+\(\S\+\)\s\+\([0-9]\+\)\s\+\(SHA256:[a-f0-9]\+\)$/\1 \2 \3 \4/p" \ | sed -ne "s/^'\([^']\+\)'\s\+\(\S\+\)\s\+\([0-9]\+\)\s\+\(SHA256:[a-f0-9]\+\)$/\1 \2 \3 \4/p" \
| while read uri fname size hash; do | while read -r uri fname size hash; do
echo "processing $fname" >&2 echo "processing $fname" >&2
if [ -e "$tmpdir/$fname" ]; then if [ -e "$tmpdir/$fname" ]; then
echo "$tmpdir/$fname already exists" >&2 echo "$tmpdir/$fname already exists" >&2
@ -45,7 +47,7 @@ env --chdir="$tmpdir" APT_CONFIG="$tmpfile" apt-get download --print-uris eatmyd
| tar --directory="$rootdir/usr/bin" --strip-components=3 --extract --verbose ./usr/bin/eatmydata | tar --directory="$rootdir/usr/bin" --strip-components=3 --extract --verbose ./usr/bin/eatmydata
;; ;;
libeatmydata1_*_$chrootarch.deb) libeatmydata1_*_$chrootarch.deb)
libdir="/usr/lib/$(dpkg-architecture -a $chrootarch -q DEB_HOST_MULTIARCH)" libdir="/usr/lib/$(dpkg-architecture -a "$chrootarch" -q DEB_HOST_MULTIARCH)"
mkdir -p "$rootdir$libdir" mkdir -p "$rootdir$libdir"
dpkg-deb --fsys-tarfile "$tmpdir/$fname" \ dpkg-deb --fsys-tarfile "$tmpdir/$fname" \
| tar --directory="$rootdir$libdir" --strip-components=4 --extract --verbose --wildcards ".$libdir/libeatmydata.so*" | tar --directory="$rootdir$libdir" --strip-components=4 --extract --verbose --wildcards ".$libdir/libeatmydata.so*"

View file

@ -1,4 +1,6 @@
#!/bin/sh #!/bin/sh
#
# shellcheck disable=SC2086
set -eu set -eu
@ -21,17 +23,17 @@ case $MMDEBSTRAP_MODE in
echo "removing the following directories:" >&2 ;; echo "removing the following directories:" >&2 ;;
esac esac
cat "$rootdir/run/mmdebstrap/file-mirror-automount" \ < "$rootdir/run/mmdebstrap/file-mirror-automount" \
| xargs $xargsopts echo " $rootdir/{}" xargs $xargsopts echo " $rootdir/{}"
case $MMDEBSTRAP_MODE in case $MMDEBSTRAP_MODE in
root|unshare) root|unshare)
cat "$rootdir/run/mmdebstrap/file-mirror-automount" \ < "$rootdir/run/mmdebstrap/file-mirror-automount" \
| xargs $xargsopts umount "$rootdir/{}" xargs $xargsopts umount "$rootdir/{}"
;; ;;
*) *)
cat "$rootdir/run/mmdebstrap/file-mirror-automount" \ < "$rootdir/run/mmdebstrap/file-mirror-automount" \
| xargs $xargsopts rm -r "$rootdir/{}" xargs $xargsopts rm -r "$rootdir/{}"
;; ;;
esac esac

View file

@ -9,10 +9,10 @@ fi
rootdir="$1" rootdir="$1"
# process all configured apt repositories # process all configured apt repositories
env APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get indextargets --no-release-info --format '$(REPO_URI)' \ env APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-get indextargets --no-release-info --format '$(REPO_URI)' \
| sed -ne 's/^file:\/\+//p' \ | sed -ne 's/^file:\/\+//p' \
| sort -u \ | sort -u \
| while read path; do | while read -r path; do
mkdir -p "$rootdir/run/mmdebstrap" mkdir -p "$rootdir/run/mmdebstrap"
case $MMDEBSTRAP_MODE in case $MMDEBSTRAP_MODE in
root|unshare) root|unshare)
@ -22,8 +22,8 @@ env APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get indextargets --no-release-info --f
;; ;;
*) *)
echo "copying /$path into the chroot" >&2 echo "copying /$path into the chroot" >&2
mkdir -p "$rootdir/$(dirname $path)" mkdir -p "$rootdir/$(dirname "$path")"
cp -av "/$path" "$rootdir/$(dirname $path)" cp -av "/$path" "$rootdir/$(dirname "$path")"
;; ;;
esac esac
printf '/%s\0' "$path" >> "$rootdir/run/mmdebstrap/file-mirror-automount" printf '/%s\0' "$path" >> "$rootdir/run/mmdebstrap/file-mirror-automount"

View file

@ -51,8 +51,10 @@ ARCH=$(dpkg --print-architecture)
eval "$(APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-config shell ARCH Apt::Architecture)" eval "$(APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-config shell ARCH Apt::Architecture)"
if [ -e /usr/share/debootstrap/functions ]; then if [ -e /usr/share/debootstrap/functions ]; then
# shellcheck disable=SC1091
. /usr/share/debootstrap/functions . /usr/share/debootstrap/functions
doing_variant () { [ $1 != "buildd" ]; } doing_variant () { [ "$1" != "buildd" ]; }
# shellcheck disable=SC2034
MERGED_USR="yes" MERGED_USR="yes"
setup_merged_usr setup_merged_usr
else else

View file

@ -58,11 +58,9 @@ deletecache() {
;; ;;
esac esac
done done
if [ -e $dir/debian-*.qcow ]; then for f in "$dir/debian-"*.qcow; do
rm --one-file-system "$dir"/debian-*.qcow rm --one-file-system "$f"
else done
echo "does not exist: $dir/debian-*.qcow" >&2
fi
if [ -e "$dir/debian/pool/main" ]; then if [ -e "$dir/debian/pool/main" ]; then
rm --one-file-system --recursive "$dir/debian/pool/main" rm --one-file-system --recursive "$dir/debian/pool/main"
else else
@ -103,7 +101,7 @@ get_oldaptnames() {
xz -dc "$1/$2" \ xz -dc "$1/$2" \
| grep-dctrl --no-field-names --show-field=Package,Version,Architecture,Filename '' \ | grep-dctrl --no-field-names --show-field=Package,Version,Architecture,Filename '' \
| paste -sd " \n" \ | paste -sd " \n" \
| while read name ver arch fname; do | while read -r name ver arch fname; do
if [ ! -e "$1/$fname" ]; then if [ ! -e "$1/$fname" ]; then
continue continue
fi fi
@ -131,7 +129,7 @@ get_newaptnames() {
xz -dc "$1/$2" \ xz -dc "$1/$2" \
| grep-dctrl --no-field-names --show-field=Package,Version,Architecture,Filename,SHA256 '' \ | grep-dctrl --no-field-names --show-field=Package,Version,Architecture,Filename,SHA256 '' \
| paste -sd " \n" \ | paste -sd " \n" \
| while read name ver arch fname hash; do | while read -r name ver arch fname hash; do
# sanity check for the hash because sometimes the # sanity check for the hash because sometimes the
# archive switches the hash algorithm # archive switches the hash algorithm
if [ "${#hash}" -ne 64 ]; then if [ "${#hash}" -ne 64 ]; then
@ -150,7 +148,7 @@ get_newaptnames() {
# since we move hardlinks around, the same hardlink might've been # since we move hardlinks around, the same hardlink might've been
# moved already into the same place by another distribution. # moved already into the same place by another distribution.
# mv(1) refuses to copy A to B if both are hardlinks of each other. # mv(1) refuses to copy A to B if both are hardlinks of each other.
if [ "$aptname" -ef "$1/$fname" ]; then if [ -e "$aptname" ] && [ -e "$1/$fname" ] && [ "$(stat -c "%d %i" "$aptname")" = "$(stat -c "%d %i" "$1/$fname")" ]; then
# both files are already the same so we just need to # both files are already the same so we just need to
# delete the source # delete the source
rm "$aptname" rm "$aptname"
@ -232,7 +230,7 @@ Acquire::https::Dl-Limit "1000";
Acquire::Retries "5"; Acquire::Retries "5";
END END
> "$rootdir/var/lib/dpkg/status" : > "$rootdir/var/lib/dpkg/status"
APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get update APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get update
@ -264,7 +262,7 @@ END
--or --field=Priority important --or --field=Priority standard \ --or --field=Priority important --or --field=Priority standard \
\)) \))
pkgs="$(echo $pkgs) build-essential busybox gpg eatmydata" pkgs="$pkgs build-essential busybox gpg eatmydata"
# we need usr-is-merged to simulate debootstrap behaviour for all dists # we need usr-is-merged to simulate debootstrap behaviour for all dists
# starting from Debian 12 (Bullseye) # starting from Debian 12 (Bullseye)
@ -273,6 +271,7 @@ END
*) pkgs="$pkgs usr-is-merged usrmerge" ;; *) pkgs="$pkgs usr-is-merged usrmerge" ;;
esac esac
# shellcheck disable=SC2086
APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get --yes install $pkgs APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get --yes install $pkgs
# to be able to also test gpg verification, we need to create a mirror # to be able to also test gpg verification, we need to create a mirror
@ -336,7 +335,7 @@ END
# new one anymore # new one anymore
comm -23 "$rootdir/oldaptnames" "$rootdir/newaptnames" | xargs --delimiter="\n" --no-run-if-empty rm comm -23 "$rootdir/oldaptnames" "$rootdir/newaptnames" | xargs --delimiter="\n" --no-run-if-empty rm
# now the apt cache should be empty # now the apt cache should be empty
if [ ! -z "$(ls -1qA "$rootdir/var/cache/apt/archives/")" ]; then if [ -n "$(ls -1qA "$rootdir/var/cache/apt/archives/")" ]; then
echo "$rootdir/var/cache/apt/archives not empty:" echo "$rootdir/var/cache/apt/archives not empty:"
ls -la "$rootdir/var/cache/apt/archives/" ls -la "$rootdir/var/cache/apt/archives/"
exit 1 exit 1
@ -420,7 +419,7 @@ fi
for nativearch in $arches; do for nativearch in $arches; do
for dist in oldstable stable testing unstable; do for dist in oldstable stable testing unstable; do
# non-host architectures are only downloaded for $DEFAULT_DIST # non-host architectures are only downloaded for $DEFAULT_DIST
if [ $nativearch != $HOSTARCH ] && [ $DEFAULT_DIST != $dist ]; then if [ "$nativearch" != "$HOSTARCH" ] && [ "$DEFAULT_DIST" != "$dist" ]; then
continue continue
fi fi
# we need a first pass without updates and security patches # we need a first pass without updates and security patches
@ -465,12 +464,10 @@ cleanuptmpdir() {
if [ ! -e "$tmpdir" ]; then if [ ! -e "$tmpdir" ]; then
return return
fi fi
for f in "$tmpdir/extlinux.conf" \ for f in "$tmpdir/worker.sh" \
"$tmpdir/worker.sh" \
"$tmpdir/mini-httpd" "$tmpdir/hosts" \ "$tmpdir/mini-httpd" "$tmpdir/hosts" \
"$tmpdir/debian-chroot.tar" \ "$tmpdir/debian-chroot.tar" \
"$tmpdir/mmdebstrap.service" \ "$tmpdir/mmdebstrap.service"; do
"$tmpdir/debian-$DEFAULT_DIST.img"; do
if [ ! -e "$f" ]; then if [ ! -e "$f" ]; then
echo "does not exist: $f" >&2 echo "does not exist: $f" >&2
continue continue
@ -480,16 +477,17 @@ cleanuptmpdir() {
rmdir "$tmpdir" rmdir "$tmpdir"
} }
export SOURCE_DATE_EPOCH=$(date --date="$(grep-dctrl -s Date -n '' "$newmirrordir/dists/$DEFAULT_DIST/Release")" +%s) SOURCE_DATE_EPOCH="$(date --date="$(grep-dctrl -s Date -n '' "$newmirrordir/dists/$DEFAULT_DIST/Release")" +%s)"
export SOURCE_DATE_EPOCH
if [ "$HAVE_QEMU" = "yes" ]; then if [ "$HAVE_QEMU" = "yes" ]; then
case "$HOSTARCH" in case "$HOSTARCH" in
amd64|i386) amd64|i386|arm64)
# okay # okay
;; ;;
*) *)
echo "qemu support is only available on amd64 and i386" >&2 echo "qemu support is only available on amd64, i386 and arm64" >&2
echo "because syslinux is only available on those arches" >&2 echo "because grub is only available on those arches" >&2
exit 1 exit 1
;; ;;
esac esac
@ -503,7 +501,7 @@ if [ "$HAVE_QEMU" = "yes" ]; then
tmpdir="$(mktemp -d)" tmpdir="$(mktemp -d)"
trap "cleanuptmpdir; cleanup_newcachedir" EXIT INT TERM trap "cleanuptmpdir; cleanup_newcachedir" EXIT INT TERM
pkgs=perl-doc,systemd-sysv,perl,arch-test,fakechroot,fakeroot,mount,uidmap,qemu-user-static,binfmt-support,qemu-user,dpkg-dev,mini-httpd,libdevel-cover-perl,libtemplate-perl,debootstrap,procps,apt-cudf,aspcud,python3,libcap2-bin,gpg,debootstrap,distro-info-data,iproute2,ubuntu-keyring,apt-utils pkgs=perl-doc,systemd-sysv,perl,arch-test,fakechroot,fakeroot,mount,uidmap,qemu-user-static,binfmt-support,qemu-user,dpkg-dev,mini-httpd,libdevel-cover-perl,libtemplate-perl,debootstrap,procps,apt-cudf,aspcud,python3,libcap2-bin,gpg,debootstrap,distro-info-data,iproute2,ubuntu-keyring,apt-utils,grub-efi
if [ "$DEFAULT_DIST" != "oldstable" ]; then if [ "$DEFAULT_DIST" != "oldstable" ]; then
pkgs="$pkgs,squashfs-tools-ng,genext2fs" pkgs="$pkgs,squashfs-tools-ng,genext2fs"
fi fi
@ -534,21 +532,12 @@ if [ "$HAVE_QEMU" = "yes" ]; then
else else
arches=$HOSTARCH arches=$HOSTARCH
fi fi
$CMD --variant=apt --architectures=$arches --include="$pkgs" \ $CMD --variant=apt --architectures="$arches" --include="$pkgs" \
--aptopt='Acquire::http::Dl-Limit "1000"' \ --aptopt='Acquire::http::Dl-Limit "1000"' \
--aptopt='Acquire::https::Dl-Limit "1000"' \ --aptopt='Acquire::https::Dl-Limit "1000"' \
--aptopt='Acquire::Retries "5"' \ --aptopt='Acquire::Retries "5"' \
$DEFAULT_DIST - "$mirror" > "$tmpdir/debian-chroot.tar" $DEFAULT_DIST - "$mirror" > "$tmpdir/debian-chroot.tar"
cat << END > "$tmpdir/extlinux.conf"
default linux
timeout 0
label linux
kernel /vmlinuz
append initrd=/initrd.img root=/dev/vda1 rw console=tty0 console=ttyS0,115200n8
serial 0 115200
END
cat << END > "$tmpdir/mmdebstrap.service" cat << END > "$tmpdir/mmdebstrap.service"
[Unit] [Unit]
Description=mmdebstrap worker script Description=mmdebstrap worker script
@ -631,13 +620,30 @@ END
if [ -z ${DISK_SIZE+x} ]; then if [ -z ${DISK_SIZE+x} ]; then
DISK_SIZE=10G DISK_SIZE=10G
fi fi
guestfish -N "$tmpdir/debian-$DEFAULT_DIST.img"=disk:$DISK_SIZE -- \ case "$HOSTARCH" in
part-disk /dev/sda mbr : \ amd64) GRUB_TARGET=x86_64-efi;;
mkfs ext2 /dev/sda1 : \ i386) GRUB_TARGET=i386-efi;;
mount /dev/sda1 / : \ arm64) GRUB_TARGET=arm64-efi;;
tar-in "$tmpdir/debian-chroot.tar" / : \ esac
case "$HOSTARCH" in
arm64) SERIAL="loglevel=3 console=tty0 console=ttyAMA0,115200n8" ;;
*) SERIAL="loglevel=3 console=tty0 console=ttyS0,115200n8" ;;
esac
guestfish -- \
disk-create "$newcachedir/debian-$DEFAULT_DIST.qcow" qcow2 "$DISK_SIZE" : \
add-drive "$newcachedir/debian-$DEFAULT_DIST.qcow" format:qcow2 : \
launch : \
part-init /dev/sda gpt : \
part-add /dev/sda primary 8192 262144 : \
part-add /dev/sda primary 262145 -34 : \
part-set-gpt-type /dev/sda 1 C12A7328-F81F-11D2-BA4B-00A0C93EC93B : \
mkfs ext2 /dev/sda2 : \
mount /dev/sda2 / : \
tar-in "$tmpdir/debian-chroot.tar" / xattrs:true : \
mkdir-p /boot/efi : \
mkfs vfat /dev/sda1 : \
mount /dev/sda1 /boot/efi : \
command /sbin/ldconfig : \ command /sbin/ldconfig : \
copy-in "$tmpdir/extlinux.conf" / : \
mkdir-p /etc/systemd/system/multi-user.target.wants : \ mkdir-p /etc/systemd/system/multi-user.target.wants : \
ln-s ../mmdebstrap.service /etc/systemd/system/multi-user.target.wants/mmdebstrap.service : \ ln-s ../mmdebstrap.service /etc/systemd/system/multi-user.target.wants/mmdebstrap.service : \
copy-in "$tmpdir/mmdebstrap.service" /etc/systemd/system/ : \ copy-in "$tmpdir/mmdebstrap.service" /etc/systemd/system/ : \
@ -645,15 +651,16 @@ END
copy-in "$tmpdir/mini-httpd" /etc/default : \ copy-in "$tmpdir/mini-httpd" /etc/default : \
copy-in "$tmpdir/hosts" /etc/ : \ copy-in "$tmpdir/hosts" /etc/ : \
touch /mmdebstrap-testenv : \ touch /mmdebstrap-testenv : \
upload /usr/lib/EXTLINUX/mbr.bin /mbr.bin : \ command "sh -c 'echo UUID=\$(blkid -c /dev/null -o value -s UUID /dev/sda2) / ext4 errors=remount-ro 0 1 > /etc/fstab'" : \
copy-file-to-device /mbr.bin /dev/sda size:440 : \ command "sh -c 'echo UUID=\$(blkid -c /dev/null -o value -s UUID /dev/sda1) /boot/efi vfat errors=remount-ro 0 2 >> /etc/fstab'" : \
rm /mbr.bin : \ command "sed -i 's/^GRUB_CMDLINE_LINUX_DEFAULT=/GRUB_CMDLINE_LINUX_DEFAULT=\"biosdevname=0 net.ifnames=0 consoleblank=0 rw $SERIAL\"/' /etc/default/grub" : \
extlinux / : \ command "update-initramfs -u" : \
command "grub-mkconfig -o /boot/grub/grub.cfg" : \
command "grub-install /dev/sda --target=$GRUB_TARGET --no-nvram --force-extra-removable --no-floppy --modules=part_gpt --grub-mkdevicemap=/boot/grub/device.map" : \
sync : \ sync : \
umount /boot/efi : \
umount / : \ umount / : \
part-set-bootable /dev/sda 1 true : \
shutdown shutdown
qemu-img convert -O qcow2 "$tmpdir/debian-$DEFAULT_DIST.img" "$newcachedir/debian-$DEFAULT_DIST.qcow"
cleanuptmpdir cleanuptmpdir
trap "cleanup_newcachedir" EXIT INT TERM trap "cleanup_newcachedir" EXIT INT TERM
fi fi

View file

@ -23,7 +23,7 @@
use strict; use strict;
use warnings; use warnings;
our $VERSION = '1.2.2'; our $VERSION = '1.2.3';
use English; use English;
use Getopt::Long; use Getopt::Long;
@ -45,6 +45,7 @@ use Term::ANSIColor;
use Socket; use Socket;
use Time::HiRes; use Time::HiRes;
use Math::BigInt; use Math::BigInt;
use Text::ParseWords;
use version; use version;
## no critic (InputOutput::RequireBriefOpen) ## no critic (InputOutput::RequireBriefOpen)
@ -61,12 +62,17 @@ use version;
*_LINUX_CAPABILITY_VERSION_3 = \0x20080522; *_LINUX_CAPABILITY_VERSION_3 = \0x20080522;
*CAP_SYS_ADMIN = \21; *CAP_SYS_ADMIN = \21;
*PR_CAPBSET_READ = \23; *PR_CAPBSET_READ = \23;
# from sys/mount.h
*MS_BIND = \0x1000;
*MS_REC = \0x4000;
*MNT_DETACH = \2;
our ( our (
$CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWNS, $CLONE_NEWUTS,
$CLONE_NEWIPC, $CLONE_NEWUSER, $CLONE_NEWIPC, $CLONE_NEWUSER,
$CLONE_NEWPID, $CLONE_NEWNET, $CLONE_NEWPID, $CLONE_NEWNET,
$_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN, $_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN,
$PR_CAPBSET_READ $PR_CAPBSET_READ, $MS_BIND,
$MS_REC, $MNT_DETACH
); );
#<<< #<<<
@ -1108,28 +1114,11 @@ sub run_apt_download_progress {
return @listofdebs; return @listofdebs;
} }
sub run_chroot { sub setup_mounts {
my $cmd = shift;
my $options = shift; my $options = shift;
my @cleanup_tasks = (); my @cleanup_tasks = ();
my $cleanup = sub {
my $signal = $_[0];
while (my $task = pop @cleanup_tasks) {
$task->();
}
if ($signal) {
warning "pid $PID cought signal: $signal";
exit 1;
}
};
local $SIG{INT} = $cleanup;
local $SIG{HUP} = $cleanup;
local $SIG{PIPE} = $cleanup;
local $SIG{TERM} = $cleanup;
eval { eval {
if (any { $_ eq $options->{mode} } ('root', 'unshare')) { if (any { $_ eq $options->{mode} } ('root', 'unshare')) {
# if more than essential should be installed, make the system look # if more than essential should be installed, make the system look
@ -1164,10 +1153,8 @@ sub run_chroot {
} }
} elsif ($type == 3 or $type == 4) { } elsif ($type == 3 or $type == 4) {
# character/block special # character/block special
if ( if (any { $_ =~ '^chroot/mount(?:/dev)?$' }
any { $_ =~ '^chroot/mount(?:/dev)?$' } @{ $options->{skip} }) {
@{ $options->{skip} }
) {
info "skipping chroot/mount/dev as requested"; info "skipping chroot/mount/dev as requested";
} elsif (!$options->{canmount}) { } elsif (!$options->{canmount}) {
warning "skipping bind-mounting ./dev/$fname"; warning "skipping bind-mounting ./dev/$fname";
@ -1189,7 +1176,7 @@ sub run_chroot {
or error "cannot create /dev/pts/ptmx symlink"; or error "cannot create /dev/pts/ptmx symlink";
push @cleanup_tasks, sub { push @cleanup_tasks, sub {
unlink "$options->{root}/dev/ptmx" unlink "$options->{root}/dev/ptmx"
or error "unlink /dev/ptmx"; or warning "unlink /dev/ptmx";
}; };
next; next;
} }
@ -1226,10 +1213,8 @@ sub run_chroot {
} }
} elsif ($type == 5) { } elsif ($type == 5) {
# directory # directory
if ( if (any { $_ =~ '^chroot/mount(?:/dev)?$' }
any { $_ =~ '^chroot/mount(?:/dev)?$' } @{ $options->{skip} }) {
@{ $options->{skip} }
) {
info "skipping chroot/mount/dev as requested"; info "skipping chroot/mount/dev as requested";
} elsif (!$options->{canmount}) { } elsif (!$options->{canmount}) {
warning "skipping bind-mounting ./dev/$fname"; warning "skipping bind-mounting ./dev/$fname";
@ -1495,6 +1480,12 @@ sub run_chroot {
if (any { $_ eq 'chroot/policy-rc.d' } @{ $options->{skip} }) { if (any { $_ eq 'chroot/policy-rc.d' } @{ $options->{skip} }) {
info "skipping chroot/policy-rc.d as requested"; info "skipping chroot/policy-rc.d as requested";
} else { } else {
push @cleanup_tasks, sub {
if (-f "$options->{root}/usr/sbin/policy-rc.d") {
unlink "$options->{root}/usr/sbin/policy-rc.d"
or error "cannot unlink policy-rc.d: $!";
}
};
if (-d "$options->{root}/usr/sbin/") { if (-d "$options->{root}/usr/sbin/") {
open my $fh, '>', "$options->{root}/usr/sbin/policy-rc.d" open my $fh, '>', "$options->{root}/usr/sbin/policy-rc.d"
or error "cannot open policy-rc.d: $!"; or error "cannot open policy-rc.d: $!";
@ -1510,6 +1501,14 @@ sub run_chroot {
if (any { $_ eq 'chroot/start-stop-daemon' } @{ $options->{skip} }) { if (any { $_ eq 'chroot/start-stop-daemon' } @{ $options->{skip} }) {
info "skipping chroot/start-stop-daemon as requested"; info "skipping chroot/start-stop-daemon as requested";
} else { } else {
push @cleanup_tasks, sub {
if (-e "$options->{root}/sbin/start-stop-daemon.REAL") {
move(
"$options->{root}/sbin/start-stop-daemon.REAL",
"$options->{root}/sbin/start-stop-daemon"
) or error "cannot move start-stop-daemon: $!";
}
};
if (-f "$options->{root}/sbin/start-stop-daemon") { if (-f "$options->{root}/sbin/start-stop-daemon") {
if (-e "$options->{root}/sbin/start-stop-daemon.REAL") { if (-e "$options->{root}/sbin/start-stop-daemon.REAL") {
error error
@ -1531,40 +1530,12 @@ sub run_chroot {
or error "cannot chmod start-stop-daemon: $!"; or error "cannot chmod start-stop-daemon: $!";
} }
} }
&{$cmd}();
# cleanup
if (any { $_ eq 'chroot/start-stop-daemon' } @{ $options->{skip} }) {
info "skipping chroot/start-stop-daemon as requested";
} else {
if (-e "$options->{root}/sbin/start-stop-daemon.REAL") {
move(
"$options->{root}/sbin/start-stop-daemon.REAL",
"$options->{root}/sbin/start-stop-daemon"
) or error "cannot move start-stop-daemon: $!";
}
}
if (any { $_ eq 'chroot/policy-rc.d' } @{ $options->{skip} }) {
info "skipping chroot/policy-rc.d as requested";
} else {
if (-f "$options->{root}/usr/sbin/policy-rc.d") {
unlink "$options->{root}/usr/sbin/policy-rc.d"
or error "cannot unlink policy-rc.d: $!";
}
}
}; };
my $error = $@; if ($@) {
error "setup_mounts failed: $@";
# we use the cleanup function to do the unmounting
$cleanup->(0);
if ($error) {
error "run_chroot failed: $error";
} }
return; return @cleanup_tasks;
} }
sub run_hooks { sub run_hooks {
@ -1629,8 +1600,64 @@ sub run_hooks {
("MMDEBSTRAP_INCLUDE=" . (join ",", @escaped_includes)); ("MMDEBSTRAP_INCLUDE=" . (join ",", @escaped_includes));
} }
my $runner = sub { # Unset the close-on-exec flag, so that the file descriptor does not
# get closed when we exec
my $flags = fcntl($options->{hooksock}, F_GETFD, 0)
or error "fcntl F_GETFD: $!";
fcntl($options->{hooksock}, F_SETFD, $flags & ~FD_CLOEXEC)
or error "fcntl F_SETFD: $!";
{
foreach my $script (@{ $options->{"${name}_hook"} }) { foreach my $script (@{ $options->{"${name}_hook"} }) {
my $type = $script->[0];
$script = $script->[1];
if ($type eq "pivoted") {
info "running --chrooted-$name-hook in shell: sh -c "
. "'$script'";
my $pid = fork() // error "fork() failed: $!";
if ($pid == 0) {
# child
my @cmdprefix = ();
if ($options->{mode} eq 'fakechroot') {
# we are calling the chroot executable instead of
# chrooting the process so that fakechroot can handle
# it
@cmdprefix = ('chroot', $options->{root});
} elsif ($options->{mode} eq 'root') {
# unsharing the mount namespace is not enough for
# pivot_root to work as root (why?) unsharing the user
# namespace as well (but without remapping) makes
# pivot_root work (why??) but still makes later lazy
# umounts fail (why???). Since pivot_root is mainly
# useful for being able to run unshare mode inside
# unshare mode, we fall back to just calling chroot()
# until somebody has motivation and time to figure out
# what is going on.
chroot $options->{root}
or error "failed to chroot(): $!";
$options->{root} = "/";
chdir "/" or error "failed chdir() to /: $!";
} elsif ($options->{mode} eq 'unshare') {
0 == syscall &SYS_unshare, $CLONE_NEWNS
or error "unshare() failed: $!";
pivot_root($options->{root});
} else {
error "unknown mode: $options->{mode}";
}
0 == system(@cmdprefix, 'env', @env_opts, 'sh', '-c',
$script)
or error "command failed: $script";
exit 0;
}
waitpid($pid, 0);
$? == 0 or error "chrooted hook failed with exit code $?";
next;
}
# inode and device number of chroot before
my ($dev_before, $ino_before, undef) = stat($options->{root});
if ( if (
$script =~ /^( $script =~ /^(
copy-in|copy-out copy-in|copy-out
@ -1659,24 +1686,12 @@ sub run_hooks {
open(STDIN, '<&', $options->{hooksock}) open(STDIN, '<&', $options->{hooksock})
or error "cannot open STDIN: $!"; or error "cannot open STDIN: $!";
# we execute ourselves under sh to avoid having to # Text::ParseWords::shellwords does for perl what shlex
# implement a clever parser of the quoting used in $script # does for python
# for the filenames my @args = shellwords $script;
my $prefix = ""; hookhelper($options->{root}, $options->{mode}, $name,
if ($is_covering) { $options->{qemu}, $verbosity_level, @args);
$prefix exit 0;
= "$EXECUTABLE_NAME -MDevel::Cover=-silent,-nogcov ";
}
exec 'sh', '-c',
"$prefix$PROGRAM_NAME --hook-helper"
. " \"\$1\" \"\$2\" \"\$3\" \"\$4\" \"\$5\" $script",
'exec', $options->{root}, $options->{mode}, $name,
(
defined $options->{qemu}
? "qemu-$options->{qemu}"
: 'env',
$verbosity_level
);
} }
waitpid($pid, 0); waitpid($pid, 0);
$? == 0 or error "special hook failed with exit code $?"; $? == 0 or error "special hook failed with exit code $?";
@ -1695,22 +1710,29 @@ sub run_hooks {
'sh', '-c', $script, 'exec', $options->{root}) 'sh', '-c', $script, 'exec', $options->{root})
or error "command failed: $script"; or error "command failed: $script";
} }
# If the chroot directory vanished, check if pivot_root was
# performed.
#
# Running pivot_root is only really useful in the customize-hooks
# because mmdebstrap uses apt from the outside to install packages
# and that will fail after pivot_root because the process doesn't
# have access to the system on the outside anymore.
if (!-e $options->{root}) {
my ($dev_root, $ino_root, undef) = stat("/");
if ($dev_before == $dev_root and $ino_before == $ino_root) {
info "detected pivot_root, changing chroot directory to /";
# the old chroot directory is now /
# the hook probably executed pivot_root
$options->{root} = "/";
chdir "/" or error "failed chdir() to /: $!";
} else {
error "chroot directory $options->{root} vanished";
}
}
} }
}; };
# Unset the close-on-exec flag, so that the file descriptor does not
# get closed when we exec
my $flags = fcntl($options->{hooksock}, F_GETFD, 0)
or error "fcntl F_GETFD: $!";
fcntl($options->{hooksock}, F_SETFD, $flags & ~FD_CLOEXEC)
or error "fcntl F_SETFD: $!";
if ($name eq 'setup') {
# execute directly without mounting anything (the mount points do not
# exist yet)
&{$runner}();
} else {
run_chroot(\&$runner, $options);
}
# Restore flags # Restore flags
fcntl($options->{hooksock}, F_SETFD, $flags) or error "fcntl F_SETFD: $!"; fcntl($options->{hooksock}, F_SETFD, $flags) or error "fcntl F_SETFD: $!";
return; return;
@ -1766,6 +1788,34 @@ sub setup {
# FIXME: dpkg could be changed to produce the same results # FIXME: dpkg could be changed to produce the same results
run_extract($options, $essential_pkgs); run_extract($options, $essential_pkgs);
# setup mounts
my @cleanup_tasks = ();
my $cleanup = sub {
my $signal = $_[0];
while (my $task = pop @cleanup_tasks) {
$task->();
}
if ($signal) {
warning "pid $PID cought signal: $signal";
exit 1;
}
};
# we only need to setup the mounts if there is anything to do
if ( $options->{variant} ne 'custom'
or scalar @{ $options->{include} } > 0
or scalar @{ $options->{"extract_hook"} } > 0
or scalar @{ $options->{"essential_hook"} } > 0
or scalar @{ $options->{"customize_hook"} } > 0) {
local $SIG{INT} = $cleanup;
local $SIG{HUP} = $cleanup;
local $SIG{PIPE} = $cleanup;
local $SIG{TERM} = $cleanup;
@cleanup_tasks = setup_mounts($options);
}
eval {
run_hooks('extract', $options); run_hooks('extract', $options);
if ($options->{variant} ne 'extract') { if ($options->{variant} ne 'extract') {
@ -1778,10 +1828,18 @@ sub setup {
run_hooks('essential', $options); run_hooks('essential', $options);
run_install($options, $chrootcmd); run_install($options);
run_hooks('customize', $options); run_hooks('customize', $options);
} }
};
my $msg = $@;
$cleanup->(0);
if ($msg) {
error "setup failed: $msg";
}
if (any { $_ eq 'cleanup' } @{ $options->{skip} }) { if (any { $_ eq 'cleanup' } @{ $options->{skip} }) {
info "skipping cleanup as requested"; info "skipping cleanup as requested";
@ -2309,10 +2367,8 @@ sub run_download() {
dryrun => $options->{dryrun}, dryrun => $options->{dryrun},
}, },
); );
} elsif ( } elsif (any { $_ eq $options->{variant} }
any { $_ eq $options->{variant} } ('essential', 'standard', 'important', 'required', 'buildd')) {
('essential', 'standard', 'important', 'required', 'buildd')
) {
# 2021-06-07, #debian-apt on OFTC, times in UTC+2 # 2021-06-07, #debian-apt on OFTC, times in UTC+2
# 17:27 < DonKult> (?essential includes 'apt' through) # 17:27 < DonKult> (?essential includes 'apt' through)
# 17:30 < josch> DonKult: no, because pkgCacheGen::ForceEssential ","; # 17:30 < josch> DonKult: no, because pkgCacheGen::ForceEssential ",";
@ -2780,18 +2836,11 @@ sub run_essential() {
info "simulate installing essential packages..."; info "simulate installing essential packages...";
} else { } else {
info "installing essential packages..."; info "installing essential packages...";
run_chroot(
sub {
run_dpkg_progress({ run_dpkg_progress({
ARGV => [ ARGV =>
@{$chrootcmd}, 'dpkg', [@{$chrootcmd}, 'dpkg', '--install', '--force-depends'],
'--install', '--force-depends'
],
PKGS => $essential_pkgs, PKGS => $essential_pkgs,
}); });
},
$options
);
} }
} else { } else {
error "unknown mode: $options->{mode}"; error "unknown mode: $options->{mode}";
@ -2819,7 +2868,6 @@ sub run_essential() {
sub run_install() { sub run_install() {
my $options = shift; my $options = shift;
my $chrootcmd = shift;
my %pkgs_to_install; my %pkgs_to_install;
for my $incl (@{ $options->{include} }) { for my $incl (@{ $options->{include} }) {
@ -2836,10 +2884,8 @@ sub run_install() {
if ($options->{variant} eq 'buildd') { if ($options->{variant} eq 'buildd') {
$pkgs_to_install{'build-essential'} = (); $pkgs_to_install{'build-essential'} = ();
} }
if ( if (any { $_ eq $options->{variant} }
any { $_ eq $options->{variant} } ('required', 'important', 'standard', 'buildd')) {
('required', 'important', 'standard', 'buildd')
) {
# Many of the priority:required packages are also essential:yes. We # Many of the priority:required packages are also essential:yes. We
# make sure not to select those here to avoid useless "xxx is already # make sure not to select those here to avoid useless "xxx is already
# the newest version" messages. # the newest version" messages.
@ -2912,10 +2958,7 @@ sub run_install() {
# --root but this would only make sense in situations where there # --root but this would only make sense in situations where there
# is no dpkg inside the chroot. # is no dpkg inside the chroot.
if (!$options->{dryrun}) { if (!$options->{dryrun}) {
run_chroot( info "installing remaining packages inside the chroot...";
sub {
info "installing remaining packages inside the"
. " chroot...";
run_apt_progress({ run_apt_progress({
ARGV => [ ARGV => [
'apt-get', 'apt-get',
@ -2926,10 +2969,7 @@ sub run_install() {
'-o', '-o',
'DPkg::Options::=dpkg', 'DPkg::Options::=dpkg',
$options->{mode} eq 'fakechroot' $options->{mode} eq 'fakechroot'
? ( ? ('-o', 'DPkg::Install::Recursive::force=true')
'-o',
'DPkg::Install::Recursive::force=true'
)
: (), : (),
'-o', '-o',
"DPkg::Chroot-Directory=$options->{root}", "DPkg::Chroot-Directory=$options->{root}",
@ -2938,9 +2978,6 @@ sub run_install() {
], ],
PKGS => [@pkgs_to_install], PKGS => [@pkgs_to_install],
}); });
},
$options
);
} else { } else {
info "simulate installing remaining packages inside the" info "simulate installing remaining packages inside the"
. " chroot..."; . " chroot...";
@ -3175,17 +3212,30 @@ sub chrooted_realpath {
return $result; return $result;
} }
sub pivot_root {
my $root = shift;
my $target = "/mnt";
my $put_old = "tmp";
0 == syscall &SYS_mount, $root, $target, 0, $MS_REC | $MS_BIND, 0
or error "mount failed: $!";
chdir "/mnt" or error "failed chdir() to /mnt: $!";
0 == syscall &SYS_pivot_root, my $new_root = ".", $put_old
or error "pivot_root failed: $!";
chroot "." or error "failed to chroot() to .: $!";
0 == syscall &SYS_umount2, $put_old, $MNT_DETACH
or error "umount2 failed: $!";
0 == syscall &SYS_umount2, my $sys = "sys", $MNT_DETACH
or error "umount2 failed: $!";
return;
}
sub hookhelper { sub hookhelper {
my ($root, $mode, $hook, $qemu, $verbosity, $command, @args) = @_;
$verbosity_level = $verbosity;
# we put everything in an eval block because that way we can easily handle # we put everything in an eval block because that way we can easily handle
# errors without goto labels or much code duplication: the error handler # errors without goto labels or much code duplication: the error handler
# has to send an "error" message to the other side # has to send an "error" message to the other side
eval { eval {
my $root = $ARGV[1];
my $mode = $ARGV[2];
my $hook = $ARGV[3];
my $qemu = $ARGV[4];
$verbosity_level = $ARGV[5];
my $command = $ARGV[6];
my @cmdprefix = (); my @cmdprefix = ();
my @tarcmd = ( my @tarcmd = (
@ -3209,16 +3259,14 @@ sub hookhelper {
error "unknown hook: $hook"; error "unknown hook: $hook";
} }
if ( if (any { $_ eq $command } ('copy-in', 'tar-in', 'upload', 'sync-in'))
any { $_ eq $command } {
('copy-in', 'tar-in', 'upload', 'sync-in') if (scalar @args < 2) {
) {
if (scalar @ARGV < 9) {
error "$command needs at least one path on the" error "$command needs at least one path on the"
. " outside and the output path inside the chroot"; . " outside and the output path inside the chroot";
} }
my $outpath = $ARGV[-1]; my $outpath = pop @args;
for (my $i = 7 ; $i < $#ARGV ; $i++) { foreach my $file (@args) {
# the right argument for tar's --directory argument depends on # the right argument for tar's --directory argument depends on
# whether tar is called from inside the chroot or from the # whether tar is called from inside the chroot or from the
# outside # outside
@ -3226,17 +3274,13 @@ sub hookhelper {
if ($hook eq 'setup') { if ($hook eq 'setup') {
# tar runs outside, so acquire the correct path # tar runs outside, so acquire the correct path
$directory = chrooted_realpath $root, $outpath; $directory = chrooted_realpath $root, $outpath;
} elsif ( } elsif (any { $_ eq $hook }
any { $_ eq $hook } ('extract', 'essential', 'customize')) {
('extract', 'essential', 'customize')
) {
if ($mode eq 'fakechroot') { if ($mode eq 'fakechroot') {
# tar will run inside the chroot # tar will run inside the chroot
$directory = $outpath; $directory = $outpath;
} elsif ( } elsif (any { $_ eq $mode }
any { $_ eq $mode } ('root', 'chrootless', 'unshare')) {
('root', 'chrootless', 'unshare')
) {
$directory = chrooted_realpath $root, $outpath; $directory = chrooted_realpath $root, $outpath;
} else { } else {
error "unknown mode: $mode"; error "unknown mode: $mode";
@ -3267,10 +3311,8 @@ sub hookhelper {
# open the requested file for writing # open the requested file for writing
open $fh, '|-', @cmdprefix, 'sh', '-c', 'cat > "$1"', open $fh, '|-', @cmdprefix, 'sh', '-c', 'cat > "$1"',
'exec', $directory // error "failed to fork(): $!"; 'exec', $directory // error "failed to fork(): $!";
} elsif ( } elsif (any { $_ eq $command }
any { $_ eq $command } ('copy-in', 'tar-in', 'sync-in')) {
('copy-in', 'tar-in', 'sync-in')
) {
# open a tar process that extracts the tarfile that we # open a tar process that extracts the tarfile that we
# supply it with on stdin to the output directory inside # supply it with on stdin to the output directory inside
# the chroot # the chroot
@ -3288,20 +3330,17 @@ sub hookhelper {
# instruct the parent process to create a tarball of the # instruct the parent process to create a tarball of the
# requested path outside the chroot # requested path outside the chroot
debug "helper: sending mktar"; debug "helper: sending mktar";
print STDOUT ( print STDOUT (pack("n", length $file) . "mktar" . $file);
pack("n", length $ARGV[$i]) . "mktar" . $ARGV[$i]);
} elsif ($command eq 'sync-in') { } elsif ($command eq 'sync-in') {
# instruct the parent process to create a tarball of the # instruct the parent process to create a tarball of the
# content of the requested path outside the chroot # content of the requested path outside the chroot
debug "helper: sending mktac"; debug "helper: sending mktac";
print STDOUT ( print STDOUT (pack("n", length $file) . "mktac" . $file);
pack("n", length $ARGV[$i]) . "mktac" . $ARGV[$i]);
} elsif (any { $_ eq $command } ('upload', 'tar-in')) { } elsif (any { $_ eq $command } ('upload', 'tar-in')) {
# instruct parent process to open a tarball of the # instruct parent process to open a tarball of the
# requested path outside the chroot for reading # requested path outside the chroot for reading
debug "helper: sending openr"; debug "helper: sending openr";
print STDOUT ( print STDOUT (pack("n", length $file) . "openr" . $file);
pack("n", length $ARGV[$i]) . "openr" . $ARGV[$i]);
} else { } else {
error "unknown command: $command"; error "unknown command: $command";
} }
@ -3356,35 +3395,29 @@ sub hookhelper {
error "tar failed"; error "tar failed";
} }
} }
} elsif ( } elsif (any { $_ eq $command }
any { $_ eq $command } ('copy-out', 'tar-out', 'download', 'sync-out')) {
('copy-out', 'tar-out', 'download', 'sync-out') if (scalar @args < 2) {
) {
if (scalar @ARGV < 9) {
error "$command needs at least one path inside the chroot and" error "$command needs at least one path inside the chroot and"
. " the output path on the outside"; . " the output path on the outside";
} }
my $outpath = $ARGV[-1]; my $outpath = pop @args;
for (my $i = 7 ; $i < $#ARGV ; $i++) { foreach my $file (@args) {
# the right argument for tar's --directory argument depends on # the right argument for tar's --directory argument depends on
# whether tar is called from inside the chroot or from the # whether tar is called from inside the chroot or from the
# outside # outside
my $directory; my $directory;
if ($hook eq 'setup') { if ($hook eq 'setup') {
# tar runs outside, so acquire the correct path # tar runs outside, so acquire the correct path
$directory = chrooted_realpath $root, $ARGV[$i]; $directory = chrooted_realpath $root, $file;
} elsif ( } elsif (any { $_ eq $hook }
any { $_ eq $hook } ('extract', 'essential', 'customize')) {
('extract', 'essential', 'customize')
) {
if ($mode eq 'fakechroot') { if ($mode eq 'fakechroot') {
# tar will run inside the chroot # tar will run inside the chroot
$directory = $ARGV[$i]; $directory = $file;
} elsif ( } elsif (any { $_ eq $mode }
any { $_ eq $mode } ('root', 'chrootless', 'unshare')) {
('root', 'chrootless', 'unshare') $directory = chrooted_realpath $root, $file;
) {
$directory = chrooted_realpath $root, $ARGV[$i];
} else { } else {
error "unknown mode: $mode"; error "unknown mode: $mode";
} }
@ -3497,11 +3530,11 @@ sub hookhelper {
} }
sub hooklistener { sub hooklistener {
$verbosity_level = shift;
# we put everything in an eval block because that way we can easily handle # we put everything in an eval block because that way we can easily handle
# errors without goto labels or much code duplication: the error handler # errors without goto labels or much code duplication: the error handler
# has to send an "error" message to the other side # has to send an "error" message to the other side
eval { eval {
$verbosity_level = $ARGV[1];
while (1) { while (1) {
# get the next message # get the next message
my $msg = "error"; my $msg = "error";
@ -4086,10 +4119,8 @@ sub get_sourceslist_by_suite {
# the security mirror changes, starting with bullseye # the security mirror changes, starting with bullseye
# https://lists.debian.org/87r26wqr2a.fsf@43-1.org # https://lists.debian.org/87r26wqr2a.fsf@43-1.org
my $bullseye_or_later = 0; my $bullseye_or_later = 0;
if ( if (any { $_ eq $suite } ('stable', 'bullseye', 'bookworm', 'trixie'))
any { $_ eq $suite } {
('stable', 'bullseye', 'bookworm', 'trixie')
) {
$bullseye_or_later = 1; $bullseye_or_later = 1;
} }
my $distro_info = '/usr/share/distro-info/debian.csv'; my $distro_info = '/usr/share/distro-info/debian.csv';
@ -4235,14 +4266,15 @@ sub main() {
umask 022; umask 022;
if (scalar @ARGV >= 7 && $ARGV[0] eq "--hook-helper") { if (scalar @ARGV >= 7 && $ARGV[0] eq "--hook-helper") {
hookhelper(); shift @ARGV; # shift off "--hook-helper"
hookhelper(@ARGV);
exit 0; exit 0;
} }
# this is the counterpart to --hook-helper and will receive and carry # this is the counterpart to --hook-helper and will receive and carry
# out its instructions # out its instructions
if (scalar @ARGV == 2 && $ARGV[0] eq "--hook-listener") { if (scalar @ARGV == 2 && $ARGV[0] eq "--hook-listener") {
hooklistener(); hooklistener($ARGV[1]);
exit 0; exit 0;
} }
@ -4402,16 +4434,25 @@ sub main() {
'force-check-gpg' => 'force-check-gpg' =>
sub { push @{ $options->{noop} }, 'force-check-gpg'; }, sub { push @{ $options->{noop} }, 'force-check-gpg'; },
'setup-hook=s' => sub { 'setup-hook=s' => sub {
push @{ $options->{setup_hook} }, $_[1]; push @{ $options->{setup_hook} }, ["normal", $_[1]];
}, },
'extract-hook=s' => sub { 'extract-hook=s' => sub {
push @{ $options->{extract_hook} }, $_[1]; push @{ $options->{extract_hook} }, ["normal", $_[1]];
},
'chrooted-extract-hook=s' => sub {
push @{ $options->{extract_hook} }, ["pivoted", $_[1]];
}, },
'essential-hook=s' => sub { 'essential-hook=s' => sub {
push @{ $options->{essential_hook} }, $_[1]; push @{ $options->{essential_hook} }, ["normal", $_[1]];
},
'chrooted-essential-hook=s' => sub {
push @{ $options->{essential_hook} }, ["pivoted", $_[1]];
}, },
'customize-hook=s' => sub { 'customize-hook=s' => sub {
push @{ $options->{customize_hook} }, $_[1]; push @{ $options->{customize_hook} }, ["normal", $_[1]];
},
'chrooted-customize-hook=s' => sub {
push @{ $options->{customize_hook} }, ["pivoted", $_[1]];
}, },
'hook-directory=s' => sub { 'hook-directory=s' => sub {
my ($opt_name, $opt_value) = @_; my ($opt_name, $opt_value) = @_;
@ -4446,7 +4487,7 @@ sub main() {
# list of hooks # list of hooks
foreach my $hook (keys %scripts) { foreach my $hook (keys %scripts) {
push @{ $options->{"${hook}_hook"} }, push @{ $options->{"${hook}_hook"} },
(sort @{ $scripts{$hook} }); (map { ["normal", $_] } (sort @{ $scripts{$hook} }));
} }
}, },
# Sometimes --simulate fails even though non-simulate succeeds because # Sometimes --simulate fails even though non-simulate succeeds because
@ -4487,6 +4528,14 @@ sub main() {
if (scalar @{ $options->{"${hook}_hook"} } > 0) { if (scalar @{ $options->{"${hook}_hook"} } > 0) {
warning "In dry-run mode, --$hook-hook options have no effect"; warning "In dry-run mode, --$hook-hook options have no effect";
} }
if ($options->{mode} eq 'chrootless') {
foreach my $script (@{ $options->{"${hook}_hook"} }) {
if ($script->[0] eq "pivoted") {
error "--chrooted-$hook-hook are illegal in "
. "chrootless mode";
}
}
}
} }
} }
@ -5520,10 +5569,8 @@ sub main() {
); );
waitpid $pid, 0; waitpid $pid, 0;
$? == 0 or error "havemknod failed"; $? == 0 or error "havemknod failed";
} elsif ( } elsif (any { $_ eq $options->{mode} }
any { $_ eq $options->{mode} } ('root', 'fakechroot', 'chrootless')) {
('root', 'fakechroot', 'chrootless')
) {
$options->{havemknod} = havemknod($options->{root}); $options->{havemknod} = havemknod($options->{root});
} else { } else {
error "unknown mode: $options->{mode}"; error "unknown mode: $options->{mode}";
@ -5681,10 +5728,8 @@ sub main() {
}, },
\@idmap \@idmap
); );
} elsif ( } elsif (any { $_ eq $options->{mode} }
any { $_ eq $options->{mode} } ('root', 'fakechroot', 'chrootless')) {
('root', 'fakechroot', 'chrootless')
) {
$pid = fork() // error "fork() failed: $!"; $pid = fork() // error "fork() failed: $!";
if ($pid == 0) { if ($pid == 0) {
local $SIG{'INT'} = 'DEFAULT'; local $SIG{'INT'} = 'DEFAULT';
@ -5746,10 +5791,8 @@ sub main() {
0 == system('chroot', $options->{root}, 'tar', 0 == system('chroot', $options->{root}, 'tar',
@taropts, '-C', '/', '.') @taropts, '-C', '/', '.')
or error "tar failed: $?"; or error "tar failed: $?";
} elsif ( } elsif (any { $_ eq $options->{mode} } ('root', 'chrootless'))
any { $_ eq $options->{mode} } {
('root', 'chrootless')
) {
# If the chroot directory is not owned by the root user, # If the chroot directory is not owned by the root user,
# then we assume that no measure was taken to fake root # then we assume that no measure was taken to fake root
# permissions. Since the final tarball should contain # permissions. Since the final tarball should contain
@ -5812,11 +5855,8 @@ sub main() {
open(STDIN, '<&', $parentsock) open(STDIN, '<&', $parentsock)
or error "cannot open STDIN: $!"; or error "cannot open STDIN: $!";
my @prefix = (); hooklistener($verbosity_level);
if ($is_covering) { exit 0;
@prefix = ($EXECUTABLE_NAME, "-MDevel::Cover=-silent,-nogcov");
}
exec @prefix, $PROGRAM_NAME, "--hook-listener", $verbosity_level;
} }
waitpid($lpid, 0); waitpid($lpid, 0);
if ($? != 0) { if ($? != 0) {
@ -5981,10 +6021,8 @@ sub main() {
rmdir "$options->{root}" rmdir "$options->{root}"
or error "cannot rmdir $options->{root}: $!"; or error "cannot rmdir $options->{root}: $!";
} }
} elsif ( } elsif (any { $_ eq $options->{mode} }
any { $_ eq $options->{mode} } ('root', 'fakechroot', 'chrootless')) {
('root', 'fakechroot', 'chrootless')
) {
# without unshare, we use the system's rm to recursively remove the # without unshare, we use the system's rm to recursively remove the
# temporary directory just to make sure that we do not accidentally # temporary directory just to make sure that we do not accidentally
# remove more than we should by using --one-file-system. # remove more than we should by using --one-file-system.
@ -6030,7 +6068,8 @@ B<mmdebstrap> creates a Debian chroot of I<SUITE> into I<TARGET> from one or
more I<MIRROR>s. It is meant as an alternative to the debootstrap tool (see more I<MIRROR>s. It is meant as an alternative to the debootstrap tool (see
section B<DEBOOTSTRAP>). In contrast to debootstrap it uses apt to resolve section B<DEBOOTSTRAP>). In contrast to debootstrap it uses apt to resolve
dependencies and is thus able to use more than one mirror and resolve more dependencies and is thus able to use more than one mirror and resolve more
complex dependencies. complex dependencies. See section B<OPERATION> for an overview of how
B<mmdebstrap> works internally.
If no I<MIRROR> option is provided, L<http://deb.debian.org/debian> is used. If no I<MIRROR> option is provided, L<http://deb.debian.org/debian> is used.
If I<SUITE> is a stable release name and no I<MIRROR> is specified, then If I<SUITE> is a stable release name and no I<MIRROR> is specified, then
@ -6200,7 +6239,6 @@ Example: Exclude paths to reduce chroot size
--dpkgopt='path-exclude=/usr/share/doc/*' --dpkgopt='path-exclude=/usr/share/doc/*'
--dpkgopt='path-include=/usr/share/doc/*/copyright' --dpkgopt='path-include=/usr/share/doc/*/copyright'
--dpkgopt='path-include=/usr/share/doc/*/changelog.Debian.*' --dpkgopt='path-include=/usr/share/doc/*/changelog.Debian.*'
--dpkgopt='path-exclude=/usr/share/{doc,info,man,omf,help,gnome/help}/*'
=item B<--include>=I<pkg1>[,I<pkg2>,...] =item B<--include>=I<pkg1>[,I<pkg2>,...]
@ -6757,7 +6795,28 @@ retained.
=head1 OPERATION =head1 OPERATION
This section gives an overview of the different steps to create a chroot. This section gives an overview of the different steps to create a chroot. At
its core, what B<mmdebstrap> does can be put into a 14 line shell script:
mkdir -p "$2/etc/apt" "$2/var/cache"
cat << END > "$2/apt.conf"
Apt::Architecture "$(dpkg --print-architecture)";
Apt::Architectures "$(dpkg --print-architecture)";
Dir "$(cd "$2" && pwd)";
Dir::Etc::Trusted "$(eval "$(apt-config shell v Dir::Etc::Trusted/f)"; printf "$v")";
Dir::Etc::TrustedParts "$(eval "$(apt-config shell v Dir::Etc::TrustedParts/d)"; printf "$v")";
END
echo "deb http://deb.debian.org/debian/ $1 main" > "$2/etc/apt/sources.list"
APT_CONFIG="$2/apt.conf" apt-get update
APT_CONFIG="$2/apt.conf" apt-get --yes --download-only install '?essential'
for f in "$2"/var/cache/apt/archives/*.deb; do dpkg-deb --extract "$f" "$2"; done
chroot "$2" sh -c "dpkg --install --force-depends /var/cache/apt/archives/*.deb"
The additional complexity of B<mmdebstrap> is to support operation without
superuser privileges, bit-by-bit reproducible output, hooks and foreign
architecture support.
The remainder of this section explains what B<mmdebstrap> does step-by-step.
=over 8 =over 8
@ -6820,6 +6879,17 @@ C<apt-get dist-upgrade>. In the remaining variants, all Packages files
downloaded by the B<update> step are inspected to find the C<Essential:yes> downloaded by the B<update> step are inspected to find the C<Essential:yes>
package set as well as all packages of the required priority. package set as well as all packages of the required priority.
=item B<mount>
Mount relevant device nodes, F</proc> and F</sys> into the chroot and unmount
them afterwards. This can be disabled using B<--skip=chroot/mount> or
specifically by B<--skip=chroot/mount/dev>, B<--skip=chroot/mount/proc> and
B<--skip=chroot/mount/sys>, respectively. B<mmdebstrap> will disable running
services by temporarily moving F</usr/sbin/policy-rc.d> and
F</sbin/start-stop-daemon> if they exist. This can be disabled with
B<--skip=chroot/policy-rc.d> and B<--skip=chroot/start-stop-daemon>,
respectively.
=item B<extract> =item B<extract>
Extract the downloaded packages into the rootfs. Extract the downloaded packages into the rootfs.
@ -6860,17 +6930,10 @@ out in B<extract> mode.
Run B<--customize-hook> options and all F<customize*> scripts in B<--hook-dir>. Run B<--customize-hook> options and all F<customize*> scripts in B<--hook-dir>.
This step is not carried out in B<extract> mode. This step is not carried out in B<extract> mode.
Whenever B<mmdebstrap> does a chroot call in B<root> or B<unshare> modes, it =item B<unmount>
will mount relevant device nodes, F</proc> and F</sys> into the chroot and
unmount them afterwards. This can be disabled using B<--skip=chroot/mount> or
specifically by B<--skip=chroot/mount/dev>, B<--skip=chroot/mount/proc> and
B<--skip=chroot/mount/sys>, respectively.
For each command that is run inside the chroot, B<mmdebstrap> will disable Unmount everything that was mounted during the B<mount> stage and restores
running services by temporarily moving F</usr/sbin/policy-rc.d> and F</usr/sbin/policy-rc.d> and F</sbin/start-stop-daemon> if necessary.
F</sbin/start-stop-daemon> if they exist. This can be disabled with
B<--skip=chroot/policy-rc.d> and B<--skip=chroot/start-stop-daemon>,
respectively.
=item B<cleanup> =item B<cleanup>

View file

@ -145,7 +145,7 @@ case "$nativearch" in
[ $BOOT = bios ] || [ $BOOT = efi ] [ $BOOT = bios ] || [ $BOOT = efi ]
if [ $BOOT = bios ]; then if [ $BOOT = bios ]; then
include="linux-image-686-pae grub-pc" include="linux-image-686-pae grub-pc"
grub_target="i386-efi" grub_target="i386-pc"
elif [ $BOOT = efi ]; then elif [ $BOOT = efi ]; then
include="linux-image-686-pae grub-efi" include="linux-image-686-pae grub-efi"
grub_target="i386-efi" grub_target="i386-efi"

View file

@ -25,6 +25,26 @@ cleanup() {
trap cleanup INT TERM EXIT trap cleanup INT TERM EXIT
ARCH=$(dpkg --print-architecture)
case $ARCH in
i386)
MACHINE="accel=kvm:tcg"
CODE="/usr/share/OVMF/OVMF32_CODE_4M.secboot.fd"
QEMUARCH="i386"
;;
amd64)
MACHINE="accel=kvm:tcg"
CODE="/usr/share/OVMF/OVMF_CODE.fd"
QEMUARCH="x86_64"
;;
arm64)
MACHINE="type=virt,gic-version=host,accel=kvm"
CODE="/usr/share/AAVMF/AAVMF_CODE.fd,readonly"
QEMUARCH="aarch64"
;;
*) echo "qemu kvm not supported on $ARCH" >&2;;
esac
# the path to debian-$DEFAULT_DIST.qcow must be absolute or otherwise qemu will # the path to debian-$DEFAULT_DIST.qcow must be absolute or otherwise qemu will
# look for the path relative to debian-$DEFAULT_DIST-overlay.qcow # look for the path relative to debian-$DEFAULT_DIST-overlay.qcow
qemu-img create -f qcow2 -b "$(realpath $cachedir)/debian-$DEFAULT_DIST.qcow" -F qcow2 "$tmpdir/debian-$DEFAULT_DIST-overlay.qcow" qemu-img create -f qcow2 -b "$(realpath $cachedir)/debian-$DEFAULT_DIST.qcow" -F qcow2 "$tmpdir/debian-$DEFAULT_DIST-overlay.qcow"
@ -34,15 +54,16 @@ qemu-img create -f qcow2 -b "$(realpath $cachedir)/debian-$DEFAULT_DIST.qcow" -F
# or this (quit with ctrl+q): # or this (quit with ctrl+q):
# socat stdin,raw,echo=0,escape=0x11 unix-connect:/tmp/ttyS0 # socat stdin,raw,echo=0,escape=0x11 unix-connect:/tmp/ttyS0
ret=0 ret=0
timeout --foreground 20m qemu-system-x86_64 \ timeout --foreground 40m qemu-system-"$QEMUARCH" \
-cpu host \ -cpu host \
-no-user-config \ -no-user-config \
-M accel=kvm:tcg -m 4G -nographic \ -M "$MACHINE" -m 4G -nographic \
-object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 \ -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 \
-monitor unix:/tmp/monitor,server,nowait \ -monitor unix:/tmp/monitor,server,nowait \
-serial unix:/tmp/ttyS0,server,nowait \ -serial unix:/tmp/ttyS0,server,nowait \
-serial unix:/tmp/ttyS1,server,nowait \ -serial unix:/tmp/ttyS1,server,nowait \
-net nic,model=virtio -net user \ -net nic,model=virtio -net user \
-drive if=pflash,format=raw,unit=0,read-only,file="$CODE" \
-virtfs local,id=mmdebstrap,path="$(pwd)/shared",security_model=none,mount_tag=mmdebstrap \ -virtfs local,id=mmdebstrap,path="$(pwd)/shared",security_model=none,mount_tag=mmdebstrap \
-drive file="$tmpdir/debian-$DEFAULT_DIST-overlay.qcow",cache=unsafe,index=0,if=virtio \ -drive file="$tmpdir/debian-$DEFAULT_DIST-overlay.qcow",cache=unsafe,index=0,if=virtio \
>"$tmpdir/log" 2>&1 || ret=$? >"$tmpdir/log" 2>&1 || ret=$?

View file

@ -74,8 +74,8 @@ for d in ./var/lib/apt/lists/partial ./var/cache/apt/archives/partial; do
chroot /tmp/debian-debootstrap chmod 0700 $d chroot /tmp/debian-debootstrap chmod 0700 $d
chroot /tmp/debian-debootstrap chown _apt:root $d chroot /tmp/debian-debootstrap chown _apt:root $d
done done
tar -C /tmp/debian-debootstrap --numeric-owner --xattrs --xattrs-include='*' --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root1.tar . tar -C /tmp/debian-debootstrap --numeric-owner --xattrs --xattrs-include='*' --sort=name --clamp-mtime --mtime="$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds)" -cf /tmp/root1.tar .
tar -C /tmp/debian-mm --numeric-owner --xattrs --xattrs-include='*' --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root2.tar . tar -C /tmp/debian-mm --numeric-owner --xattrs --xattrs-include='*' --sort=name --clamp-mtime --mtime="$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds)" -cf /tmp/root2.tar .
tar --full-time --verbose -tf /tmp/root1.tar > /tmp/root1.tar.list tar --full-time --verbose -tf /tmp/root1.tar > /tmp/root1.tar.list
tar --full-time --verbose -tf /tmp/root2.tar > /tmp/root2.tar.list tar --full-time --verbose -tf /tmp/root2.tar > /tmp/root2.tar.list
# despite SOURCE_DATE_EPOCH and --clamp-mtime, the timestamps in the tarball # despite SOURCE_DATE_EPOCH and --clamp-mtime, the timestamps in the tarball

View file

@ -7,9 +7,9 @@ if [ ! -e /mmdebstrap-testenv ]; then
fi fi
for f in /usr/share/keyrings/*.gpg; do for f in /usr/share/keyrings/*.gpg; do
name=$(basename "$f" .gpg) name=$(basename "$f" .gpg)
gpg --enarmor < /usr/share/keyrings/$name.gpg \ gpg --enarmor < "/usr/share/keyrings/$name.gpg" \
| sed 's/ PGP ARMORED FILE/ PGP PUBLIC KEY BLOCK/;/^Comment: /d' \ | sed 's/ PGP ARMORED FILE/ PGP PUBLIC KEY BLOCK/;/^Comment: /d' \
> /etc/apt/trusted.gpg.d/$name.asc > "/etc/apt/trusted.gpg.d/$name.asc"
done done
rm /etc/apt/trusted.gpg.d/*.gpg rm /etc/apt/trusted.gpg.d/*.gpg
rm /usr/share/keyrings/*.gpg rm /usr/share/keyrings/*.gpg

View file

@ -3,7 +3,7 @@ set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
trap "rm -f /tmp/debian-chroot.tar" EXIT INT TERM trap "rm -f /tmp/debian-chroot.tar" EXIT INT TERM
{{ CMD }} --mode={{ MODE }} --variant=custom \ {{ CMD }} --mode={{ MODE }} --variant=custom \
--include $(cat pkglist.txt | tr '\n' ',') \ --include "$(tr '\n' ',' < pkglist.txt)" \
--aptopt='APT::Solver "aspcud"' \ --aptopt='APT::Solver "aspcud"' \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }} {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort \ tar -tf /tmp/debian-chroot.tar | sort \

View file

@ -12,7 +12,7 @@ echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"
# we cannot use useradd because passwd is not Essential:yes # we cannot use useradd because passwd is not Essential:yes
{{ CMD }} --variant={{ VARIANT }} --mode={{ MODE }} \ {{ CMD }} --variant={{ VARIANT }} --mode={{ MODE }} \
--essential-hook='case {{ DIST }} in oldstable|stable) if [ {{ VARIANT }} = - ]; then echo _apt:*:100:65534::/nonexistent:/usr/sbin/nologin >> "$1"/etc/passwd; fi;; esac' \ --essential-hook='case {{ DIST }} in oldstable|stable) if [ {{ VARIANT }} = - ]; then echo _apt:*:100:65534::/nonexistent:/usr/sbin/nologin >> "$1"/etc/passwd; fi;; esac' \
$(case {{ DIST }} in oldstable|stable) : ;; *) echo --hook-dir=./hooks/merged-usr ;; esac) \ "$(case {{ DIST }} in oldstable|stable) echo --merged-usr ;; *) echo --hook-dir=./hooks/merged-usr ;; esac)" \
{{ DIST }} /tmp/debian-{{ DIST }}-mm.tar {{ MIRROR }} {{ DIST }} /tmp/debian-{{ DIST }}-mm.tar {{ MIRROR }}
mkdir /tmp/debian-{{ DIST }}-mm mkdir /tmp/debian-{{ DIST }}-mm
@ -147,6 +147,7 @@ done
# Because of unreproducible uids (#969631) we created the _apt user ourselves # Because of unreproducible uids (#969631) we created the _apt user ourselves
# and because passwd is not Essential:yes we didn't use useradd. But newer # and because passwd is not Essential:yes we didn't use useradd. But newer
# versions of adduser and shadow will create a different /etc/shadow # versions of adduser and shadow will create a different /etc/shadow
if [ "{{ VARIANT }}" = "-" ]; then
case {{ DIST }} in oldstable|stable) case {{ DIST }} in oldstable|stable)
for f in shadow shadow-; do for f in shadow shadow-; do
if grep -q '^_apt:!:' /tmp/debian-{{ DIST }}-debootstrap/etc/$f; then if grep -q '^_apt:!:' /tmp/debian-{{ DIST }}-debootstrap/etc/$f; then
@ -154,12 +155,13 @@ for f in shadow shadow-; do
fi fi
done;; done;;
esac esac
fi
for log in faillog lastlog; do for log in faillog lastlog; do
if ! cmp /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /tmp/debian-{{ DIST }}-mm/var/log/$log >&2;then if ! cmp /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /tmp/debian-{{ DIST }}-mm/var/log/$log >&2;then
# if the files differ, make sure they are all zeroes # if the files differ, make sure they are all zeroes
cmp -n $(stat -c %s /tmp/debian-{{ DIST }}-debootstrap/var/log/$log) /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /dev/zero >&2 cmp -n "$(stat -c %s "/tmp/debian-{{ DIST }}-debootstrap/var/log/$log")" "/tmp/debian-{{ DIST }}-debootstrap/var/log/$log" /dev/zero >&2
cmp -n $(stat -c %s /tmp/debian-{{ DIST }}-mm/var/log/$log) /tmp/debian-{{ DIST }}-mm/var/log/$log /dev/zero >&2 cmp -n "$(stat -c %s "/tmp/debian-{{ DIST }}-mm/var/log/$log")" "/tmp/debian-{{ DIST }}-mm/var/log/$log" /dev/zero >&2
# then delete them # then delete them
rm /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /tmp/debian-{{ DIST }}-mm/var/log/$log rm /tmp/debian-{{ DIST }}-debootstrap/var/log/$log /tmp/debian-{{ DIST }}-mm/var/log/$log
fi fi
@ -170,7 +172,7 @@ done
if [ "{{ VARIANT }}" = "-" ]; then if [ "{{ VARIANT }}" = "-" ]; then
case {{ DIST }} in testing|unstable) case {{ DIST }} in testing|unstable)
for f in group group- gshadow gshadow-; do for f in group group- gshadow gshadow-; do
! cmp /tmp/debian-{{ DIST }}-mm/etc/$f /tmp/debian-{{ DIST }}-debootstrap/etc/$f 2>/dev/null cmp /tmp/debian-{{ DIST }}-mm/etc/$f /tmp/debian-{{ DIST }}-debootstrap/etc/$f 2>/dev/null && exit 1
for d in mm debootstrap; do for d in mm debootstrap; do
sort /tmp/debian-{{ DIST }}-$d/etc/$f > /tmp/debian-{{ DIST }}-$d/etc/$f.bak sort /tmp/debian-{{ DIST }}-$d/etc/$f > /tmp/debian-{{ DIST }}-$d/etc/$f.bak
mv /tmp/debian-{{ DIST }}-$d/etc/$f.bak /tmp/debian-{{ DIST }}-$d/etc/$f mv /tmp/debian-{{ DIST }}-$d/etc/$f.bak /tmp/debian-{{ DIST }}-$d/etc/$f
@ -181,8 +183,7 @@ if [ "{{ VARIANT }}" = "-" ]; then
fi fi
# workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=917773 # workaround for https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=917773
# also needed for users that are created by systemd-sysusers before systemd 252 case {{ DIST }} in oldstable|stable)
# https://github.com/systemd/systemd/pull/24534
for f in shadow shadow-; do for f in shadow shadow-; do
if [ ! -e /tmp/debian-{{ DIST }}-mm/etc/$f ]; then if [ ! -e /tmp/debian-{{ DIST }}-mm/etc/$f ]; then
continue continue
@ -195,7 +196,8 @@ for f in shadow shadow-; do
else else
echo no difference for /etc/$f on {{ DIST }} {{ VARIANT }} >&2 echo no difference for /etc/$f on {{ DIST }} {{ VARIANT }} >&2
fi fi
done done;;
esac
# check if the file content differs # check if the file content differs
diff --unified --no-dereference --recursive /tmp/debian-{{ DIST }}-debootstrap /tmp/debian-{{ DIST }}-mm >&2 diff --unified --no-dereference --recursive /tmp/debian-{{ DIST }}-debootstrap /tmp/debian-{{ DIST }}-mm >&2
@ -206,10 +208,10 @@ find /tmp/debian-{{ DIST }}-debootstrap /tmp/debian-{{ DIST }}-mm -type d -print
# debootstrap never ran apt -- fixing permissions # debootstrap never ran apt -- fixing permissions
for d in ./var/lib/apt/lists/partial ./var/cache/apt/archives/partial; do for d in ./var/lib/apt/lists/partial ./var/cache/apt/archives/partial; do
chroot /tmp/debian-{{ DIST }}-debootstrap chmod 0700 $d chroot /tmp/debian-{{ DIST }}-debootstrap chmod 0700 $d
chroot /tmp/debian-{{ DIST }}-debootstrap chown $(id -u _apt):root $d chroot /tmp/debian-{{ DIST }}-debootstrap chown "$(id -u _apt):root" $d
done done
tar -C /tmp/debian-{{ DIST }}-debootstrap --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root1.tar . tar -C /tmp/debian-{{ DIST }}-debootstrap --numeric-owner --sort=name --clamp-mtime --mtime="$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds)" -cf /tmp/root1.tar .
tar -C /tmp/debian-{{ DIST }}-mm --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/root2.tar . tar -C /tmp/debian-{{ DIST }}-mm --numeric-owner --sort=name --clamp-mtime --mtime="$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds)" -cf /tmp/root2.tar .
tar --full-time --verbose -tf /tmp/root1.tar > /tmp/root1.tar.list tar --full-time --verbose -tf /tmp/root1.tar > /tmp/root1.tar.list
tar --full-time --verbose -tf /tmp/root2.tar > /tmp/root2.tar.list tar --full-time --verbose -tf /tmp/root2.tar > /tmp/root2.tar.list
diff -u /tmp/root1.tar.list /tmp/root2.tar.list >&2 diff -u /tmp/root1.tar.list /tmp/root2.tar.list >&2

View file

@ -2,6 +2,10 @@
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }} export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
if dpkg --compare-versions "$(dpkg-query -W -f='${Version}' libpam-runtime)" le 1.5.2-5; then
# https://bugs.debian.org/1022952
exit 0
fi
trap "rm -f /tmp/chrootless.tar /tmp/root.tar" EXIT INT TERM trap "rm -f /tmp/chrootless.tar /tmp/root.tar" EXIT INT TERM
# we need --hook-dir=./hooks/merged-usr because usrmerge does not understand # we need --hook-dir=./hooks/merged-usr because usrmerge does not understand
# DPKG_ROOT # DPKG_ROOT

View file

@ -20,7 +20,7 @@ for INCLUDE in '' 'systemd-sysv'; do
--hook-dir=./hooks/merged-usr ${INCLUDE:+--include="$INCLUDE"} \ --hook-dir=./hooks/merged-usr ${INCLUDE:+--include="$INCLUDE"} \
{{ DIST }} "/tmp/root.tar" {{ MIRROR }} {{ DIST }} "/tmp/root.tar" {{ MIRROR }}
echo 0 > /proc/sys/fs/binfmt_misc/qemu-aarch64 echo 0 > /proc/sys/fs/binfmt_misc/qemu-aarch64
! arch-test arm64 arch-test arm64 && exit 1
{{ CMD }} --mode=chrootless --architecture=arm64 --variant={{ VARIANT }} \ {{ CMD }} --mode=chrootless --architecture=arm64 --variant={{ VARIANT }} \
--hook-dir=./hooks/merged-usr ${INCLUDE:+--include="$INCLUDE"} \ --hook-dir=./hooks/merged-usr ${INCLUDE:+--include="$INCLUDE"} \
{{ DIST }} "/tmp/chrootless.tar" {{ MIRROR }} {{ DIST }} "/tmp/chrootless.tar" {{ MIRROR }}

View file

@ -11,7 +11,7 @@ if [ ! -e /mmdebstrap-testenv ]; then
exit 1 exit 1
fi fi
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
trap "rm -f \"$tmpdir\"/*.deb /tmp/orig.tar /tmp/test1.tar /tmp/test2.tar; rmdir \"$tmpdir\"" EXIT INT TERM trap 'rm -f "$tmpdir"/*.deb /tmp/orig.tar /tmp/test1.tar /tmp/test2.tar; rmdir "$tmpdir"' EXIT INT TERM
include="--include=doc-debian" include="--include=doc-debian"
if [ "{{ VARIANT }}" = "custom" ]; then if [ "{{ VARIANT }}" = "custom" ]; then

View file

@ -42,8 +42,8 @@ $prefix {{ CMD }} --mode={{ MODE }} --variant=apt --architectures=arm64 {{ DIST
| sed 's/aarch64-linux-gnu/x86_64-linux-gnu/' \ | sed 's/aarch64-linux-gnu/x86_64-linux-gnu/' \
| sed 's/arm64/amd64/'; | sed 's/arm64/amd64/';
} | sort > tar2.txt } | sort > tar2.txt
{ cat tar1.txt \ { < tar1.txt \
| grep -v '^\./usr/bin/i386$' \ grep -v '^\./usr/bin/i386$' \
| grep -v '^\./usr/bin/x86_64$' \ | grep -v '^\./usr/bin/x86_64$' \
| grep -v '^\./lib32$' \ | grep -v '^\./lib32$' \
| grep -v '^\./lib64$' \ | grep -v '^\./lib64$' \

View file

@ -6,7 +6,7 @@
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
prefix= prefix=
include= include=,
if [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != root ] && [ "{{ MODE }}" != auto ]; then if [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != root ] && [ "{{ MODE }}" != auto ]; then
# this must be qemu # this must be qemu
if ! id -u user >/dev/null 2>&1; then if ! id -u user >/dev/null 2>&1; then
@ -24,11 +24,11 @@ if [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != root ] && [ "{{ MODE }}" != auto ];
sysctl -w kernel.unprivileged_userns_clone=1 sysctl -w kernel.unprivileged_userns_clone=1
fi fi
prefix="runuser -u user --" prefix="runuser -u user --"
if [ "{{ MODE }}" = extract ] || [ "{{ MODE }}" = custom ]; then if [ "{{ VARIANT }}" = extract ] || [ "{{ VARIANT }}" = custom ]; then
include="--include=$(cat pkglist.txt | tr '\n' ',')" include="$(tr '\n' ',' < pkglist.txt)"
fi fi
fi fi
$prefix {{ CMD }} --mode={{ MODE }} $include --dry-run --variant={{ VARIANT }} {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }} $prefix {{ CMD }} --mode={{ MODE }} --include="$include" --dry-run --variant={{ VARIANT }} {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
if [ -e /tmp/debian-chroot.tar ]; then if [ -e /tmp/debian-chroot.tar ]; then
echo "/tmp/debian-chroot.tar must not be created with --dry-run" >&2 echo "/tmp/debian-chroot.tar must not be created with --dry-run" >&2
exit 1 exit 1

View file

@ -117,7 +117,7 @@ END
# use script to create a fake tty # use script to create a fake tty
# run all tests as root and as a normal user (the latter requires ptmxmode=666) # run all tests as root and as a normal user (the latter requires ptmxmode=666)
script -qfc "$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \ script -qfc "$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \
--include=gcc,libc6-dev,python3 \ --include=gcc,libc6-dev,python3,adduser \
--customize-hook='chroot \"\$1\" adduser --gecos user --disabled-password user' \ --customize-hook='chroot \"\$1\" adduser --gecos user --disabled-password user' \
--customize-hook='chroot \"\$1\" python3 -c \"import pty; print(pty.openpty())\"' \ --customize-hook='chroot \"\$1\" python3 -c \"import pty; print(pty.openpty())\"' \
--customize-hook='chroot \"\$1\" runuser -u user -- python3 -c \"import pty; print(pty.openpty())\"' \ --customize-hook='chroot \"\$1\" runuser -u user -- python3 -c \"import pty; print(pty.openpty())\"' \

View file

@ -7,5 +7,5 @@ export LC_ALL=C.UTF-8
trap "rm -f Release; rm -rf /tmp/debian-chroot" EXIT INT TERM trap "rm -f Release; rm -rf /tmp/debian-chroot" EXIT INT TERM
/usr/lib/apt/apt-helper download-file "{{ MIRROR }}/dists/{{ DIST }}/Release" Release /usr/lib/apt/apt-helper download-file "{{ MIRROR }}/dists/{{ DIST }}/Release" Release
codename=$(awk '/^Codename: / { print $2; }' Release) codename=$(awk '/^Codename: / { print $2; }' Release)
{{ CMD }} --mode={{ MODE }} --variant=apt $codename /tmp/debian-chroot {{ MIRROR }} {{ CMD }} --mode={{ MODE }} --variant=apt "$codename" /tmp/debian-chroot {{ MIRROR }}
echo "deb {{ MIRROR }} $codename main" | diff -u - /tmp/debian-chroot/etc/apt/sources.list echo "deb {{ MIRROR }} $codename main" | diff -u - /tmp/debian-chroot/etc/apt/sources.list

View file

@ -22,8 +22,8 @@ apt-get remove --yes qemu-user-static binfmt-support qemu-user
| sed 's/i386/amd64/' \ | sed 's/i386/amd64/' \
| sed 's/\/stubs-32.ph$/\/stubs-64.ph/'; | sed 's/\/stubs-32.ph$/\/stubs-64.ph/';
} | sort > tar2.txt } | sort > tar2.txt
{ cat tar1.txt \ { < tar1.txt \
| grep -v '^\./usr/bin/i386$' \ grep -v '^\./usr/bin/i386$' \
| grep -v '^\./usr/bin/x86_64$' \ | grep -v '^\./usr/bin/x86_64$' \
| grep -v '^\./usr/lib32/$' \ | grep -v '^\./usr/lib32/$' \
| grep -v '^\./lib32$' \ | grep -v '^\./lib32$' \

View file

@ -3,19 +3,24 @@
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
{{ CMD }} --variant=apt \ # instead of obtaining a .deb from our cache, we create a new package because
--customize-hook='mkdir "$1"/tmp/apt' \ # otherwise apt might decide to download the package with the same name and
--customize-hook='chroot "$1" env --chdir=/tmp/apt apt-get download busybox' \ # version from the cache instead of using the local .deb
--customize-hook='copy-out /tmp/apt /tmp' \ mkdir -p /tmp/dummypkg/DEBIAN
{{ DIST }} /dev/null {{ MIRROR }} cat << END > "/tmp/dummypkg/DEBIAN/control"
pkg="$(find /tmp/apt -type f)" Package: dummypkg
# some sanity checks Priority: optional
[ -f "$pkg" ] Section: oldlibs
case $pkg in Maintainer: Johannes Schauer Marin Rodrigues <josch@debian.org>
/tmp/apt/busybox*_{{ HOSTARCH }}.deb) : ;; Architecture: all
*) exit 1;; Multi-Arch: foreign
esac Source: dummypkg
# now try to install that package Version: 1
{{ CMD }} --variant=apt --include="$pkg" \ Description: dummypkg
--customize-hook='chroot "$1" dpkg-query -W -f="\${Status}\n" busybox | grep "^install ok installed$"' \ END
dpkg-deb --build "/tmp/dummypkg" "/tmp/dummypkg.deb"
{{ CMD }} --variant=apt --include="/tmp/dummypkg.deb" \
--hook-dir=./hooks/file-mirror-automount \
--customize-hook='chroot "$1" dpkg-query -W -f="\${Status}\n" dummypkg | grep "^install ok installed$"' \
{{ DIST }} /dev/null {{ MIRROR }} {{ DIST }} /dev/null {{ MIRROR }}

View file

@ -21,13 +21,14 @@ for cmd in echo cat sed grep; do
test -L /tmp/debian-chroot/bin/$cmd test -L /tmp/debian-chroot/bin/$cmd
test "$(readlink /tmp/debian-chroot/bin/$cmd)" = "/bin/busybox" test "$(readlink /tmp/debian-chroot/bin/$cmd)" = "/bin/busybox"
done done
for cmd in sort; do for cmd in sort tee; do
test -L /tmp/debian-chroot/usr/bin/$cmd test -L /tmp/debian-chroot/usr/bin/$cmd
test "$(readlink /tmp/debian-chroot/usr/bin/$cmd)" = "/bin/busybox" test "$(readlink /tmp/debian-chroot/usr/bin/$cmd)" = "/bin/busybox"
done done
chroot /tmp/debian-chroot echo foobar \ chroot /tmp/debian-chroot echo foobar \
| chroot /tmp/debian-chroot cat \ | chroot /tmp/debian-chroot cat \
| chroot /tmp/debian-chroot sort \ | chroot /tmp/debian-chroot sort \
| chroot /tmp/debian-chroot tee /dev/null \
| chroot /tmp/debian-chroot sed 's/foobar/blubber/' \ | chroot /tmp/debian-chroot sed 's/foobar/blubber/' \
| chroot /tmp/debian-chroot grep blubber >/dev/null | chroot /tmp/debian-chroot grep blubber >/dev/null
rm -r /tmp/debian-chroot rm -r /tmp/debian-chroot

View file

@ -11,7 +11,7 @@ fi
prefix= prefix=
[ "$(id -u)" -eq 0 ] && prefix="runuser -u user --" [ "$(id -u)" -eq 0 ] && prefix="runuser -u user --"
$prefix {{ CMD }} --mode=chrootless --variant=custom --include=doc-debian {{ DIST }} /tmp/debian-chroot {{ MIRROR }} $prefix {{ CMD }} --mode=chrootless --variant=custom --include=doc-debian {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
tar -C /tmp/debian-chroot --owner=0 --group=0 --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/debian-chroot.tar . tar -C /tmp/debian-chroot --owner=0 --group=0 --numeric-owner --sort=name --clamp-mtime --mtime="$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds)" -cf /tmp/debian-chroot.tar .
tar tvf /tmp/debian-chroot.tar > doc-debian.tar.list tar tvf /tmp/debian-chroot.tar > doc-debian.tar.list
rm /tmp/debian-chroot.tar rm /tmp/debian-chroot.tar
# delete contents of doc-debian # delete contents of doc-debian

View file

@ -14,7 +14,7 @@ prefix=
$prefix {{ CMD }} --mode=chrootless --skip=cleanup/tmp --variant=custom --include=doc-debian --setup-hook='touch "$1/tmp/setup"' --customize-hook='touch "$1/tmp/customize"' {{ DIST }} /tmp/debian-chroot {{ MIRROR }} $prefix {{ CMD }} --mode=chrootless --skip=cleanup/tmp --variant=custom --include=doc-debian --setup-hook='touch "$1/tmp/setup"' --customize-hook='touch "$1/tmp/customize"' {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
rm /tmp/debian-chroot/tmp/setup rm /tmp/debian-chroot/tmp/setup
rm /tmp/debian-chroot/tmp/customize rm /tmp/debian-chroot/tmp/customize
tar -C /tmp/debian-chroot --owner=0 --group=0 --numeric-owner --sort=name --clamp-mtime --mtime=$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds) -cf /tmp/debian-chroot.tar . tar -C /tmp/debian-chroot --owner=0 --group=0 --numeric-owner --sort=name --clamp-mtime --mtime="$(date --utc --date=@{{ SOURCE_DATE_EPOCH }} --iso-8601=seconds)" -cf /tmp/debian-chroot.tar .
tar tvf /tmp/debian-chroot.tar | grep -v ' ./dev' | diff -u doc-debian.tar.list - tar tvf /tmp/debian-chroot.tar | grep -v ' ./dev' | diff -u doc-debian.tar.list -
rm /tmp/debian-chroot.tar rm /tmp/debian-chroot.tar
# delete contents of doc-debian # delete contents of doc-debian

55
tests/pivot_root Normal file
View file

@ -0,0 +1,55 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
trap "rm -f /tmp/chroot1.tar /tmp/chroot2.tar /tmp/chroot3.tar /tmp/mmdebstrap" EXIT INT TERM
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
adduser --gecos user --disabled-password user
fi
prefix=
[ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && prefix="runuser -u user --"
MMDEBSTRAP=
[ -e /usr/bin/mmdebstrap ] && MMDEBSTRAP=/usr/bin/mmdebstrap
[ -e ./mmdebstrap ] && MMDEBSTRAP=./mmdebstrap
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \
--include=mount \
{{ DIST }} /tmp/chroot1.tar {{ MIRROR }}
if [ {{ MODE }} = "unshare" ]; then
# calling pivot_root in root mode does not work for mysterious reasons:
# pivot_root: failed to change root from `.' to `mnt': Invalid argument
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt --include=mount \
--customize-hook="upload $MMDEBSTRAP /$MMDEBSTRAP" \
--customize-hook='chmod +x "$1"/'"$MMDEBSTRAP" \
--customize-hook='mount -o rbind "$1" /mnt && cd /mnt && /sbin/pivot_root . mnt' \
--customize-hook='unshare -U echo nested unprivileged unshare' \
--customize-hook='{{ CMD }} --mode=unshare --variant=apt --include=mount {{ DIST }} /tmp/chroot3.tar {{ MIRROR }}' \
--customize-hook='copy-out /tmp/chroot3.tar /tmp' \
--customize-hook='rm "$1/'"$MMDEBSTRAP"'"' \
--customize-hook='umount -l mnt sys' \
{{ DIST }} /tmp/chroot2.tar {{ MIRROR }}
cmp /tmp/chroot1.tar /tmp/chroot2.tar
cmp /tmp/chroot1.tar /tmp/chroot3.tar
rm /tmp/chroot2.tar /tmp/chroot3.tar
fi
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt --include=mount \
--customize-hook="upload $MMDEBSTRAP /$MMDEBSTRAP" \
--customize-hook='chmod +x "$1"/'"$MMDEBSTRAP" \
--chrooted-customize-hook='{{ CMD }} --mode=unshare --variant=apt --include=mount {{ DIST }} /tmp/chroot3.tar {{ MIRROR }}' \
--customize-hook='copy-out /tmp/chroot3.tar /tmp' \
--customize-hook='rm "$1/'"$MMDEBSTRAP"'"' \
{{ DIST }} /tmp/chroot2.tar {{ MIRROR }}
cmp /tmp/chroot1.tar /tmp/chroot2.tar
cmp /tmp/chroot1.tar /tmp/chroot3.tar

View file

@ -1,12 +1,13 @@
#!/bin/sh #!/bin/sh
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
setsid --wait {{ CMD }} --mode=root --variant=apt --customize-hook='touch done && sleep 10 && touch fail' {{ DIST }} /tmp/debian-chroot {{ MIRROR }} & setsid --wait {{ CMD }} --mode=root --variant=apt --customize-hook='touch hookstarted && sleep 10 && touch fail' {{ DIST }} /tmp/debian-chroot {{ MIRROR }} &
pid=$! pid=$!
while sleep 1; do [ -e done ] && break; done while sleep 1; do [ -e hookstarted ] && break; done
rm done rm hookstarted
pgid=$(echo $(ps -p $pid -o pgid=)) # negative PID values choose the whole process group
/bin/kill --signal INT -- -$pgid pgid=$((-1*$(ps -p "$pid" -o pgid=)))
/bin/kill --signal INT -- "$pgid"
ret=0 ret=0
wait $pid || ret=$? wait $pid || ret=$?
rm -r /tmp/debian-chroot rm -r /tmp/debian-chroot

View file

@ -7,9 +7,10 @@ ln -s /real /tmp/root/link
mkdir /tmp/root/real mkdir /tmp/root/real
run_testA() { run_testA() {
echo content > /tmp/foo echo content > /tmp/foo
{ { { {{ CMD }} --hook-helper /tmp/root root setup env 1 upload /tmp/foo $1 < /tmp/myfifo 3>&-; echo $? >&3; printf "\\000\\000adios"; # shellcheck disable=SC2094
{ { { {{ CMD }} --hook-helper /tmp/root root setup env 1 upload /tmp/foo "$1" < /tmp/myfifo 3>&-; echo $? >&3; printf "\\000\\000adios";
} | {{ CMD }} --hook-listener 1 3>&- >/tmp/myfifo; echo $?; } 3>&1; } | {{ CMD }} --hook-listener 1 3>&- >/tmp/myfifo; echo $?; } 3>&1;
} | { read xs1; [ "$xs1" -eq 0 ]; read xs2; [ "$xs2" -eq 0 ]; } } | { read -r xs1; [ "$xs1" -eq 0 ]; read -r xs2; [ "$xs2" -eq 0 ]; }
echo content | diff -u - /tmp/root/real/foo echo content | diff -u - /tmp/root/real/foo
rm /tmp/foo rm /tmp/foo
rm /tmp/root/real/foo rm /tmp/root/real/foo

View file

@ -3,4 +3,4 @@ set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
# we redirect to /dev/null instead of using --quiet to not cause a broken pipe # we redirect to /dev/null instead of using --quiet to not cause a broken pipe
# when grep exits before mmdebstrap was able to write all its output # when grep exits before mmdebstrap was able to write all its output
{{ CMD }} --version | egrep '^mmdebstrap [0-9](\.[0-9])+$' >/dev/null {{ CMD }} --version | grep -E '^mmdebstrap [0-9](\.[0-9])+$' >/dev/null