diff --git a/coverage.sh b/coverage.sh index 3cee41d..dd84247 100755 --- a/coverage.sh +++ b/coverage.sh @@ -300,8 +300,8 @@ cat << END > shared/test.sh set -eu export LC_ALL=C.UTF-8 mount -t tmpfs -o nodev,nosuid,size=300M tmpfs /tmp -# use --customize to exercise the mounting/unmounting code of block devices in root mode -$CMD --mode=root --variant=apt --customize='mount | grep /dev/full' --customize='test "\$(echo foo | tee /dev/full 2>&1 1>/dev/null)" = "tee: /dev/full: No space left on device"' unstable /tmp/unstable-chroot.tar $mirror +# use --customize-hook to exercise the mounting/unmounting code of block devices in root mode +$CMD --mode=root --variant=apt --customize-hook='mount | grep /dev/full' --customize-hook='test "\$(echo foo | tee /dev/full 2>&1 1>/dev/null)" = "tee: /dev/full: No space left on device"' unstable /tmp/unstable-chroot.tar $mirror tar -tf /tmp/unstable-chroot.tar | sort > tar2.txt diff -u tar1.txt tar2.txt rm /tmp/unstable-chroot.tar @@ -528,7 +528,60 @@ else ./run_null.sh SUDO fi -print_header "mode=root,variant=apt: test --customize" +print_header "mode=root,variant=apt: test --setup-hook" +cat << END > shared/test.sh +#!/bin/sh +set -eu +export LC_ALL=C.UTF-8 +cat << 'SCRIPT' > customize.sh +#!/bin/sh +for d in sbin lib; do ln -s usr/\$d "\$1/\$d"; mkdir -p "\$1/usr/\$d"; done +SCRIPT +chmod +x customize.sh +$CMD --mode=root --variant=apt --setup-hook='ln -s usr/bin "\$1/bin"; mkdir -p "\$1/usr/bin"' --setup-hook=./customize.sh unstable /tmp/debian-unstable $mirror +tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort > tar2.txt +{ sed -e 's/^\.\/bin\//.\/usr\/bin\//;s/^\.\/lib\//.\/usr\/lib\//;s/^\.\/sbin\//.\/usr\/sbin\//;' tar1.txt; echo ./bin; echo ./lib; echo ./sbin; } | sort -u | diff -u - tar2.txt +rm customize.sh +rm -r /tmp/debian-unstable +END +if [ "$HAVE_QEMU" = "yes" ]; then + ./run_qemu.sh +else + ./run_null.sh SUDO +fi + +print_header "mode=root,variant=apt: test --essential-hook" +cat << END > shared/test.sh +#!/bin/sh +set -eu +export LC_ALL=C.UTF-8 +cat << 'SCRIPT' > customize.sh +#!/bin/sh +echo tzdata tzdata/Zones/Europe select Berlin | chroot "\$1" debconf-set-selections +SCRIPT +chmod +x customize.sh +$CMD --mode=root --variant=apt --include=tzdata --essential-hook='echo tzdata tzdata/Areas select Europe | chroot "\$1" debconf-set-selections' --essential-hook=./customize.sh unstable /tmp/debian-unstable $mirror +echo Europe/Berlin | cmp /tmp/debian-unstable/etc/timezone +tar -C /tmp/debian-unstable --one-file-system -c . | tar -t | sort \ + | grep -v '^./etc/localtime' \ + | grep -v '^./etc/timezone' \ + | grep -v '^./usr/sbin/tzconfig' \ + | grep -v '^./usr/share/doc/tzdata' \ + | grep -v '^./usr/share/zoneinfo' \ + | grep -v '^./var/lib/dpkg/info/tzdata.' \ + | grep -v '^./var/log/apt/eipp.log.xz$' \ + > tar2.txt +diff -u tar1.txt tar2.txt +rm customize.sh +rm -r /tmp/debian-unstable +END +if [ "$HAVE_QEMU" = "yes" ]; then + ./run_qemu.sh +else + ./run_null.sh SUDO +fi + +print_header "mode=root,variant=apt: test --customize-hook" cat << END > shared/test.sh #!/bin/sh set -eu @@ -539,7 +592,7 @@ chroot "\$1" whoami > "\$1/output2" chroot "\$1" pwd >> "\$1/output2" SCRIPT chmod +x customize.sh -$CMD --mode=root --variant=apt --customize='chroot "\$1" sh -c "whoami; pwd" > "\$1/output1"' --customize=./customize.sh unstable /tmp/debian-unstable $mirror +$CMD --mode=root --variant=apt --customize-hook='chroot "\$1" sh -c "whoami; pwd" > "\$1/output1"' --customize-hook=./customize.sh unstable /tmp/debian-unstable $mirror printf "root\n/\n" | cmp /tmp/debian-unstable/output1 printf "root\n/\n" | cmp /tmp/debian-unstable/output2 rm /tmp/debian-unstable/output1 diff --git a/mmdebstrap b/mmdebstrap index dc71594..98bab9b 100755 --- a/mmdebstrap +++ b/mmdebstrap @@ -875,6 +875,40 @@ sub run_chroot(&$) { } } +sub run_hooks($$) { + my $name = shift; + my $options = shift; + + if (scalar @{$options->{"${name}_hook"}} == 0) { + return; + } + + my $runner = sub { + foreach my $script (@{$options->{"${name}_hook"}}) { + if ( -x $script || $script !~ m/[^\w@\%+=:,.\/-]/a) { + info "running --$name-hook directly: $script $options->{root}"; + # execute it directly if it's an executable file + # or if it there are no shell metacharacters + # (the /a regex modifier makes \w match only ASCII) + 0 == system($script, $options->{root}) or error "command failed: $script"; + } else { + info "running --$name-hook in shell: sh -c '$script' exec $options->{root}"; + # otherwise, wrap everything in sh -c + 0 == system('sh', '-c', $script, 'exec', $options->{root}) or error "command failed: $script"; + } + } + }; + + if ($name eq 'setup') { + # execute directly without mounting anything (the mount points do not + # exist yet) + &{$runner}(); + } else { + run_chroot \&$runner, $options; + } + +} + sub setup { my $options = shift; @@ -1068,6 +1102,16 @@ sub setup { # into account. $ENV{"APT_CONFIG"} = "$tmpfile"; + # setting PATH for chroot, ldconfig, start-stop-daemon... + if (defined $ENV{PATH} && $ENV{PATH} ne "") { + $ENV{PATH} = "$ENV{PATH}:/usr/sbin:/usr/bin:/sbin:/bin"; + } else { + $ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin"; + } + + # run setup hooks + run_hooks('setup', $options); + info "running apt-get update..."; run_apt_progress({ ARGV => ['apt-get', 'update'] }); @@ -1085,13 +1129,6 @@ sub setup { } } - # setting PATH for chroot, ldconfig, start-stop-daemon... - if (defined $ENV{PATH} && $ENV{PATH} ne "") { - $ENV{PATH} = "$ENV{PATH}:/usr/sbin:/usr/bin:/sbin:/bin"; - } else { - $ENV{PATH} = "/usr/sbin:/usr/bin:/sbin:/bin"; - } - my %pkgs_to_install; if (defined $options->{include}) { for my $pkg (split /,/, $options->{include}) { @@ -1307,6 +1344,9 @@ sub setup { if (any { $_ eq $options->{variant} } ('extract', 'custom')) { # nothing to do } elsif (any { $_ eq $options->{variant} } ('essential', 'apt', 'standard', 'important', 'required', 'buildd', 'minbase')) { + # run essential hooks + run_hooks('essential', $options); + if (%pkgs_to_install) { run_apt_progress({ ARGV => ['apt-get', '--yes', @@ -1495,6 +1535,9 @@ sub setup { unlink "$options->{root}/$deb" or error "cannot unlink $deb"; } + # run essential hooks + run_hooks('essential', $options); + if (%pkgs_to_install) { # some packages have to be installed from the outside before anything # can be installed from the inside. @@ -1592,23 +1635,7 @@ sub setup { error "unknown mode: $options->{mode}"; } - if (scalar @{$options->{customize}} > 0) { - run_chroot { - foreach my $script (@{$options->{customize}}) { - if ( -x $script || $script !~ m/[^\w@\%+=:,.\/-]/a) { - info "running customize script directly: $script $options->{root}"; - # execute it directly if it's an executable file - # or if it there are no shell metacharacters - # (the /a regex modifier makes \w match only ASCII) - 0 == system($script, $options->{root}) or error "customization script failed: $script"; - } else { - info "running customize script in shell: sh -c '$script' exec $options->{root}"; - # otherwise, wrap everything in sh -c - 0 == system('sh', '-c', $script, 'exec', $options->{root}) or error "customization script failed: $script"; - } - } - } $options; - } + run_hooks('customize', $options); # clean up temporary configuration file unlink "$options->{root}/etc/apt/apt.conf.d/00mmdebstrap" or error "failed to unlink /etc/apt/apt.conf.d/00mmdebstrap: $!"; @@ -1663,7 +1690,9 @@ sub main() { dpkgopts => [], aptopts => [], noop => [], - customize => [], + setup_hook => [], + essential_hook => [], + customize_hook => [], }; chomp ($options->{architectures} = `dpkg --print-architecture`); Getopt::Long::Configure ("bundling"); @@ -1685,8 +1714,10 @@ sub main() { 'resolve-deps' => sub { push @{$options->{noop}}, 'resolve-deps'; }, 'merged-usr' => sub { push @{$options->{noop}}, 'merged-usr'; }, 'no-merged-usr' => sub { push @{$options->{noop}}, 'no-merged-usr'; }, - # option is hidden until I'm happy with it - 'customize=s@' => \$options->{customize}, + # hook options are hidden until I'm happy with them + 'setup-hook=s@' => \$options->{setup_hook}, + 'essential-hook=s@' => \$options->{essential_hook}, + 'customize-hook=s@' => \$options->{customize_hook}, ) or pod2usage(-exitval => 2, -verbose => 1); foreach my $arg (@{$options->{noop}}) { @@ -2465,23 +2496,65 @@ running mmdebstrap. =begin comment -=item B<--customize>=I +=item B<--setup-hook>=I -Execute arbitrary Is after the chroot is set up and before it is -cleaned up. Can be specified multiple times. The commands are executed in the -order in which they are given on the command line. If I is an -existing executable file or if I does not contain any shell -metacharacters, then I is directly exec-ed with the path to the -chroot directory passed as the first argument. Otherwise, I is -executed under I and the chroot directory can be accessed via I<$1>. +Execute arbitrary Is right after initial setup (directory creation, +configuration of apt and dpkg, ...) but before any packages are downloaded or +installed. At that point, the chroot directory does not contain any +executables and thus cannot be chroot-ed into. The option can be specified +multiple times and the commands are executed in the order in which they are +given on the command line. If I is an existing executable file or if +I does not contain any shell metacharacters, then I is +directly exec-ed with the path to the chroot directory passed as the first +argument. Otherwise, I is executed under I and the chroot +directory can be accessed via I<$1>. All environment variables used by +B (like C, C, C and C) +are preserved. Examples: - --customize='chroot "$1" passwd --delete root' - --customize='chroot "$1" useradd --home-dir /home/user --create-home user' - --customize='chroot "$1" passwd --delete user' - --customize='echo host > "$1/etc/hostname"' - --customize=/usr/share/autopkgtest/setup-commands/setup-testbed + --setup-hook='for d in bin sbin lib; do ln -s usr/$d "$1/$d"; mkdir -p "$1/usr/$d"; done' + +=item B<--essential-hook>=I + +Execute arbitrary Is after the Essential:yes packages have been +installed but before installing the remaining packages. The hook is not +executed for the B and B variants. The option can be +specified multiple times and the commands are executed in the order in which +they are given on the command line. If I is an existing executable +file or if I does not contain any shell metacharacters, then +I is directly exec-ed with the path to the chroot directory passed as +the first argument. Otherwise, I is executed under I and the +chroot directory can be accessed via I<$1>. All environment variables used by +B (like C, C, C and C) +are preserved. + +Examples: + + --essential-hook='echo unattended-upgrades unattended-upgrades/enable_auto_updates boolean true | chroot "$1" debconf-set-selections' + --essential-hook='echo tzdata tzdata/Areas select Europe | chroot "$1" debconf-set-selections' + --essential-hook='echo tzdata tzdata/Zones/Europe select Berlin | chroot "$1" debconf-set-selections' + +=item B<--customize-hook>=I + +Execute arbitrary Is after the chroot is set up and all packages got +installed but before final cleanup actions are carried out. The option can be +specified multiple times and the commands are executed in the order in which +they are given on the command line. If I is an existing executable +file or if I does not contain any shell metacharacters, then +I is directly exec-ed with the path to the chroot directory passed as +the first argument. Otherwise, I is executed under I and the +chroot directory can be accessed via I<$1>. All environment variables used by +B (like C, C, C and C) +are preserved. + +Examples: + + --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' + --customize-hook='echo host > "$1/etc/hostname"' + --customize-hook=/usr/share/autopkgtest/setup-commands/setup-testbed =end comment @@ -2643,12 +2716,12 @@ Use as debootstrap replacement in sbuild-createchroot: Use as replacement for autopkgtest-build-qemu and vmdb2: $ mmdebstrap --variant=important --include=linux-image-amd64 \ - --customize='chroot "$1" passwd --delete root' \ - --customize='chroot "$1" useradd --home-dir /home/user --create-home user' \ - --customize='chroot "$1" passwd --delete user' \ - --customize='echo host > "$1/etc/hostname"' \ - --customize='echo "127.0.0.1 localhost host" > "$1/etc/hosts"' \ - --customize=/usr/share/autopkgtest/setup-commands/setup-testbed \ + --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' \ + --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 \ unstable debian-unstable.tar $ cat << END > extlinux.conf > default linux