Wait for (reap) potential zombies and otherwise long-running background processes

Otherwise they might hog resources like /dev/null which can then not be
unmounted resulting in their mountpoints (the regular files) not being
removable and then the removal of device nodes in run_cleanup (if
mmdebstrap is run with --skip=output/dev) will fail.

Another potential solution would be to run each hook and apt invocation
in its own process namespace but this would require to remount /proc and
this in turn would require a new mount namespace as well but we'd like
to keep the mount namespace across multiple hooks...
This commit is contained in:
Johannes Schauer Marin Rodrigues 2024-08-18 13:10:18 +02:00
parent 98b3c7f2cd
commit d0568a2b9e
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1
3 changed files with 74 additions and 8 deletions

View file

@ -430,3 +430,6 @@ Modes: root unshare
Test: skip-tar-in-mknod Test: skip-tar-in-mknod
Modes: unshare Modes: unshare
Test: zombie-reaping
Modes: unshare

View file

@ -1964,6 +1964,23 @@ sub setup {
my $msg = $@; my $msg = $@;
# Wait for (reap) potential zombies and otherwise long-running background
# processes or otherwise they might hog resources like /dev/null which can
# then not be unmounted resulting in their mountpoints (the regular files)
# not being removable and then the removal of device nodes in run_cleanup
# (if mmdebstrap is run with --skip=output/dev) will fail.
if (any { $_ eq 'zombie-reaping' } @{ $options->{skip} }) {
info "skipping zombie-reaping as requested";
} else {
if (waitpid(-1, POSIX::WNOHANG) >= 0) {
info "waiting for background processes to finish...";
}
while ((my $child = waitpid(-1, 0)) > 0) {
my $status = $? >> 8;
info "PID $child exited with exit code $status";
}
}
$cleanup->(0); $cleanup->(0);
if ($msg) { if ($msg) {
error "setup failed: $msg"; error "setup failed: $msg";
@ -7238,14 +7255,16 @@ 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 if I<command> does not contain any shell metacharacters, then I<command> is
directly exec-ed with the path to the chroot directory passed as the first directly exec-ed with the path to the chroot directory passed as the first
argument. Otherwise, I<command> is executed under I<sh> and the chroot argument. Otherwise, I<command> is executed under I<sh> and the chroot
directory can be accessed via I<$1>. Most environment variables set by directory can be accessed via I<$1>. Background (daemon) processes spawned in
B<mmdebstrap> (like C<DEBIAN_FRONTEND>, C<LC_ALL> and C<PATH>) are preserved. a hook are not guaranteed to persist beyond the hook that created them.
Most notably, C<APT_CONFIG> is being unset. If you need the path to
C<APT_CONFIG> as written by mmdebstrap it can be found in the Most environment variables set by B<mmdebstrap> (like C<DEBIAN_FRONTEND>,
C<MMDEBSTRAP_APT_CONFIG> environment variable. All environment variables set by C<LC_ALL> and C<PATH>) are preserved. Most notably, C<APT_CONFIG> is being
the user are preserved, except for C<TMPDIR> which is cleared. See section unset. If you need the path to C<APT_CONFIG> as written by mmdebstrap it can be
B<TMPDIR>. Furthermore, C<MMDEBSTRAP_MODE> will store the mode set by found in the C<MMDEBSTRAP_APT_CONFIG> environment variable. All environment
B<--mode>, C<MMDEBSTRAP_FORMAT> stores the format chosen by B<--format>, 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_FORMAT> stores the format chosen by B<--format>,
C<MMDEBSTRAP_HOOK> stores which hook is currently run (setup, extract, C<MMDEBSTRAP_HOOK> stores which hook is currently run (setup, extract,
essential, customize), C<MMDEBSTRAP_ARGV0> stores the name of the binary with essential, customize), C<MMDEBSTRAP_ARGV0> stores the name of the binary with
which B<mmdebstrap> was executed and C<MMDEBSTRAP_VERBOSITY> stores the which B<mmdebstrap> was executed and C<MMDEBSTRAP_VERBOSITY> stores the
@ -7480,6 +7499,11 @@ This step is not carried out in B<extract> mode.
Unmount everything that was mounted during the B<mount> stage and restores Unmount everything that was mounted during the B<mount> stage and restores
F</usr/sbin/policy-rc.d> and F</usr/sbin/start-stop-daemon> if necessary. F</usr/sbin/policy-rc.d> and F</usr/sbin/start-stop-daemon> if necessary.
=item B<zombie-reaping>
Wait for (reap) still running processes (background processes or zombie
processes), unless B<--skip=zombie-reaping> is used.
=item B<cleanup> =item B<cleanup>
Performs cleanup tasks, unless B<--skip=cleanup> is used: Performs cleanup tasks, unless B<--skip=cleanup> is used:

39
tests/zombie-reaping Normal file
View file

@ -0,0 +1,39 @@
#!/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
MMTARFILTER=
[ -x /usr/bin/mmtarfilter ] && MMTARFILTER=/usr/bin/mmtarfilter
[ -x ./tarfilter ] && MMTARFILTER=./tarfilter
$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \
--skip=output/dev \
--customize-hook='chroot "$1" sh -c "sleep 1m > /dev/null" &' \
{{ DIST }} - {{ MIRROR }} \
| "$MMTARFILTER" --path-exclude="/dev" \
> /tmp/debian-chroot.tar
origfilter() {
< ./cache/mmdebstrap-{{ DIST }}-apt.tar \
"$MMTARFILTER" --path-exclude="/dev/*" --path-exclude="/dev"
}
origfilter | cmp - /tmp/debian-chroot.tar \
|| origfilter | diffoscope - /tmp/debian-chroot.tar