Compare commits

...

33 commits

Author SHA1 Message Date
abcfda0442
add example for mmdebstrap-autopkgtest-build-qemu 2023-10-23 11:56:15 +02:00
63d5ffb2a6
reflow paragraph 2023-10-23 11:55:53 +02:00
a1e5043676
add example on how to remove /etc/resolv.conf and /etc/hostname to make the chroot reproducible 2023-10-23 11:55:35 +02:00
ecc167e87e
add example for using debvm-create and debvm-run 2023-10-23 11:54:12 +02:00
2d758ba576
add example on how to add additional apt sources 2023-10-23 11:52:28 +02:00
16c7276921
be more verbose when sending SIGHUP to the process group 2023-10-23 11:51:41 +02:00
1a62ccec46
do not run genext2fs if $numblocks value is invalid 2023-10-23 11:51:14 +02:00
d02ea1c7f1
hooklistener: print errors as warning and not just as debug messages 2023-10-23 11:50:09 +02:00
ce12fbdd41
hooklistener: process error messages 2023-10-23 11:49:14 +02:00
aef8fcfb75
add comment explaining why dpkg-deb --extract still cannot be used 2023-10-23 11:48:32 +02:00
cb500ef6ba
add --skip=tar-in/mknod,copy-in/mknod,sync-in/mknod 2023-10-23 11:47:27 +02:00
c33f719278
coverage.py: print total runtime 2023-10-23 11:43:32 +02:00
84c53fc120
hook-helper: repurpose third arg for skip options now that qemu-user info does not need to be propagated anymore (was only needed for proot) 2023-10-23 10:41:46 +02:00
27d1bad2a5
since debootstrap 1.0.133, the buildd variant only installs apt and not priority:required anymore 2023-10-23 10:38:10 +02:00
261cfda58f
make_mirror.sh: drop disorderfs 2023-10-23 10:35:20 +02:00
629187cd68
set MMDEBSTRAP_FORMAT in hooks 2023-10-23 10:35:07 +02:00
e77e194ebd
only warn if symlink cannot be created, don't error out 2023-10-23 10:33:54 +02:00
bc7ce4affc
skip symlink creation if it already exists 2023-10-23 10:33:37 +02:00
199e577757
tarfilter: add --type-exclude option 2023-10-23 10:26:47 +02:00
21366f76b7
Stop copying qemu-$arch-static binary into the chroot
- since qemu-user binfmt has support for pre-opening the interpreter
   since stretch, there is no reason to copy it into the chroot anymore
2023-09-28 07:53:48 +02:00
bf41b91e6f
Properly implement --skip=output/dev and add --skip=output/mknod
- the first implementation of --skip=output/dev was broken in root mode
 - add tests
 - add documentation
2023-09-28 07:53:43 +02:00
cee8b67045
clarify, that deb.d.o is only used if SUITE is not a stable release 2023-09-27 08:03:23 +02:00
669c404938
typos: qume->qemu, SYS_CAP_ADMIN->CAP_SYS_ADMIN 2023-09-27 08:00:15 +02:00
767fa11571
mmdebstrap-autopkgtest-build-qemu: add reference to old grub+guestfish implementation 2023-09-27 07:58:53 +02:00
c23727e136
export container=mmdebstrap-unshare environment variable in unshare-mode hooks 2023-09-27 07:56:49 +02:00
eaa67aea9c
fixup comment indentation 2023-09-27 07:52:35 +02:00
6068e7d22e
tarfilter: fixup comment 2023-09-27 07:49:04 +02:00
8339721fca
make_mirror.sh: also use bash and wget to check for the proxy running 2023-09-27 07:47:08 +02:00
e5bcc1827e
make_mirror.sh: print success message at the end 2023-09-27 07:44:56 +02:00
806ea4b35d
make_mirror.sh: support FORCE_UPDATE=yes 2023-09-27 07:42:12 +02:00
8bf8da5e8e
coverage.txt: verbose and debug tests rely on variant:standard chroots 2023-09-27 07:41:26 +02:00
2c5e6db317
mmdebstrap-autopkgtest-build-qemu: add POD manual 2023-09-19 12:56:08 +02:00
c741711938
mmdebstrap-autopkgtest-build-qemu: make executable 2023-09-19 12:55:56 +02:00
13 changed files with 656 additions and 230 deletions

View file

@ -446,6 +446,10 @@ def main():
print("failed %d:" % len(failed), file=sys.stderr)
for f in failed:
print(f, file=sys.stderr)
currenttime = time.time()
walltime = timedelta(seconds=int(currenttime - starttime))
print(f"total runtime: {walltime}", file=sys.stderr)
if failed:
exit(1)

View file

@ -277,12 +277,14 @@ Variants: - standard
Skip-If:
variant == "-" and hostarch not in ["armel", "armhf", "mipsel"] # #1031276
variant == "standard" and hostarch in ["armel", "armhf", "mipsel"] # #1031276
variant == "standard" and dist == "oldstable" # #864082, #1004557, #1004558
Test: debug
Variants: - standard
Skip-If:
variant == "-" and hostarch not in ["armel", "armhf", "mipsel"] # #1031276
variant == "standard" and hostarch in ["armel", "armhf", "mipsel"] # #1031276
variant == "standard" and dist == "oldstable" # #864082, #1004557, #1004558
Test: quiet
Needs-Root: true
@ -420,3 +422,12 @@ Skip-If: hostarch in ["i386", "armel", "armhf", "mipsel"] # #1023286
Test: auto-mode-as-normal-user
Modes: auto
Test: skip-output-dev
Modes: root unshare
Test: skip-output-mknod
Modes: root unshare
Test: skip-tar-in-mknod
Modes: unshare

View file

@ -250,6 +250,17 @@ END
trap "-" EXIT INT TERM
)
check_proxy_running() {
if timeout 1 bash -c 'exec 3<>/dev/tcp/127.0.0.1/8080 && printf "GET http://deb.debian.org/debian/dists/'"$DEFAULT_DIST"'/InRelease HTTP/1.1\nHost: deb.debian.org\n\n" >&3 && grep "Suite: '"$DEFAULT_DIST"'" <&3 >/dev/null' 2>/dev/null; then
return 0
elif timeout 1 env http_proxy="http://127.0.0.1:8080/" wget --quiet -O - "http://deb.debian.org/debian/dists/$DEFAULT_DIST/InRelease" | grep "Suite: $DEFAULT_DIST" >/dev/null; then
return 0
elif timeout 1 curl --proxy "http://127.0.0.1:8080/" --silent "http://deb.debian.org/debian/dists/$DEFAULT_DIST/InRelease" | grep "Suite: $DEFAULT_DIST" >/dev/null; then
return 0
fi
return 1
}
if [ -e "./shared/cache.A" ] && [ -e "./shared/cache.B" ]; then
echo "both ./shared/cache.A and ./shared/cache.B exist" >&2
echo "was a former run of the script aborted?" >&2
@ -301,8 +312,9 @@ components=main
# by default, use the mmdebstrap executable in the current directory
: "${CMD:=./mmdebstrap}"
: "${USE_HOST_APT_CONFIG:=no}"
: "${FORCE_UPDATE:=no}"
if [ -e "$oldmirrordir/dists/$DEFAULT_DIST/InRelease" ]; then
if [ "$FORCE_UPDATE" != "yes" ] && [ -e "$oldmirrordir/dists/$DEFAULT_DIST/InRelease" ]; then
http_code=$(curl --output /dev/null --silent --location --head --time-cond "$oldmirrordir/dists/$DEFAULT_DIST/InRelease" --write-out '%{http_code}' "$mirror/dists/$DEFAULT_DIST/InRelease")
case "$http_code" in
200) ;; # need update
@ -316,7 +328,7 @@ PROXYPID=$!
trap 'kill "$PROXYPID" || :' EXIT INT TERM
for i in $(seq 10); do
curl --proxy "http://127.0.0.1:8080/" --silent -o /dev/null "http://deb.debian.org/debian/dists/$DEFAULT_DIST/InRelease" && break
check_proxy_running && break
sleep 1
done
if [ ! -s "$newmirrordir/dists/$DEFAULT_DIST/InRelease" ]; then
@ -425,7 +437,7 @@ if [ "$HAVE_QEMU" = "yes" ]; then
PROXYPID=$!
for i in $(seq 10); do
curl --proxy "http://127.0.0.1:8080/" --silent -o /dev/null "http://deb.debian.org/debian/dists/$DEFAULT_DIST/InRelease" && break
check_proxy_running && break
sleep 1
done
if [ ! -s "$newmirrordir/dists/$DEFAULT_DIST/InRelease" ]; then
@ -437,7 +449,7 @@ if [ "$HAVE_QEMU" = "yes" ]; then
tmpdir="$(mktemp -d)"
trap 'kill "$PROXYPID" || :;cleanuptmpdir; cleanup_newcachedir' EXIT INT TERM
pkgs=perl-doc,systemd-sysv,perl,arch-test,fakechroot,fakeroot,mount,uidmap,qemu-user-static,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,disorderfs,squashfs-tools-ng,genext2fs,linux-image-generic
pkgs=perl-doc,systemd-sysv,perl,arch-test,fakechroot,fakeroot,mount,uidmap,qemu-user-static,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,squashfs-tools-ng,genext2fs,linux-image-generic
if [ ! -e ./mmdebstrap ]; then
pkgs="$pkgs,mmdebstrap"
fi
@ -549,3 +561,5 @@ mv --no-target-directory ./shared/cache.tmp ./shared/cache
deletecache "$oldcachedir"
trap - EXIT INT TERM
echo "$0 finished successfully" >&2

