Compare commits

...

11 Commits

@ -1,3 +1,10 @@
0.8.3 (2022-01-08)
------------------
- allow codenames with apt patterns (requires apt >= 2.3.14)
- don't overwrite existing files in setup code
- don't copy in qemu-user-static binary if it's not needed
0.8.2 (2021-12-14) 0.8.2 (2021-12-14)
------------------ ------------------

@ -127,7 +127,7 @@ if [ ! -e shared/hooks/eatmydata/customize.sh ] || [ hooks/eatmydata/customize.s
fi fi
fi fi
starttime= starttime=
total=177 total=182
skipped=0 skipped=0
runtests=0 runtests=0
i=1 i=1
@ -533,6 +533,34 @@ else
runtests=$((runtests+1)) runtests=$((runtests+1))
fi fi
# make sure that using codenames works https://bugs.debian.org/cgi-bin/1003191
for dist in oldstable stable testing unstable; do
print_header "mode=$defaultmode,variant=apt: test $dist using codename"
cat << END > shared/test.sh
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
/usr/lib/apt/apt-helper download-file "$mirror/dists/$dist/Release" Release
codename=\$(awk '/^Codename: / { print \$2; }' Release)
rm Release
$CMD --mode=$defaultmode --variant=apt \$codename /tmp/debian-chroot.tar $mirror
if [ "$dist" = "$DEFAULT_DIST" ]; then
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
fi
rm /tmp/debian-chroot.tar
END
if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh
runtests=$((runtests+1))
elif [ "$defaultmode" = "root" ]; then
./run_null.sh SUDO
runtests=$((runtests+1))
else
./run_null.sh
runtests=$((runtests+1))
fi
done
print_header "mode=unshare,variant=apt: fail without /etc/subuid" print_header "mode=unshare,variant=apt: fail without /etc/subuid"
cat << END > shared/test.sh cat << END > shared/test.sh
#!/bin/sh #!/bin/sh
@ -1988,6 +2016,35 @@ else
skipped=$((skipped+1)) skipped=$((skipped+1))
fi fi
print_header "mode=root,variant=apt: test ascii armored keys"
cat << END > shared/test.sh
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
for f in /usr/share/keyrings/*.gpg; do
name=\$(basename "\$f" .gpg)
gpg --enarmor < /usr/share/keyrings/\$name.gpg \
| sed 's/ PGP ARMORED FILE/ PGP PUBLIC KEY BLOCK/;/^Comment: /d' \
> /etc/apt/trusted.gpg.d/\$name.asc
done
rm /etc/apt/trusted.gpg.d/*.gpg
rm /usr/share/keyrings/*.gpg
$CMD --mode=root --variant=apt $DEFAULT_DIST /tmp/debian-chroot.tar $mirror
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot.tar
END
if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh
runtests=$((runtests+1))
else
echo "HAVE_QEMU != yes -- Skipping test..." >&2
skipped=$((skipped+1))
fi
print_header "mode=root,variant=apt: test signed-by with host keys" print_header "mode=root,variant=apt: test signed-by with host keys"
cat << END > shared/test.sh cat << END > shared/test.sh
#!/bin/sh #!/bin/sh

@ -80,6 +80,9 @@ deletecache() {
rm "$dir/debian$i" rm "$dir/debian$i"
done done
rm "$dir/mmdebstrapcache" rm "$dir/mmdebstrapcache"
# remove all symlinks
find "$dir" -type l -delete
# now the rest should only be empty directories # now the rest should only be empty directories
if [ -e "$dir" ]; then if [ -e "$dir" ]; then
find "$dir" -depth -print0 | xargs -0 --no-run-if-empty rmdir find "$dir" -depth -print0 | xargs -0 --no-run-if-empty rmdir
@ -270,11 +273,14 @@ END
curl --location "$mirror/dists/$dist/Release" > "$newmirrordir/dists/$dist/Release" curl --location "$mirror/dists/$dist/Release" > "$newmirrordir/dists/$dist/Release"
curl --location "$mirror/dists/$dist/Release.gpg" > "$newmirrordir/dists/$dist/Release.gpg" curl --location "$mirror/dists/$dist/Release.gpg" > "$newmirrordir/dists/$dist/Release.gpg"
curl --location "$mirror/dists/$dist/main/binary-$nativearch/Packages.xz" > "$newmirrordir/dists/$dist/main/binary-$nativearch/Packages.xz" curl --location "$mirror/dists/$dist/main/binary-$nativearch/Packages.xz" > "$newmirrordir/dists/$dist/main/binary-$nativearch/Packages.xz"
codename=$(awk '/^Codename: / { print $2; }' < "$newmirrordir/dists/$dist/Release")
[ -L "$newmirrordir/dists/$codename" ] || ln -s "$dist" "$newmirrordir/dists/$codename"
case "$dist" in oldstable|stable) case "$dist" in oldstable|stable)
mkdir -p "$newmirrordir/dists/$dist-updates/main/binary-$nativearch/" mkdir -p "$newmirrordir/dists/$dist-updates/main/binary-$nativearch/"
curl --location "$mirror/dists/$dist-updates/Release" > "$newmirrordir/dists/$dist-updates/Release" curl --location "$mirror/dists/$dist-updates/Release" > "$newmirrordir/dists/$dist-updates/Release"
curl --location "$mirror/dists/$dist-updates/Release.gpg" > "$newmirrordir/dists/$dist-updates/Release.gpg" curl --location "$mirror/dists/$dist-updates/Release.gpg" > "$newmirrordir/dists/$dist-updates/Release.gpg"
curl --location "$mirror/dists/$dist-updates/main/binary-$nativearch/Packages.xz" > "$newmirrordir/dists/$dist-updates/main/binary-$nativearch/Packages.xz" curl --location "$mirror/dists/$dist-updates/main/binary-$nativearch/Packages.xz" > "$newmirrordir/dists/$dist-updates/main/binary-$nativearch/Packages.xz"
[ -L "$newmirrordir/dists/$codename-updates" ] || ln -s "$dist-updates" "$newmirrordir/dists/$codename-updates"
;; ;;
esac esac
case "$dist" in case "$dist" in
@ -289,6 +295,7 @@ END
curl --location "$security_mirror/dists/$dist-security/Release" > "$newcachedir/debian-security/dists/$dist-security/Release" curl --location "$security_mirror/dists/$dist-security/Release" > "$newcachedir/debian-security/dists/$dist-security/Release"
curl --location "$security_mirror/dists/$dist-security/Release.gpg" > "$newcachedir/debian-security/dists/$dist-security/Release.gpg" curl --location "$security_mirror/dists/$dist-security/Release.gpg" > "$newcachedir/debian-security/dists/$dist-security/Release.gpg"
curl --location "$security_mirror/dists/$dist-security/main/binary-$nativearch/Packages.xz" > "$newcachedir/debian-security/dists/$dist-security/main/binary-$nativearch/Packages.xz" curl --location "$security_mirror/dists/$dist-security/main/binary-$nativearch/Packages.xz" > "$newcachedir/debian-security/dists/$dist-security/main/binary-$nativearch/Packages.xz"
[ -L "$newcachedir/debian-security/dists/$codename-security" ] || ln -s "$dist-security" "$newcachedir/debian-security/dists/$codename-security"
;; ;;
esac esac

@ -23,7 +23,7 @@
use strict; use strict;
use warnings; use warnings;
our $VERSION = '0.8.2'; our $VERSION = '0.8.3';
use English; use English;
use Getopt::Long; use Getopt::Long;
@ -102,7 +102,13 @@ my @devfiles = (
# 3 -> debug output # 3 -> debug output
my $verbosity_level = 1; my $verbosity_level = 1;
my $is_covering = !!(eval { Devel::Cover::get_coverage() }); my $is_covering = 0;
{
# make $@ local, so we don't print "Undefined subroutine called"
# in other parts where we evaluate $@
local $@ = '';
$is_covering = !!(eval { Devel::Cover::get_coverage() });
}
# the reason why Perl::Critic warns about this is, that it suspects that the # the reason why Perl::Critic warns about this is, that it suspects that the
# programmer wants to implement a test whether the terminal is interactive or # programmer wants to implement a test whether the terminal is interactive or
@ -1657,7 +1663,7 @@ sub run_setup() {
# from inside the chroot. # from inside the chroot.
# The config filename is chosen such that any settings in it will be # The config filename is chosen such that any settings in it will be
# overridden by what the user specified with --aptopt. # overridden by what the user specified with --aptopt.
{ if (!-e "$options->{root}/etc/apt/apt.conf.d/00mmdebstrap") {
open my $fh, '>', "$options->{root}/etc/apt/apt.conf.d/00mmdebstrap" open my $fh, '>', "$options->{root}/etc/apt/apt.conf.d/00mmdebstrap"
or error "cannot open /etc/apt/apt.conf.d/00mmdebstrap: $!"; or error "cannot open /etc/apt/apt.conf.d/00mmdebstrap: $!";
print $fh "Apt::Install-Recommends false;\n"; print $fh "Apt::Install-Recommends false;\n";
@ -1666,7 +1672,7 @@ sub run_setup() {
} }
# apt-get update requires this # apt-get update requires this
{ if (!-e "$options->{root}/var/lib/dpkg/status") {
open my $fh, '>', "$options->{root}/var/lib/dpkg/status" open my $fh, '>', "$options->{root}/var/lib/dpkg/status"
or error "failed to open(): $!"; or error "failed to open(): $!";
close $fh; close $fh;
@ -1678,9 +1684,11 @@ sub run_setup() {
# architecture outside the chroot. # architecture outside the chroot.
chomp(my $hostarch = `dpkg --print-architecture`); chomp(my $hostarch = `dpkg --print-architecture`);
if ( if (
scalar @{ $options->{foreignarchs} } > 0 (!-e "$options->{root}/var/lib/dpkg/arch")
or ( $options->{mode} eq 'chrootless' and (
and $hostarch ne $options->{nativearch}) scalar @{ $options->{foreignarchs} } > 0
or ( $options->{mode} eq 'chrootless'
and $hostarch ne $options->{nativearch}))
) { ) {
open my $fh, '>', "$options->{root}/var/lib/dpkg/arch" open my $fh, '>', "$options->{root}/var/lib/dpkg/arch"
or error "cannot open /var/lib/dpkg/arch: $!"; or error "cannot open /var/lib/dpkg/arch: $!";
@ -1691,7 +1699,8 @@ sub run_setup() {
close $fh; close $fh;
} }
if (scalar @{ $options->{aptopts} } > 0) { if (scalar @{ $options->{aptopts} } > 0
and (!-e "$options->{root}/etc/apt/apt.conf.d/99mmdebstrap")) {
open my $fh, '>', "$options->{root}/etc/apt/apt.conf.d/99mmdebstrap" open my $fh, '>', "$options->{root}/etc/apt/apt.conf.d/99mmdebstrap"
or error "cannot open /etc/apt/apt.conf.d/99mmdebstrap: $!"; or error "cannot open /etc/apt/apt.conf.d/99mmdebstrap: $!";
foreach my $opt (@{ $options->{aptopts} }) { foreach my $opt (@{ $options->{aptopts} }) {
@ -1717,7 +1726,8 @@ sub run_setup() {
} }
} }
if (scalar @{ $options->{dpkgopts} } > 0) { if (scalar @{ $options->{dpkgopts} } > 0
and (!-e "$options->{root}/etc/dpkg/dpkg.cfg.d/99mmdebstrap")) {
# FIXME: in chrootless mode, dpkg will only read the configuration # FIXME: in chrootless mode, dpkg will only read the configuration
# from the host -- see #808203 # from the host -- see #808203
if ($options->{mode} eq 'chrootless') { if ($options->{mode} eq 'chrootless') {
@ -1747,7 +1757,7 @@ sub run_setup() {
} }
} }
{ if (!-e "$options->{root}/etc/fstab") {
open my $fh, '>', "$options->{root}/etc/fstab" open my $fh, '>', "$options->{root}/etc/fstab"
or error "cannot open fstab: $!"; or error "cannot open fstab: $!";
print $fh "# UNCONFIGURED FSTAB FOR BASE SYSTEM\n"; print $fh "# UNCONFIGURED FSTAB FOR BASE SYSTEM\n";
@ -1787,9 +1797,11 @@ sub run_setup() {
$fname .= 'main.sources'; $fname .= 'main.sources';
} }
} }
open my $fh, '>', "$fname" or error "cannot open $fname: $!"; if (!-e $fname) {
print $fh $firstentry->{content}; open my $fh, '>', "$fname" or error "cannot open $fname: $!";
close $fh; print $fh $firstentry->{content};
close $fh;
}
# everything else goes into /etc/apt/sources.list.d/ # everything else goes into /etc/apt/sources.list.d/
for (my $i = 1 ; $i < scalar @{ $options->{sourceslists} } ; $i++) { for (my $i = 1 ; $i < scalar @{ $options->{sourceslists} } ; $i++) {
my $entry = $options->{sourceslists}->[$i]; my $entry = $options->{sourceslists}->[$i];
@ -1816,15 +1828,17 @@ sub run_setup() {
error "invalid type: $entry->{type}"; error "invalid type: $entry->{type}";
} }
} }
open my $fh, '>', "$fname" or error "cannot open $fname: $!"; if (!-e $fname) {
print $fh $entry->{content}; open my $fh, '>', "$fname" or error "cannot open $fname: $!";
close $fh; print $fh $entry->{content};
close $fh;
}
} }
} }
# allow network access from within # allow network access from within
foreach my $file ("/etc/resolv.conf", "/etc/hostname") { foreach my $file ("/etc/resolv.conf", "/etc/hostname") {
if (-e $file) { if (-e $file && !-e "$options->{root}/$file") {
# this will create a new file with 644 permissions and copy # this will create a new file with 644 permissions and copy
# contents only even if $file was a symlink # contents only even if $file was a symlink
copy($file, "$options->{root}/$file") copy($file, "$options->{root}/$file")
@ -1945,15 +1959,6 @@ sub run_setup() {
} }
} }
# setting PATH for chroot, ldconfig, start-stop-daemon...
if (length $ENV{PATH}) {
## no critic (Variables::RequireLocalizedPunctuationVars)
$ENV{PATH} = "$ENV{PATH}:/usr/sbin:/usr/bin:/sbin:/bin";
} else {
## no critic (Variables::RequireLocalizedPunctuationVars)
$ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin";
}
return; return;
} }
@ -2150,7 +2155,10 @@ sub run_download() {
'?narrow(' '?narrow('
. ( . (
length($options->{suite}) length($options->{suite})
? '?archive(' . $options->{suite} . '),' ? '?or(?archive(^'
. $options->{suite}
. '$),?codename(^'
. $options->{suite} . '$)),'
: '' : ''
) )
. '?architecture(' . '?architecture('
@ -2506,19 +2514,41 @@ sub run_prepare {
$ENV{QEMU_LD_PREFIX} = $options->{root}; $ENV{QEMU_LD_PREFIX} = $options->{root};
} }
} elsif (any { $_ eq $options->{mode} } ('root', 'unshare')) { } elsif (any { $_ eq $options->{mode} } ('root', 'unshare')) {
# other modes require a static qemu-user binary my $require_qemu_static = 1;
my $qemubin = "/usr/bin/qemu-$options->{qemu}-static"; # make $@ local, so we don't print an eventual error
if (!-e $qemubin) { # in other parts where we evaluate $@
error "cannot find $qemubin"; local $@ = '';
eval {
# Check for the F flag which makes the kernel open the binfmt
# binary at configuration time instead of lazily at startup
# time. If the flag is set, then the qemu-static binary is not
# required inside the chroot.
open my $fh, '<',
"/proc/sys/fs/binfmt_misc/qemu-$options->{qemu}";
while (my $line = <$fh>) {
chomp($line);
if ($line =~ /^flags: [A-Z]*F[A-Z]*$/) {
$require_qemu_static = 0;
last;
}
}
close $fh;
};
if ($require_qemu_static) {
# other modes require a static qemu-user binary
my $qemubin = "/usr/bin/qemu-$options->{qemu}-static";
if (!-e $qemubin) {
error "cannot find $qemubin";
}
copy $qemubin, "$options->{root}/$qemubin"
or error "cannot copy $qemubin: $!";
# File::Copy does not retain permissions but on some
# platforms (like Travis CI) the binfmt interpreter must
# have the executable bit set or otherwise execve will
# fail with EACCES
chmod 0755, "$options->{root}/$qemubin"
or error "cannot chmod $qemubin: $!";
} }
copy $qemubin, "$options->{root}/$qemubin"
or error "cannot copy $qemubin: $!";
# File::Copy does not retain permissions but on some
# platforms (like Travis CI) the binfmt interpreter must
# have the executable bit set or otherwise execve will
# fail with EACCES
chmod 0755, "$options->{root}/$qemubin"
or error "cannot chmod $qemubin: $!";
} else { } else {
error "unknown mode: $options->{mode}"; error "unknown mode: $options->{mode}";
} }
@ -2704,7 +2734,10 @@ sub run_install() {
"?narrow(" "?narrow("
. ( . (
length($options->{suite}) length($options->{suite})
? '?archive(' . $options->{suite} . '),' ? '?or(?archive(^'
. $options->{suite}
. '$),?codename(^'
. $options->{suite} . '$)),'
: '' : ''
) )
. "?architecture($options->{nativearch})," . "?architecture($options->{nativearch}),"
@ -2864,8 +2897,11 @@ sub run_cleanup() {
or error "failed to unlink $ENV{APT_CONFIG}: $!"; or error "failed to unlink $ENV{APT_CONFIG}: $!";
} }
if (defined $options->{qemu} if (any { $_ eq 'cleanup/mmdebstrap/qemu' } @{ $options->{skip} }) {
and any { $_ eq $options->{mode} } ('root', 'unshare')) { info "skipping cleanup/mmdebstrap/qume as requested";
} elsif (defined $options->{qemu}
and any { $_ eq $options->{mode} } ('root', 'unshare')
and -e "$options->{root}/usr/bin/qemu-$options->{qemu}-static") {
unlink "$options->{root}/usr/bin/qemu-$options->{qemu}-static" unlink "$options->{root}/usr/bin/qemu-$options->{qemu}-static"
or error or error
"cannot unlink /usr/bin/qemu-$options->{qemu}-static: $!"; "cannot unlink /usr/bin/qemu-$options->{qemu}-static: $!";
@ -3915,19 +3951,25 @@ 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;
my $distro_info = '/usr/share/distro-info/debian.csv'; if (
any { $_ eq $suite }
('stable', 'bullseye', 'bookworm', 'trixie')
) {
$bullseye_or_later = 1;
}
my $distro_info = '/usr/share/distro-info/debian.csv';
# make $@ local, so we don't print "Can't locate Debian/DistroInfo.pm" # make $@ local, so we don't print "Can't locate Debian/DistroInfo.pm"
# in other parts where we evaluate $@ # in other parts where we evaluate $@
local $@ = ''; local $@ = '';
eval { require Debian::DistroInfo; }; eval { require Debian::DistroInfo; };
if (!$@) { if (!$@) {
# libdistro-info-perl is installed debug "libdistro-info-perl is installed";
my $debinfo = DebianDistroInfo->new(); my $debinfo = DebianDistroInfo->new();
if ($debinfo->version($suite, 0) >= 11) { if ($debinfo->version($suite, 0) >= 11) {
$bullseye_or_later = 1; $bullseye_or_later = 1;
} }
} elsif (-f $distro_info) { } elsif (-f $distro_info) {
# distro-info-data is installed debug "distro-info-data is installed";
open my $fh, '<', $distro_info open my $fh, '<', $distro_info
or error "cannot open $distro_info: $!"; or error "cannot open $distro_info: $!";
my $i = 0; my $i = 0;
@ -3974,13 +4016,7 @@ sub get_sourceslist_by_suite {
$bullseye_or_later = 1; $bullseye_or_later = 1;
} }
} else { } else {
# neither libdistro-info-perl nor distro-info-data is installed debug "neither libdistro-info-perl nor distro-info-data installed";
if (
any { $_ eq $suite }
('stable', 'bullseye', 'bookworm', 'trixie')
) {
$bullseye_or_later = 1;
}
} }
if ($bullseye_or_later) { if ($bullseye_or_later) {
# starting from bullseye use # starting from bullseye use
@ -4314,7 +4350,11 @@ sub main() {
error "invalid format. Choose from " . (join ', ', @valid_formats); error "invalid format. Choose from " . (join ', ', @valid_formats);
} }
if (!defined $ENV{PATH} || $ENV{PATH} eq "") { # setting PATH for chroot, ldconfig, start-stop-daemon...
if (length $ENV{PATH}) {
## no critic (Variables::RequireLocalizedPunctuationVars)
$ENV{PATH} = "$ENV{PATH}:/usr/sbin:/usr/bin:/sbin:/bin";
} else {
## no critic (Variables::RequireLocalizedPunctuationVars) ## no critic (Variables::RequireLocalizedPunctuationVars)
$ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin"; $ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin";
} }
@ -4374,8 +4414,8 @@ sub main() {
and $content =~ /^apt ([0-9]+\.[0-9]+\.[0-9]+) \([a-z0-9-]+\)$/m) { and $content =~ /^apt ([0-9]+\.[0-9]+\.[0-9]+) \([a-z0-9-]+\)$/m) {
$aptversion = version->new($1); $aptversion = version->new($1);
} }
if ($aptversion < "2.3.10") { if ($aptversion < "2.3.14") {
error "need apt >= 2.3.10 but have $aptversion"; error "need apt >= 2.3.14 but have $aptversion";
} }
} }
@ -5723,7 +5763,12 @@ sub main() {
if ($@) { if ($@) {
# we cannot die here because that would leave the other thread # we cannot die here because that would leave the other thread
# running without a parent # running without a parent
# We send SIGHUP to all our processes (including eventually
# running tar and this process itself) to reliably tear down
# all running child processes. The main process is not affected
# because we are ignoring SIGHUP.
warning "creating tarball failed: $@"; warning "creating tarball failed: $@";
kill HUP => -getpgrp();
$exitstatus = 1; $exitstatus = 1;
} }
} else { } else {
@ -5804,9 +5849,11 @@ sub main() {
if ($exitstatus == 0) { if ($exitstatus == 0) {
my $duration = Time::HiRes::time - $before; my $duration = Time::HiRes::time - $before;
info "success in " . (sprintf "%.04f", $duration) . " seconds"; info "success in " . (sprintf "%.04f", $duration) . " seconds";
exit 0;
} }
exit $exitstatus; error "mmdebstrap failed to run";
return 1;
} }
main(); main();
@ -6546,7 +6593,9 @@ chroot as I<fileinside>. In contrast to B<copy-in>, this command only
handles files and not directories. To copy a directory recursively into the handles files and not directories. To copy a directory recursively into the
chroot, use B<copy-in> or B<tar-in>. Its advantage is, that by being able to chroot, use B<copy-in> or B<tar-in>. Its advantage is, that by being able to
specify the full path on the inside, including the filename, the file on the specify the full path on the inside, including the filename, the file on the
inside can have a different name from the file on the outside. inside can have a different name from the file on the outside. In contrast to
B<copy-in> and B<tar-in>, permission and ownership information will not be
retained.
=back =back

Loading…
Cancel
Save