View file

@ -1170,12 +1170,18 @@ sub setup_mounts {
);
next;
}
if (-e "$options->{root}/dev/$fname") {
warning(
"skipping creation of ./dev/$fname because it"
. " already exists in the target");
next;
}
push @cleanup_tasks, sub {
unlink "$options->{root}/dev/$fname"
or warning("cannot unlink ./dev/$fname: $!");
};
symlink $linkname, "$options->{root}/dev/$fname"
or error
or warning
"cannot create symlink ./dev/$fname -> $linkname";
}
} elsif ($type == 3 or $type == 4) {
@ -1266,8 +1272,9 @@ sub setup_mounts {
next;
}
if (!$options->{havemknod}) {
# If had mknod, then the directory to bind-mount into
# was already created in the run_setup function.
# If had mknod, then the directory to bind-mount
# into was already created in the run_setup
# function.
push @cleanup_tasks, sub {
rmdir "$options->{root}/dev/$fname"
or warning("cannot rmdir ./dev/$fname: $!");
@ -1309,17 +1316,17 @@ sub setup_mounts {
or warning("umount ./dev/$fname failed: $?");
};
if ($fname eq "pts/") {
# We cannot just bind-mount /dev/pts from the host as
# doing so will make posix_openpt() fail. Instead, we
# need to mount a new devpts.
# We need ptmxmode=666 because /dev/ptmx is a symlink
# to /dev/pts/ptmx and without it posix_openpt() will
# fail if we are not the root user.
# See also:
# kernel.org/doc/Documentation/filesystems/devpts.txt
# salsa.debian.org/debian/schroot/-/merge_requests/2
# https://bugs.debian.org/856877
# https://bugs.debian.org/817236
# We cannot just bind-mount /dev/pts from the host
# as doing so will make posix_openpt() fail.
# Instead, we need to mount a new devpts.
# We need ptmxmode=666 because /dev/ptmx is a
# symlink to /dev/pts/ptmx and without it
# posix_openpt() will fail if we are not the root
# user. See also:
# kernel.o/doc/Documentation/filesystems/devpts.txt
# salsa.d.o/debian/schroot/-/merge_requests/2
# https://bugs.debian.org/856877
# https://bugs.debian.org/817236
0 == system(
'mount',
'-t',
@ -1623,6 +1630,7 @@ sub run_hooks {
if (defined $options->{suite}) {
push @env_opts, "MMDEBSTRAP_SUITE=$options->{suite}";
}
push @env_opts, "MMDEBSTRAP_FORMAT=$options->{format}";
# Storing the hook name is important for hook scripts to potentially change
# their behavior depending on the hook. It's also important for when the
# hook wants to use the mmdebstrap --hook-helper.
@ -1653,6 +1661,9 @@ sub run_hooks {
push @env_opts,
("MMDEBSTRAP_ESSENTIAL=" . (join " ", @{$essential_pkgs}));
}
if ($options->{mode} eq 'unshare') {
push @env_opts, "container=mmdebstrap-unshare";
}
# Unset the close-on-exec flag, so that the file descriptor does not
# get closed when we exec
@ -1744,7 +1755,8 @@ sub run_hooks {
# does for python
my @args = shellwords $script;
hookhelper($options->{root}, $options->{mode}, $name,
$options->{qemu}, $verbosity_level, @args);
(join ',', @{ $options->{skip} }),
$verbosity_level, @args);
exit 0;
}
waitpid($pid, 0);
@ -2589,6 +2601,8 @@ sub run_extract() {
}
# not using dpkg-deb --extract as that would replace the
# merged-usr symlinks with plain directories
# even after switching from pre-merging to post-merging, dpkg-deb
# will ignore filter rules from dpkg.cfg.d
# https://bugs.debian.org/989602
# not using dpkg --unpack because that would try running preinst
# maintainer scripts
@ -2768,90 +2782,49 @@ sub run_prepare {
error "unknown mode: $options->{mode}";
}
# copy qemu-user-static binary into chroot
if (defined $options->{qemu}) {
if ($options->{mode} eq 'fakechroot') {
# Make sure that the fakeroot and fakechroot shared
# libraries exist for the right architecture
open my $fh, '-|', 'dpkg-architecture', '-a',
$options->{nativearch},
'-qDEB_HOST_MULTIARCH' // error "failed to fork(): $!";
chomp(
my $deb_host_multiarch = do { local $/; <$fh> }
);
close $fh;
if (($? != 0) or (!$deb_host_multiarch)) {
error "dpkg-architecture failed: $?";
}
my $fakechrootdir = "/usr/lib/$deb_host_multiarch/fakechroot";
if (!-e "$fakechrootdir/libfakechroot.so") {
error "$fakechrootdir/libfakechroot.so doesn't exist."
. " Install libfakechroot:$options->{nativearch}"
. " outside the chroot";
}
my $fakerootdir = "/usr/lib/$deb_host_multiarch/libfakeroot";
if (!-e "$fakerootdir/libfakeroot-sysv.so") {
error "$fakerootdir/libfakeroot-sysv.so doesn't exist."
. " Install libfakeroot:$options->{nativearch}"
. " outside the chroot";
}
# The rest of this block sets environment variables, so we
# have to add the "no critic" statement to stop perlcritic
# from complaining about setting global variables
## no critic (Variables::RequireLocalizedPunctuationVars)
# fakechroot only fills LD_LIBRARY_PATH with the
# directories of the host's architecture. We append the
# directories of the chroot architecture.
$ENV{LD_LIBRARY_PATH}
= "$ENV{LD_LIBRARY_PATH}:$fakechrootdir:$fakerootdir";
# The binfmt support on the outside is used, so qemu needs
# to know where it has to look for shared libraries
if (defined $ENV{QEMU_LD_PREFIX}
&& $ENV{QEMU_LD_PREFIX} ne "") {
$ENV{QEMU_LD_PREFIX} = "$ENV{QEMU_LD_PREFIX}:$options->{root}";
} else {
$ENV{QEMU_LD_PREFIX} = $options->{root};
}
} elsif (any { $_ eq $options->{mode} } ('root', 'unshare')) {
my $require_qemu_static = 1;
# make $@ local, so we don't print an eventual error
# in other parts where we evaluate $@
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.
if (-e "/proc/sys/fs/binfmt_misc/qemu-$options->{qemu}") {
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: $!";
}
# foreign architecture setup for fakechroot mode
if (defined $options->{qemu} && $options->{mode} eq 'fakechroot') {
# Make sure that the fakeroot and fakechroot shared libraries exist for
# the right architecture
open my $fh, '-|', 'dpkg-architecture', '-a',
$options->{nativearch},
'-qDEB_HOST_MULTIARCH' // error "failed to fork(): $!";
chomp(
my $deb_host_multiarch = do { local $/; <$fh> }
);
close $fh;
if (($? != 0) or (!$deb_host_multiarch)) {
error "dpkg-architecture failed: $?";
}
my $fakechrootdir = "/usr/lib/$deb_host_multiarch/fakechroot";
if (!-e "$fakechrootdir/libfakechroot.so") {
error "$fakechrootdir/libfakechroot.so doesn't exist."
. " Install libfakechroot:$options->{nativearch}"
. " outside the chroot";
}
my $fakerootdir = "/usr/lib/$deb_host_multiarch/libfakeroot";
if (!-e "$fakerootdir/libfakeroot-sysv.so") {
error "$fakerootdir/libfakeroot-sysv.so doesn't exist."
. " Install libfakeroot:$options->{nativearch}"
. " outside the chroot";
}
# The rest of this block sets environment variables, so we have to add
# the "no critic" statement to stop perlcritic from complaining about
# setting global variables
## no critic (Variables::RequireLocalizedPunctuationVars)
# fakechroot only fills LD_LIBRARY_PATH with the directories of the
# host's architecture. We append the directories of the chroot
# architecture.
$ENV{LD_LIBRARY_PATH}
= "$ENV{LD_LIBRARY_PATH}:$fakechrootdir:$fakerootdir";
# The binfmt support on the outside is used, so qemu needs to know
# where it has to look for shared libraries
if (defined $ENV{QEMU_LD_PREFIX}
&& $ENV{QEMU_LD_PREFIX} ne "") {
$ENV{QEMU_LD_PREFIX} = "$ENV{QEMU_LD_PREFIX}:$options->{root}";
} else {
error "unknown mode: $options->{mode}";
$ENV{QEMU_LD_PREFIX} = $options->{root};
}
}
@ -2992,15 +2965,15 @@ sub run_install() {
my @pkgs_to_install = (@{ $options->{include} });
if ($options->{variant} eq 'buildd') {
push @pkgs_to_install, 'build-essential';
push @pkgs_to_install, 'build-essential', 'apt';
}
if (any { $_ eq $options->{variant} }
('required', 'important', 'standard', 'buildd')) {
('required', 'important', 'standard')) {
# Many of the priority:required packages are also essential:yes. We
# make sure not to select those here to avoid useless "xxx is already
# the newest version" messages.
my $priority;
if (any { $_ eq $options->{variant} } ('required', 'buildd')) {
if (any { $_ eq $options->{variant} } ('required')) {
$priority = '?and(?priority(required),?not(?essential))';
} elsif ($options->{variant} eq 'important') {
$priority = '?and(?or(?priority(required),?priority(important)),'
@ -3169,7 +3142,7 @@ sub run_cleanup() {
}
if (any { $_ eq 'cleanup/mmdebstrap/qemu' } @{ $options->{skip} }) {
info "skipping cleanup/mmdebstrap/qume as requested";
info "skipping cleanup/mmdebstrap/qemu as requested";
} elsif (defined $options->{qemu}
and any { $_ eq $options->{mode} } ('root', 'unshare')
and -e "$options->{root}/usr/bin/qemu-$options->{qemu}-static") {
@ -3258,6 +3231,52 @@ sub run_cleanup() {
closedir($dh);
}
}
if (any { $_ eq 'cleanup/dev' } @{ $options->{skip} }) {
info "skipping cleanup/dev as requested";
} else {
# By default, tar is run with --exclude=./dev because we create the
# ./dev entries ourselves using @devfiles. But if --skip=output/dev is
# used, --exclude=./dev is not passed so that the chroot includes ./dev
# as created by base-files. But if mknod was available (for example
# when running as root) then ./dev will also include the @devfiles
# entries created by run_setup() and thus the resulting tarball will
# include things inside ./dev despite the user having supplied
# --skip=output/dev. So if --skip=output/dev was passed and if a
# tarball is to be created, we need to make sure to clean up the
# ./dev entries that were created in run_setup(). This is not done
# when creating a directory because in that case we want to do the
# same as debootstrap and create a directory including device nodes.
if ($options->{format} ne 'directory' && any { $_ eq 'output/dev' }
@{ $options->{skip} }) {
foreach my $file (@devfiles) {
my ($fname, $mode, $type, $linkname, $devmajor, $devminor)
= @{$file};
if (!-e "$options->{root}/dev/$fname") {
next;
}
# do not remove ./dev itself
if ($fname eq "") {
next;
}
if ($type == 0) { # normal file
error "type 0 not implemented";
} elsif ($type == 1) { # hardlink
error "type 1 not implemented";
} elsif (any { $_ eq $type } (2, 3, 4))
{ # symlink, char, block
unlink "$options->{root}/dev/$fname"
or error "failed to unlink ./dev/$fname: $!";
} elsif ($type == 5) { # directory
rmdir "$options->{root}/dev/$fname"
or error "failed to unlink ./dev/$fname: $!";
} else {
error "unsupported type: $type";
}
}
}
}
return;
}
@ -3340,8 +3359,20 @@ sub pivot_root {
}
sub hookhelper {
my ($root, $mode, $hook, $qemu, $verbosity, $command, @args) = @_;
my ($root, $mode, $hook, $skipopt, $verbosity, $command, @args) = @_;
$verbosity_level = $verbosity;
my @skipopts = ();
if (length $skipopt) {
for my $skip (split /[,\s]+/, $skipopt) {
# strip leading and trailing whitespace
$skip =~ s/^\s+|\s+$//g;
# skip if the remainder is an empty string
if ($skip eq '') {
next;
}
push @skipopts, $skip;
}
}
# 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
# has to send an "error" message to the other side
@ -3430,8 +3461,48 @@ sub hookhelper {
@cmdprefix, @tarcmd, '--xattrs-include=*',
'--directory', $directory, '--extract', '--file', '-'
);
debug("helper: running " . (join " ", @cmd));
open($fh, '|-', @cmd) // error "failed to fork(): $!";
# go via mmtarfilter if copy-in/mknod, tar-in/mknod or
# sync-in/mknod were part of the skip options
if (any { $_ eq "$command/mknod" } @skipopts) {
info "skipping $command/mknod as requested";
my $tarfilter = "mmtarfilter";
if (-x "./tarfilter") {
$tarfilter = "./tarfilter";
}
pipe my $filter_reader, $fh or error "pipe failed: $!";
pipe my $tar_reader, my $filter_writer
or error "pipe failed: $!";
my $pid1 = fork() // error "fork() failed: $!";
if ($pid1 == 0) {
open(STDIN, '<&', $filter_reader)
or error "cannot open STDIN: $!";
open(STDOUT, '>&', $filter_writer)
or error "cannot open STDOUT: $!";
close($tar_reader)
or error "cannot close tar_reader: $!";
debug(
"helper: running $tarfilter --type-exclude=3 "
. "--type-exclude=4");
eval { Devel::Cover::set_coverage("none") }
if $is_covering;
exec $tarfilter, '--type-exclude=3',
'--type-exclude=4';
}
my $pid2 = fork() // error "fork() failed: $!";
if ($pid2 == 0) {
open(STDIN, '<&', $tar_reader)
or error "cannot open STDIN: $!";
close($filter_writer)
or error "cannot close filter_writer: $!";
debug("helper: running " . (join " ", @cmd));
eval { Devel::Cover::set_coverage("none") }
if $is_covering;
exec { $cmd[0] } @cmd;
}
} else {
debug("helper: running " . (join " ", @cmd));
open($fh, '|-', @cmd) // error "failed to fork(): $!";
}
} else {
error "unknown command: $command";
}
@ -3951,13 +4022,15 @@ sub hooklistener {
if ($? != 0) {
error "tar failed";
}
} elsif ($msg eq "error") {
error "received error on socket";
} else {
error "unknown message: $msg";
}
}
};
if ($@) {
debug("hooklistener errored out: $@");
warning("hooklistener errored out: $@");
# inform the other side that something went wrong
print STDOUT (pack("n", 0) . "error")
or error "cannot write to socket: $!";
@ -4441,6 +4514,7 @@ sub main() {
include => [],
architectures => [$hostarch],
mode => 'auto',
format => 'auto',
dpkgopts => [],
aptopts => [],
apttrusted => $apttrusted,
@ -4454,7 +4528,6 @@ sub main() {
skip => [],
};
my $logfile = undef;
my $format = 'auto';
Getopt::Long::Configure('default', 'bundling', 'auto_abbrev',
'ignore_case_always');
GetOptions(
@ -4537,7 +4610,7 @@ sub main() {
'q|quiet' => sub { $verbosity_level = 0; },
'v|verbose' => sub { $verbosity_level = 2; },
'd|debug' => sub { $verbosity_level = 3; },
'format=s' => \$format,
'format=s' => \$options->{format},
'logfile=s' => \$logfile,
# no-op options so that mmdebstrap can be used with
# sbuild-createchroot --debootstrap=mmdebstrap
@ -4704,16 +4777,16 @@ sub main() {
}
# sqfs is an alias for squashfs
if ($format eq 'sqfs') {
$format = 'squashfs';
if ($options->{format} eq 'sqfs') {
$options->{format} = 'squashfs';
}
# dir is an alias for directory
if ($format eq 'dir') {
$format = 'directory';
if ($options->{format} eq 'dir') {
$options->{format} = 'directory';
}
my @valid_formats
= ('auto', 'directory', 'tar', 'squashfs', 'ext2', 'null');
if (none { $_ eq $format } @valid_formats) {
if (none { $_ eq $options->{format} } @valid_formats) {
error "invalid format. Choose from " . (join ', ', @valid_formats);
}
@ -5480,7 +5553,7 @@ sub main() {
my $tar_compressor = get_tar_compressor($options->{target});
# figure out the right format
if ($format eq 'auto') {
if ($options->{format} eq 'auto') {
# (stat(...))[6] is the device identifier which contains the major and
# minor numbers for character special files
# major 1 and minor 3 is /dev/null on Linux
@ -5489,7 +5562,7 @@ sub main() {
and -c '/dev/null'
and major((stat("/dev/null"))[6]) == 1
and minor((stat("/dev/null"))[6]) == 3) {
$format = 'null';
$options->{format} = 'null';
} elsif ($options->{target} eq '-'
and $OSNAME eq 'linux'
and major((stat(STDOUT))[6]) == 1
@ -5497,9 +5570,9 @@ sub main() {
# by checking the major and minor number of the STDOUT fd we also
# can detect redirections to /dev/null and choose the null format
# accordingly
$format = 'null';
$options->{format} = 'null';
} elsif ($options->{target} ne '-' and -d $options->{target}) {
$format = 'directory';
$options->{format} = 'directory';
} elsif (
defined $tar_compressor
or $options->{target} =~ /\.tar$/
@ -5507,7 +5580,7 @@ sub main() {
or -p $options->{target} # named pipe (fifo)
or -c $options->{target} # character special like /dev/null
) {
$format = 'tar';
$options->{format} = 'tar';
# check if the compressor is installed
if (defined $tar_compressor) {
my $pid = fork() // error "fork() failed: $!";
@ -5527,7 +5600,7 @@ sub main() {
}
}
} elsif ($options->{target} =~ /\.(squashfs|sqfs)$/) {
$format = 'squashfs';
$options->{format} = 'squashfs';
# check if tar2sqfs is installed
my $pid = fork() // error "fork() failed: $!";
if ($pid == 0) {
@ -5543,7 +5616,7 @@ sub main() {
error("failed to start tar2sqfs --version");
}
} elsif ($options->{target} =~ /\.ext2$/) {
$format = 'ext2';
$options->{format} = 'ext2';
# check if the installed version of genext2fs supports tarballs on
# stdin
(undef, my $filename) = tempfile(
@ -5564,32 +5637,34 @@ sub main() {
error "genext2fs failed with exit status: $exitstatus";
}
} else {
$format = 'directory';
$options->{format} = 'directory';
}
info "automatically chosen format: $format";
info "automatically chosen format: $options->{format}";
}
if ($options->{target} eq '-' and $format ne 'tar' and $format ne 'null') {
error "the $format format is unable to write to standard output";
if ( $options->{target} eq '-'
and $options->{format} ne 'tar'
and $options->{format} ne 'null') {
error "the $options->{format} format is unable to write to stdout";
}
if ($format eq 'null'
if ($options->{format} eq 'null'
and none { $_ eq $options->{target} } ('-', '/dev/null')) {
info "ignoring target $options->{target} with null format";
}
if ($format eq 'ext2') {
if ($options->{format} eq 'ext2') {
if (!can_execute 'genext2fs') {
error "need genext2fs for ext2 format";
}
} elsif ($format eq 'squashfs') {
} elsif ($options->{format} eq 'squashfs') {
if (!can_execute 'tar2sqfs') {
error "need tar2sqfs binary from the squashfs-tools-ng package";
}
}
if (any { $_ eq $format } ('tar', 'squashfs', 'ext2', 'null')) {
if ($format ne 'null') {
if (any { $_ eq $options->{format} } ('tar', 'squashfs', 'ext2', 'null')) {
if ($options->{format} ne 'null') {
if (any { $_ eq $options->{variant} } ('extract', 'custom')
and $options->{mode} eq 'fakechroot') {
info "creating a tarball or squashfs image or ext2 image in"
@ -5626,7 +5701,7 @@ sub main() {
) {
chmod 0755, $options->{root} or error "cannot chmod root: $!";
}
} elsif ($format eq 'directory') {
} elsif ($options->{format} eq 'directory') {
# user does not seem to have specified a tarball as output, thus work
# directly in the supplied directory
$options->{root} = $options->{target};
@ -5682,7 +5757,7 @@ sub main() {
}
}
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
# check for double quotes because apt doesn't allow to escape them and
@ -5766,15 +5841,28 @@ sub main() {
error "unknown mode: $options->{mode}";
}
# If a tarball is to be created, we always (except if --skip=output/dev is
# passed) craft the /dev entries ourselves.
# Why do we put /dev entries in the final tarball?
# - because debootstrap does it
# - because schroot (#856877) and pbuilder rely on it and we care about
# Debian buildds (using schroot) and reproducible builds infra (using
# pbuilder)
# If both the above assertion change, we can stop creating /dev entries as
# well.
my $devtar = '';
# We always craft the /dev entries ourselves if a tarball is to be created
if (any { $_ eq $format } ('tar', 'squashfs', 'ext2')) {
if (any { $_ eq $options->{format} } ('tar', 'squashfs', 'ext2')) {
foreach my $file (@devfiles) {
my ($fname, $mode, $type, $linkname, $devmajor, $devminor)
= @{$file};
if (length "./dev/$fname" > 100) {
error "tar entry cannot exceed 100 characters";
}
if ($type == 3
and any { $_ eq 'output/mknod' } @{ $options->{skip} }) {
info "skipping output/mknod as requested for ./dev/$fname";
next;
}
my $entry = pack(
'a100 a8 a8 a8 a12 a12 A8 a1 a100 a8 a32 a32 a8 a8 a155 x12',
"./dev/$fname",
@ -5798,10 +5886,10 @@ sub main() {
= sprintf("%06o\0", unpack("%16C*", $entry));
$devtar .= $entry;
}
} elsif (any { $_ eq $format } ('directory', 'null')) {
} elsif (any { $_ eq $options->{format} } ('directory', 'null')) {
# nothing to do
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
my $exitstatus = 0;
@ -5814,11 +5902,14 @@ sub main() {
'--format=pax',
'--pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime',
'-c',
'--exclude=./dev',
'--exclude=./lost+found'
);
# only exclude ./dev if device nodes are written out (the default)
if (none { $_ eq 'output/dev' } @{ $options->{skip} }) {
push @taropts, '--exclude=./dev';
}
# tar2sqfs and genext2fs do not support extended attributes
if ($format eq "squashfs") {
if ($options->{format} eq "squashfs") {
# tar2sqfs supports user.*, trusted.* and security.* but not system.*
# https://bugs.debian.org/988100
# lib/sqfs/xattr/xattr.c of https://github.com/AgentD/squashfs-tools-ng
@ -5827,7 +5918,7 @@ sub main() {
warning("tar2sqfs does not support extended attributes"
. " from the 'system' namespace");
push @taropts, '--xattrs', '--xattrs-exclude=system.*';
} elsif ($format eq "ext2") {
} elsif ($options->{format} eq "ext2") {
warning "genext2fs does not support extended attributes";
} else {
push @taropts, '--xattrs';
@ -5879,7 +5970,7 @@ sub main() {
close $childsock;
close $nblkreader;
if (!$options->{dryrun} && $format eq 'ext2') {
if (!$options->{dryrun} && $options->{format} eq 'ext2') {
my $numblocks = approx_disk_usage($options->{root});
print $nblkwriter "$numblocks\n";
$nblkwriter->flush();
@ -5888,7 +5979,8 @@ sub main() {
if ($options->{dryrun}) {
info "simulate creating tarball...";
} elsif (any { $_ eq $format } ('tar', 'squashfs', 'ext2')) {
} elsif (any { $_ eq $options->{format} }
('tar', 'squashfs', 'ext2')) {
info "creating tarball...";
# redirect tar output to the writing end of the pipe so
@ -5909,10 +6001,11 @@ sub main() {
or error "tar failed: $?";
info "done";
} elsif (any { $_ eq $format } ('directory', 'null')) {
} elsif (any { $_ eq $options->{format} }
('directory', 'null')) {
# nothing to do
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
exit 0;
@ -5944,7 +6037,7 @@ sub main() {
close $childsock;
close $nblkreader;
if (!$options->{dryrun} && $format eq 'ext2') {
if (!$options->{dryrun} && $options->{format} eq 'ext2') {
my $numblocks = approx_disk_usage($options->{root});
print $nblkwriter $numblocks;
$nblkwriter->flush();
@ -5953,7 +6046,8 @@ sub main() {
if ($options->{dryrun}) {
info "simulate creating tarball...";
} elsif (any { $_ eq $format } ('tar', 'squashfs', 'ext2')) {
} elsif (any { $_ eq $options->{format} }
('tar', 'squashfs', 'ext2')) {
info "creating tarball...";
# redirect tar output to the writing end of the pipe so that
@ -6001,10 +6095,10 @@ sub main() {
}
info "done";
} elsif (any { $_ eq $format } ('directory', 'null')) {
} elsif (any { $_ eq $options->{format} } ('directory', 'null')) {
# nothing to do
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
exit 0;
@ -6062,7 +6156,7 @@ sub main() {
my $numblocks = 0;
close $nblkwriter;
if (!$options->{dryrun} && $format eq 'ext2') {
if (!$options->{dryrun} && $options->{format} eq 'ext2') {
$numblocks = <$nblkreader>;
if (defined $numblocks) {
chomp $numblocks;
@ -6079,9 +6173,11 @@ sub main() {
if ($options->{dryrun}) {
# nothing to do
} elsif (any { $_ eq $format } ('directory', 'null')) {
} elsif (any { $_ eq $options->{format} } ('directory', 'null')) {
# nothing to do
} elsif (any { $_ eq $format } ('tar', 'squashfs', 'ext2')) {
} elsif ($options->{format} eq 'ext2' && $numblocks <= 0) {
# nothing to do because of invalid $numblocks
} elsif (any { $_ eq $options->{format} } ('tar', 'squashfs', 'ext2')) {
# we use eval() so that error() doesn't take this process down and
# thus leaves the setup() process without a parent
eval {
@ -6090,27 +6186,27 @@ sub main() {
error "cannot copy to standard output: $!";
}
} else {
if ( $format eq 'squashfs'
or $format eq 'ext2'
if ( $options->{format} eq 'squashfs'
or $options->{format} eq 'ext2'
or defined $tar_compressor) {
my @argv = ();
if ($format eq 'squashfs') {
if ($options->{format} eq 'squashfs') {
push @argv, 'tar2sqfs',
'--quiet', '--no-skip', '--force',
'--exportable',
'--compressor', 'xz',
'--block-size', '1048576',
$options->{target};
} elsif ($format eq 'ext2') {
} elsif ($options->{format} eq 'ext2') {
if ($numblocks <= 0) {
error "invalid number of blocks: $numblocks";
}
push @argv, 'genext2fs', '-B', 1024, '-b', $numblocks,
'-i', '16384', '-a', '-', $options->{target};
} elsif ($format eq 'tar') {
} elsif ($options->{format} eq 'tar') {
push @argv, @{$tar_compressor};
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
POSIX::sigprocmask(SIG_BLOCK, $sigset)
or error "Can't block signals: $!";
@ -6128,15 +6224,16 @@ sub main() {
or error "Can't unblock signals: $!";
# redirect stdout to file or /dev/null
if ($format eq 'squashfs' or $format eq 'ext2') {
if ( $options->{format} eq 'squashfs'
or $options->{format} eq 'ext2') {
open(STDOUT, '>', '/dev/null')
or error "cannot open /dev/null for writing: $!";
} elsif ($format eq 'tar') {
} elsif ($options->{format} eq 'tar') {
open(STDOUT, '>', $options->{target})
or error
"cannot open $options->{target} for writing: $!";
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
open(STDIN, '<&', $rfh)
or error "cannot open file handle for reading: $!";
@ -6168,12 +6265,34 @@ sub main() {
# 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.
#
# FIXME: this codepath becomes dangerous in case mmdebstrap is not
# run in its own process group. When run from the terminal, the
# shell creates a new process group as part of its job control, so
# sending SIGHUP to all processes in our own process group should
# not be dangerous. But for example, on debci, lxc will run in the
# same process group as mmdebstrap and sending SIGHUP to the whole
# process group will also kill lxc. Creating a new process group
# for $pid will break things because only the foreground job is
# allowed to read from the terminal. If a background job does it,
# i will be suspended with SIGTTIN. Even though apt could be told
# to not read from the terminal by opening STDIN from /dev/null,
# this would make --chrooted-customize-hook=bash impossible.
# Making the $pid process group the foreground job will destroy all
# the signal handling we have set up for when the user presses
# ctrl+c in a terminal. Even if we fix the signal handling we now
# find ourselves in the opposite situation: the $pid process must
# now clean up the former main process tree reliably. And we cannot
# create a new process group for everything all-in-one because that
# would also destroy CTRL+C handling from the terminal.
warning "creating tarball failed: $@";
kill HUP => -getpgrp();
my $pgroup = getpgrp();
warning "sending SIGHUP to all processes in process group $pgroup";
kill HUP => -$pgroup;
$exitstatus = 1;
}
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
close($rfh);
waitpid $pid, 0;
@ -6184,9 +6303,10 @@ sub main() {
# change signal handler message
$waiting_for = "cleanup";
if (any { $_ eq $format } ('directory')) {
if (any { $_ eq $options->{format} } ('directory')) {
# nothing to do
} elsif (any { $_ eq $format } ('tar', 'squashfs', 'ext2', 'null')) {
} elsif (any { $_ eq $options->{format} }
('tar', 'squashfs', 'ext2', 'null')) {
if (!-e $options->{root}) {
error "$options->{root} does not exist";
}
@ -6234,7 +6354,7 @@ sub main() {
error "unknown mode: $options->{mode}";
}
} else {
error "unknown format: $format";
error "unknown format: $options->{format}";
}
if ($got_signal) {
@ -6272,24 +6392,24 @@ dependencies and is thus able to use more than one mirror and resolve more
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 I<SUITE> is a stable release name and no I<MIRROR> is specified, then
mirrors for updates and security are automatically added. If a I<MIRROR>
option starts with "deb " or "deb-src " then it is used as a one-line-style
format entry for apt's sources.list inside the chroot. If a I<MIRROR> option
contains a "://" then it is interpreted as a mirror URI and the apt line
inside the chroot is assembled as "deb [arch=A] B C D" where A is the host's
native architecture, B is the I<MIRROR>, C is the given I<SUITE> and D is the
components given via B<--components> (defaults to "main"). If a I<MIRROR>
option happens to be an existing file, then its contents are pasted into the
chroot's sources.list. This can be used to supply a deb822 style
sources.list. If I<MIRROR> is C<-> then standard input is pasted into the
chroot's sources.list. More than one mirror can be specified and are appended
to the chroot's sources.list in the given order. If you specify a https or tor
I<MIRROR> and you want the chroot to be able to update itself, don't forget to
also install the ca-certificates package, the apt-transport-https package for
apt versions less than 1.5 and/or the apt-transport-tor package using the
B<--include> option, as necessary.
If no I<MIRROR> option is provided and I<SUITE> is not a stable release name,
L<http://deb.debian.org/debian> is used. If I<SUITE> is a stable release name
and no I<MIRROR> is specified, then mirrors for updates and security are
automatically added. If a I<MIRROR> option starts with "deb " or "deb-src "
then it is used as a one-line-style format entry for apt's sources.list inside
the chroot. If a I<MIRROR> option contains a "://" then it is interpreted as a
mirror URI and the apt line inside the chroot is assembled as "deb [arch=A] B C
D" where A is the host's native architecture, B is the I<MIRROR>, C is the
given I<SUITE> and D is the components given via B<--components> (defaults to
"main"). If a I<MIRROR> option happens to be an existing file, then its
contents are pasted into the chroot's sources.list. This can be used to supply
a deb822 style sources.list. If I<MIRROR> is C<-> then standard input is pasted
into the chroot's sources.list. More than one mirror can be specified and are
appended to the chroot's sources.list in the given order. If you specify a
https or tor I<MIRROR> and you want the chroot to be able to update itself,
don't forget to also install the ca-certificates package, the
apt-transport-https package for apt versions less than 1.5 and/or the
apt-transport-tor package using the B<--include> option, as necessary.
The optional I<TARGET> argument can either be the path to a directory, the path
to a tarball filename, the path to a squashfs image, the path to an ext2 image,
@ -6573,6 +6693,10 @@ installed. At that point, the chroot directory does not contain any
executables and thus cannot be chroot-ed into. See section B<HOOKS> for more
information.
Example: add additional apt sources entries on top of the default ones:
--setup-hook='echo "deb http..." > "$1"/etc/apt/sources.list.d/custom.list'
Example: Setup chroot for installing a sub-essential busybox-based chroot
with --variant=custom
--include=dpkg,busybox,libc-bin,base-files,base-passwd,debianutils
@ -6623,15 +6747,23 @@ Execute arbitrary I<command>s after the chroot is set up and all packages got
installed but before final cleanup actions are carried out. See section
B<HOOKS> for more information.
Example: Preparing a chroot for use with autopkgtest
Example: Add a user without a password
--customize-hook='chroot "$1" passwd --delete root'
--customize-hook='chroot "$1" useradd --home-dir /home/user
--create-home user'
--customize-hook='chroot "$1" passwd --delete user'
Example: set up F</etc/hostname> and F</etc/hosts>
--customize-hook='echo host > "$1/etc/hostname"'
--customize-hook='echo "127.0.0.1 localhost host" > "$1/etc/hosts"'
--customize-hook=/usr/share/autopkgtest/setup-commands/setup-testbed
Example: to mimic B<debootstrap> behaviour, B<mmdebstrap> copies from the host.
Remove them in a B<--customize-hook> to make the chroot reproducible across
multiple hosts:
--customize-hook='rm "$1"/etc/resolv.conf'
--customize-hook='rm "$1"/etc/hostname'
=item B<--hook-directory>=I<directory>
@ -6726,7 +6858,7 @@ This mode directly executes chroot and is the same mode of operation as is
used by debootstrap. It is the only mode that can directly create a directory
chroot with the right permissions. If the chroot directory is not accessible
by the _apt user, then apt sandboxing will be automatically disabled. This mode
needs to be able to mount and thus requires C<SYS_CAP_ADMIN>.
needs to be able to mount and thus requires C<CAP_SYS_ADMIN>.
=item B<unshare>
@ -6967,9 +7099,9 @@ to all four of them. Any information specific to each hook is documented under
the specific hook options in the section B<OPTIONS>.
The options can be specified multiple times and the commands are executed in
the order in which they are given on the command line. There are four
different types of hook option arguments. If the argument passed to the hook
option starts with C<copy-in>, C<copy-out>, C<tar-in>, C<tar-out>, C<upload> or
the order in which they are given on the command line. There are four different
types of hook option arguments. If the argument passed to the hook option
starts with C<copy-in>, C<copy-out>, C<tar-in>, C<tar-out>, C<upload> or
C<download> followed by a space, then the hook is interpreted as a special
hook. Otherwise, if I<command> is an existing executable file from C<$PATH> or
if I<command> does not contain any shell metacharacters, then I<command> is
@ -6982,11 +7114,12 @@ C<APT_CONFIG> as written by mmdebstrap it can be found in the
C<MMDEBSTRAP_APT_CONFIG> environment variable. All environment variables set by
the user are preserved, except for C<TMPDIR> which is cleared. See section
B<TMPDIR>. Furthermore, C<MMDEBSTRAP_MODE> will store the mode set by
B<--mode>, C<MMDEBSTRAP_HOOK> stores which hook is currently run (setup,
extract, essential, customize), C<MMDEBSTRAP_ARGV0> stores the name of the
binary with which B<mmdebstrap> was executed and C<MMDEBSTRAP_VERBOSITY> stores
the numerical verbosity level (0 for no output, 1 for normal, 2 for verbose and
3 for debug output). The C<MMDEBSTRAP_INCLUDE> variable stores the list of
B<--mode>, C<MMDEBSTRAP_FORMAT> stores the format chosen by B<--format>,
C<MMDEBSTRAP_HOOK> stores which hook is currently run (setup, extract,
essential, customize), C<MMDEBSTRAP_ARGV0> stores the name of the binary with
which B<mmdebstrap> was executed and C<MMDEBSTRAP_VERBOSITY> stores the
numerical verbosity level (0 for no output, 1 for normal, 2 for verbose and 3
for debug output). The C<MMDEBSTRAP_INCLUDE> variable stores the list of
packages, apt patterns or file paths given by the B<--include> option,
separated by a comma and with commas and percent signs in the option values
urlencoded. If I<SUITE> name was supplied, it's stored in C<MMDEBSTRAP_SUITE>.
@ -7035,7 +7168,9 @@ only update the content of an existing directory.
=item B<tar-in> I<outside.tar> I<pathinside>
Unpacks a tarball I<outside.tar> from outside the chroot into a certain
location I<pathinside> inside the chroot.
location I<pathinside> inside the chroot. In B<unshare> mode, device nodes
cannot be created. To ignore device nodes in tarballs, use
B<--skip=tar-in/mknod>.
=item B<tar-out> I<pathinside> I<outside.tar>
@ -7248,10 +7383,21 @@ Performs cleanup tasks, unless B<--skip=cleanup> is used:
=back
=item B<output>
For formats other than B<directory>, pack up the temporary chroot directory
into a tarball, ext2 image or squashfs image and delete the temporary chroot
directory.
If B<--skip=output/dev> is added, the resulting chroot will not contain the
device nodes, directories and symlinks that B<debootstrap> creates but just
an empty /dev as created by B<base-files>.
If B<--skip=output/mknod> is added, the resulting chroot will not contain
device nodes (neither block nor character special devices). This is useful
if the chroot tarball is to be exatracted in environments where mknod does
not function like in unshared user namespaces.
=back
=head1 EXAMPLES
@ -7379,7 +7525,14 @@ sources on the current system:
--wildcards './usr/src/libdvd-pkg/libdvdcss2_*_*.deb'
$ ls libdvdcss2_*_*.deb
Use as replacement for autopkgtest-build-qemu and vmdb2:
Use as replacement for autopkgtest-build-qemu and vmdb2 for all architectures
supporting EFI booting (amd64, arm64, armhf, i386, riscv64), use a convenience
wrapper around B<mmdebstrap>:
$ mmdebstrap-autopkgtest-build-qemu unstable ./autopkgtest.img
Use as replacement for autopkgtest-build-qemu and vmdb2 on architectures
supporting extlinux (amd64 and i386):
$ mmdebstrap --variant=important --include=linux-image-amd64 \
--customize-hook='chroot "$1" passwd --delete root' \
@ -7411,7 +7564,7 @@ As a debootstrap wrapper to run it without superuser privileges but using Linux
user namespaces instead. This fixes Debian bug #829134.
$ mmdebstrap --variant=custom --mode=unshare \
--setup-hook='env container=lxc debootstrap unstable "$1"' \
--setup-hook='debootstrap unstable "$1"' \
- debian-debootstrap.tar
Build a non-Debian chroot like Ubuntu bionic:
@ -7456,6 +7609,12 @@ Create a system that can be used with docker:
root
$ sudo docker rmi debian
Create and boot a qemu virtual machine for an arbitrary architecture using
the B<debvm-create> wrapper script around B<mmdebstrap>:
$ debvm-create -r stable -- --architecture=riscv64
$ debvm-run
Create a system that can be used with podman:
$ mmdebstrap unstable | podman import - debian
@ -7466,9 +7625,9 @@ Create a system that can be used with podman:
As a docker/podman replacement:
$ mmdebstrap unstable | mmtarfilter --path-exclude='/dev/*' > chroot.tar
$ mmdebstrap unstable chroot.tar
[...]
$ mmdebstrap --variant=custom --skip=update \
$ mmdebstrap --variant=custom --skip=update,tar-in/mknod \
--setup-hook='tar-in chroot.tar /' \
--customize-hook='chroot "$1" whoami' unstable /dev/null
[...]
@ -7481,8 +7640,8 @@ installed, then instead of downloading and installing the essential packages
twice you can instead build on top of the already present minimal chroot:
$ mmdebstrap --variant=apt unstable chroot.tar
$ mmdebstrap --variant=custom --setup-hook \
'mmtarfilter "--path-exclude=/dev/*" < chroot.tar | tar -C "$1" -x' \
$ mmdebstrap --variant=custom --skip=update,setup,cleanup,tar-in/mknod \
--setup-hook='tar-in chroot.tar /' \
--customize-hook='chroot "$1" apt-get install --yes pkg1 pkg2' \
'' chroot-full.tar

109
mmdebstrap-autopkgtest-build-qemu Normal file → Executable file
View file

@ -2,16 +2,111 @@
# Copyright 2023 Johannes Schauer Marin Rodrigues <josch@debian.org>
# Copyright 2023 Helmut Grohne <helmut@subdivi.de>
# SPDX-License-Identifier: MIT
#
# This script is mostly compatible with autopkgtest-build-qemu as shipped in
# autopkgtest. Main differences:
# * It does not support any value for --boot but efi.
# * It uses different tools, most importantly swaps out vmdb2.
# * It can be run as non-root via user namespaces.
# We generally use single quotes to avoid variable expansion:
# shellcheck disable=SC2016
# Replacement for autopkgtest-build-qemu and vmdb2 for all architectures
# supporting EFI booting (amd64, arm64, armhf, i386, riscv64).
# For use as replacement for autopkgtest-build-qemu and vmdb2 on ppc64el which
# neither supports extlinux nor efi booting there is an unmaintained script
# which uses grub instead to boot:
#
# https://gitlab.mister-muffin.de/josch/mmdebstrap/src/commit/
# e523741610a4ed8579642bfc755956f64c847ef3/mmdebstrap-autopkgtest-build-qemu
: <<'POD2MAN'
=head1 NAME
mmdebstrap-autopkgtest-build-qemu - autopkgtest-build-qemu without vmdb2 but mmdebstrap and EFI boot
=head1 SYNOPSIS
B<mmdebstrap-autopkgtest-build-qemu> [I<OPTIONS>] B<--boot>=B<efi> I<RELEASE> I<IMAGE>
=head1 DESCRIPTION
B<mmdebstrap-autopkgtest-build-qemu> is a mostly compatible drop-in replacement
for B<autopkgtest-build-qemu>(1) with two main differences: Firstly, it uses
B<mmdebstrap>(1) instead of B<vmdb2>(1) and thus is able to create QEMU disk
images without requiring superuser privileges. Secondly, it uses
B<systemd-boot>(7) and thus only supports booting via EFI.
=head1 POSITIONAL PARAMETERS
=over 8
=item I<RELEASE>
The release to download from the I<MIRROR>. This parameter is required.
=item I<IMAGE>
The file to write, in raw format. This parameter is required.
=back
=head1 OPTIONS
=over 8
=item B<--mirror>=I<MIRROR>
Specify which distribution to install. It defaults to
http://deb.debian.org/debian (i.e. Debian), but you can pass a mirror of any
Debian derivative.
=item B<--architecture>=I<ARCHITECTURE>
Set the architecture for the virtual machine image, specified as a B<dpkg>(1)
architecture. If omitted, the host architecture is assumed.
B<--arch>=I<ARCH> is an alias for this option.
=item B<--script>=I<SCRIPT>
Specifies a user script that will be called with the root filesystem of the
image as its first parameter. This script can them make any necesssary
modifications to the root filesystem.
The script must be a POSIX shell script, and should not depend on bash-specific
features. This script will be executed inside a B<chroot>(1) call in the
virtual machine root filesystem.
=item B<--size>=I<SIZE>
Specifies the image size for the virtual machine, defaulting to 25G.
=item B<--apt-proxy>=I<PROXY>
Specify an apt proxy to use in the virtual machine. By default, if you have
an apt proxy configured on the host, the virtual machine will automatically use
this, otherwise there is no default.
=item B<--boot>=B<efi>, B<--efi>
Select the way the generated image will expect to be booted. Unless you
explicitly select --boot=efi, operation will fail.
=item B<--keyring>=I<KEYRING>
Passes an additional B<--keyring> parameter to B<mmdebstrap>.
=back
=head1 EXAMPLES
$ mmdebstrap-autopkgtest-build-qemu --boot=efi stable /path/to/debian-stable-i386.img i386
$ mmdebstrap-autopkgtest-build-qemu --boot=efi unstable /path/to/debian-unstable.img
=head1 SEE ALSO
B<autopkgtest-build-qemu>(1), B<autopkgtest-virt-qemu>(1), B<mmdebstrap>(1), B<autopkgtest>(1)
=cut
POD2MAN
set -eu
die() {
@ -19,7 +114,7 @@ die() {
exit 1
}
usage() {
die "usage: $0 [--boot=|--architecture=|--apt-proxy=|--keyring=|--mirror=|--script=|--size=] <RELEASE> <IMAGE> [MIRROR] [ARCHITECTURE] [SCRIPT] [SIZE]"
die "usage: $0 [--architecture=|--apt-proxy=|--keyring=|--mirror=|--script=|--size=] --boot=efi <RELEASE> <IMAGE>"
}
usage_error() {
echo "error: $*" 1>&2

View file

@ -43,6 +43,29 @@ class PaxFilterAction(argparse.Action):
setattr(namespace, "paxfilter", items)
class TypeFilterAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, "typefilter", [])
match values:
case "REGTYPE" | "0":
items.append(tarfile.REGTYPE)
case "LNKTYPE" | "1":
items.append(tarfile.LNKTYPE)
case "SYMTYPE" | "2":
items.append(tarfile.SYMTYPE)
case "CHRTYPE" | "3":
items.append(tarfile.CHRTYPE)
case "BLKTYPE" | "4":
items.append(tarfile.BLKTYPE)
case "DIRTYPE" | "5":
items.append(tarfile.DIRTYPE)
case "FIFOTYPE" | "6":
items.append(tarfile.FIFOTYPE)
case _:
raise ValueError("invalid type: %s" % values)
setattr(namespace, "typefilter", items)
class TransformAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
items = getattr(namespace, "trans", [])
@ -89,10 +112,11 @@ dpkg(1) for information on how these two options work in detail. To reuse the
exact same semantics as used by dpkg, paths must be given as /path and not as
./path even though they might be stored as such in the tarball.
Secondly, filter out unwanted pax extended headers. This is useful in cases
where a tool only accepts certain xattr prefixes. For example tar2sqfs only
supports SCHILY.xattr.user.*, SCHILY.xattr.trusted.* and
SCHILY.xattr.security.* but not SCHILY.xattr.system.posix_acl_default.*.
Secondly, filter out unwanted pax extended headers using --pax-exclude and
--pax-include. This is useful in cases where a tool only accepts certain xattr
prefixes. For example tar2sqfs only supports SCHILY.xattr.user.*,
SCHILY.xattr.trusted.* and SCHILY.xattr.security.* but not
SCHILY.xattr.system.posix_acl_default.*.
Both types of options use Unix shell-style wildcards:
@ -101,10 +125,16 @@ Both types of options use Unix shell-style wildcards:
[seq] matches any character in seq
[!seq] matches any character not in seq
Thirdly, transform the path of tar members using a sed expression just as with
Thirdly, filter out files matching a specific tar archive member type using
--type-exclude. Valid type names are REGTYPE (regular file), LNKTYPE
(hardlink), SYMTYPE (symlink), CHRTYPE (character special), BLKTYPE (block
special), DIRTYPE (directory), FIFOTYPE (fifo) or their tar format flag value
(0-6, respectively).
Fourthly, transform the path of tar members using a sed expression just as with
GNU tar --transform.
Fourthly, strip leading directory components off of tar members. Just as with
Fifthly, strip leading directory components off of tar members. Just as with
GNU tar --strip-components, tar members that have less or equal components in
their path are not passed through.
@ -140,6 +170,15 @@ Lastly, shift user id and group id of each entry by the value given by the
help="Re-include a pax header after a previous exclusion. "
"This option can be specified multiple times.",
)
parser.add_argument(
"--type-exclude",
metavar="type",
action=TypeFilterAction,
help="Exclude certain member types by their type. Choose types either "
"by their name (REGTYPE, LNKTYPE, SYMTYPE, CHRTYPE, BLKTYPE, DIRTYPE, "
"FIFOTYPE) or by their tar format flag values (0-6, respectively). "
"This option can be specified multiple times.",
)
parser.add_argument(
"--transform",
"--xform",
@ -164,6 +203,7 @@ Lastly, shift user id and group id of each entry by the value given by the
if (
not hasattr(args, "pathfilter")
and not hasattr(args, "paxfilter")
and not hasattr(args, "typefilter")
and not hasattr(args, "strip_components")
):
from shutil import copyfileobj
@ -207,14 +247,24 @@ Lastly, shift user id and group id of each entry by the value given by the
skip = True
return skip
# starting with Python 3.8, the default format became PAX_FORMAT, so this
# is only for compatibility with older versions of Python 3
def type_filter_should_skip(member):
if not hasattr(args, "typefilter"):
return False
for t in args.typefilter:
if member.type == t:
return True
return False
# starting with Python 3.8, the default format became PAX_FORMAT but we
# are still explicit here in case of future changes.
with tarfile.open(fileobj=sys.stdin.buffer, mode="r|*") as in_tar, tarfile.open(
fileobj=sys.stdout.buffer, mode="w|", format=tarfile.PAX_FORMAT
) as out_tar:
for member in in_tar:
if path_filter_should_skip(member):
continue
if type_filter_should_skip(member):
continue
if args.strip_components:
comps = member.name.split("/")
# just as with GNU tar, archive members with less or equal

View file

@ -35,15 +35,8 @@ if [ -n "$AUTOPROXY" ] && [ -x "$AUTOPROXY" ] && [ -e /tmp/.auto-apt-proxy-0 ];
chmod 644 "$TMP_APT_CONFIG"
fi
# debootstrap runs mount -t proc proc /proc which doesn't work in an unshared
# namespace on privileged docker (like salsaci), so mount /proc manually
# https://bugs.debian.org/1031222
# https://salsa.debian.org/installer-team/debootstrap/-/merge_requests/91
$prefix {{ CMD }} --variant=custom --mode={{ MODE }} \
--setup-hook='env '"${AUTOPROXY:+APT_CONFIG='$TMP_APT_CONFIG'}"' container=lxc debootstrap --variant={{ VARIANT }} unstable "$1" {{ MIRROR }}' \
--setup-hook='mount -o rbind /proc "$1/proc"' \
--setup-hook='chroot "$1" dpkg-reconfigure systemd || true' \
--setup-hook='umount --lazy "$1/proc"' \
--setup-hook='env '"${AUTOPROXY:+APT_CONFIG='$TMP_APT_CONFIG'}"' debootstrap --variant={{ VARIANT }} unstable "$1" {{ MIRROR }}' \
- /tmp/debian-mm.tar {{ MIRROR }}
if [ -n "$AUTOPROXY" ] && [ -x "$AUTOPROXY" ] && [ -e /tmp/.auto-apt-proxy-0 ]; then
rm "$TMP_APT_CONFIG"

View file

@ -174,6 +174,13 @@ if [ "{{ VARIANT }}" = "-" ]; then
done
fi
# since debootstrap 1.0.133 there is no tzdata in the buildd variant and thus
# debootstrap creates its own /etc/localtime
if [ "{{ VARIANT }}" = "buildd" ]; then
[ "$(readlink /tmp/debian-{{ DIST }}-debootstrap/etc/localtime)" = /usr/share/zoneinfo/UTC ]
rm /tmp/debian-{{ DIST }}-debootstrap/etc/localtime
fi
# check if the file content differs
diff --unified --no-dereference --recursive /tmp/debian-{{ DIST }}-debootstrap /tmp/debian-{{ DIST }}-mm >&2

35
tests/skip-output-dev Normal file
View file

@ -0,0 +1,35 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
prefix=
if [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && [ "{{ MODE }}" != "auto" ]; then
if ! id "${SUDO_USER:-user}" >/dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
useradd --home-dir "/home/${SUDO_USER:-user}" --create-home "${SUDO_USER:-user}"
fi
prefix="runuser -u ${SUDO_USER:-user} --"
fi
# test this for both unshare and root mode because the code paths creating
# entries in /dev are different depending on whether mknod is available or not
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt --skip=output/dev {{ DIST }} - {{ MIRROR }} | {
tar -t;
echo ./dev/console;
echo ./dev/fd;
echo ./dev/full;
echo ./dev/null;
echo ./dev/ptmx;
echo ./dev/pts/;
echo ./dev/random;
echo ./dev/shm/;
echo ./dev/stderr;
echo ./dev/stdin;
echo ./dev/stdout;
echo ./dev/tty;
echo ./dev/urandom;
echo ./dev/zero;
} | sort | diff -u tar1.txt -

30
tests/skip-output-mknod Normal file
View file

@ -0,0 +1,30 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
prefix=
if [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && [ "{{ MODE }}" != "auto" ]; then
if ! id "${SUDO_USER:-user}" >/dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
useradd --home-dir "/home/${SUDO_USER:-user}" --create-home "${SUDO_USER:-user}"
fi
prefix="runuser -u ${SUDO_USER:-user} --"
fi
# test this for both unshare and root mode because the code paths creating
# entries in /dev are different depending on whether mknod is available or not
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt --skip=output/mknod \
{{ DIST }} - {{ MIRROR }} | {
tar -t;
echo ./dev/console;
echo ./dev/full;
echo ./dev/null;
echo ./dev/ptmx;
echo ./dev/random;
echo ./dev/tty;
echo ./dev/urandom;
echo ./dev/zero;
} | sort | diff -u tar1.txt -

28
tests/skip-tar-in-mknod Normal file
View file

@ -0,0 +1,28 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
[ {{ MODE }} = "unshare" ]
trap "rm -f /tmp/debian-chroot.tar" EXIT INT TERM
prefix=
if [ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && [ "{{ MODE }}" != "auto" ]; then
if ! id "${SUDO_USER:-user}" >/dev/null 2>&1; then
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
useradd --home-dir "/home/${SUDO_USER:-user}" --create-home "${SUDO_USER:-user}"
fi
prefix="runuser -u ${SUDO_USER:-user} --"
fi
$prefix {{ CMD }} --mode={{ MODE }} --variant=custom \
--skip=update,setup,cleanup,tar-in/mknod \
--setup-hook='tar-in ./cache/mmdebstrap-{{ DIST }}-apt.tar /' \
'' /tmp/debian-chroot.tar
cmp ./cache/mmdebstrap-{{ DIST }}-apt.tar /tmp/debian-chroot.tar \
|| diffoscope ./cache/mmdebstrap-{{ DIST }}-apt.tar /tmp/debian-chroot.tar

View file

@ -8,7 +8,7 @@ mkdir /tmp/root/real
run_testA() {
echo content > /tmp/foo
# 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-helper /tmp/root root setup '' 1 upload /tmp/foo "$1" < /tmp/myfifo 3>&-; echo $? >&3; printf "\\000\\000adios";
} | {{ CMD }} --hook-listener 1 3>&- >/tmp/myfifo; echo $?; } 3>&1;
} | { read -r xs1; [ "$xs1" -eq 0 ]; read -r xs2; [ "$xs2" -eq 0 ]; }
echo content | diff -u - /tmp/root/real/foo

View file

@ -8,12 +8,12 @@ echo "MMDEBSTRAP_APT_CONFIG $MMDEBSTRAP_APT_CONFIG"
echo "$MMDEBSTRAP_HOOK" >> /tmp/hooks
[ "$MMDEBSTRAP_MODE" = "root" ]
echo test-content $MMDEBSTRAP_HOOK > test
{{ CMD }} --hook-helper "$1" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" env 1 upload test /test <&$MMDEBSTRAP_HOOKSOCK >&$MMDEBSTRAP_HOOKSOCK
{{ CMD }} --hook-helper "$1" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" '' 1 upload test /test <&$MMDEBSTRAP_HOOKSOCK >&$MMDEBSTRAP_HOOKSOCK
rm test
echo "content inside chroot:"
cat "$1/test"
[ "test-content $MMDEBSTRAP_HOOK" = "$(cat "$1/test")" ]
{{ CMD }} --hook-helper "$1" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" env 1 download /test test <&$MMDEBSTRAP_HOOKSOCK >&$MMDEBSTRAP_HOOKSOCK
{{ CMD }} --hook-helper "$1" "$MMDEBSTRAP_MODE" "$MMDEBSTRAP_HOOK" '' 1 download /test test <&$MMDEBSTRAP_HOOKSOCK >&$MMDEBSTRAP_HOOKSOCK
echo "content outside chroot:"
cat test
[ "test-content $MMDEBSTRAP_HOOK" = "$(cat test)" ]