From 9694ed60541853cf6bb8d955b4b240dd853fa3fc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 14 May 2024 21:04:12 +0200 Subject: Adding upstream version 1.5.0. Signed-off-by: Daniel Baumann --- CHANGELOG.md | 5 + coverage.py | 2 +- coverage.txt | 6 +- .../setup00.sh | 9 + hooks/file-mirror-automount/setup00.sh | 2 +- hooks/maybe-merged-usr/essential00.sh | 4 + hooks/maybe-merged-usr/extract00.sh | 20 +- hooks/maybe-merged-usr/setup00.sh | 20 +- make_mirror.sh | 11 +- mmdebstrap | 442 ++++++++++++--------- mmdebstrap-autopkgtest-build-qemu | 33 +- tests/include-foreign-libmagic-mgc | 2 +- ...foreign-libmagic-mgc-with-multiple-arch-options | 2 +- tests/install-libmagic-mgc-on-foreign | 2 +- tests/missing-dev-sys-proc-inside-the-chroot | 6 +- tests/mmdebstrap | 15 +- tests/skip-tar-in-mknod | 5 +- 17 files changed, 367 insertions(+), 219 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fea8b9a..eea34e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +1.5.0 (2024-05-14) +------------------ + + - add --format=ext4 + 1.4.3 (2024-02-01) ------------------ diff --git a/coverage.py b/coverage.py index 7e911cf..01ee7b5 100755 --- a/coverage.py +++ b/coverage.py @@ -34,7 +34,7 @@ all_variants = [ "standard", ] default_format = "auto" -all_formats = ["auto", "directory", "tar", "squashfs", "ext2", "null"] +all_formats = ["auto", "directory", "tar", "squashfs", "ext2", "ext4", "null"] mirror = os.getenv("mirror", "http://127.0.0.1/debian") hostarch = subprocess.check_output(["dpkg", "--print-architecture"]).decode().strip() diff --git a/coverage.txt b/coverage.txt index fb09b19..dde5b33 100644 --- a/coverage.txt +++ b/coverage.txt @@ -59,7 +59,7 @@ Needs-QEMU: true Test: mmdebstrap Needs-Root: true Modes: root -Formats: tar squashfs ext2 +Formats: tar squashfs ext2 ext4 Variants: essential apt minbase buildd - standard Skip-If: variant == "standard" and dist == "oldstable" # #864082, #1004557, #1004558 @@ -68,7 +68,7 @@ Skip-If: Test: check-for-bit-by-bit-identical-format-output Modes: unshare fakechroot -Formats: tar squashfs ext2 +Formats: tar squashfs ext2 ext4 Variants: essential apt minbase buildd - standard Skip-If: variant == "standard" and dist == "oldstable" # #864082, #1004557, #1004558 @@ -433,4 +433,4 @@ Test: skip-output-mknod Modes: root unshare Test: skip-tar-in-mknod -Modes: unshare +Modes: root diff --git a/hooks/copy-host-apt-sources-and-preferences/setup00.sh b/hooks/copy-host-apt-sources-and-preferences/setup00.sh index 07caa78..9aafd78 100755 --- a/hooks/copy-host-apt-sources-and-preferences/setup00.sh +++ b/hooks/copy-host-apt-sources-and-preferences/setup00.sh @@ -1,4 +1,13 @@ #!/bin/sh +# +# This script makes sure that the apt sources.list and preferences from outside +# the chroot also exist inside the chroot by *appending* them to any existing +# files. If you do not want to keep the original content, add another setup +# hook before this one which cleans up the files you don't want to keep. +# +# If instead of copying sources.list verbatim you want to mangle its contents, +# consider using python-apt for that. An example can be found in the Debian +# packaging of mmdebstrap in ./debian/tests/sourcesfilter set -eu diff --git a/hooks/file-mirror-automount/setup00.sh b/hooks/file-mirror-automount/setup00.sh index 61f60f2..6ccbdaf 100755 --- a/hooks/file-mirror-automount/setup00.sh +++ b/hooks/file-mirror-automount/setup00.sh @@ -15,7 +15,7 @@ env APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-get indextargets --no-release-info - | while read -r path; do mkdir -p "$rootdir/run/mmdebstrap" if [ ! -d "/$path" ]; then - echo "/$path is not an existing directory" >&2 + echo "W: /$path is not an existing directory" >&2 continue fi case $MMDEBSTRAP_MODE in diff --git a/hooks/maybe-merged-usr/essential00.sh b/hooks/maybe-merged-usr/essential00.sh index a23f2f7..656057e 100755 --- a/hooks/maybe-merged-usr/essential00.sh +++ b/hooks/maybe-merged-usr/essential00.sh @@ -15,6 +15,10 @@ case "$ver" in echo "usr-is-merged package from src:usrmerge installed -- not running merged-usr essential hook" >&2 exit 0 ;; + 'not-installed ') + echo "usr-is-merged was not installed in a previous hook -- not running merged-usr essential hook" >&2 + exit 0 + ;; *) echo "unexpected situation for package usr-is-merged: $ver" >&2 exit 1 diff --git a/hooks/maybe-merged-usr/extract00.sh b/hooks/maybe-merged-usr/extract00.sh index dc88450..00bb037 100755 --- a/hooks/maybe-merged-usr/extract00.sh +++ b/hooks/maybe-merged-usr/extract00.sh @@ -4,12 +4,22 @@ set -eu env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-get update --error-on=any -# if the usr-is-merged package cannot be installed with apt, do nothing -if ! env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged > /dev/null 2>&1; then - echo "no package called usr-is-merged found -- not running merged-usr extract hook" >&2 - exit 0 +if env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged > /dev/null 2>&1; then + # if apt-cache exited successfully, then usr-is-merged exists either as + # a real or virtual package + if env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged 2>/dev/null | grep -q "Package: usr-is-merged"; then + echo "usr-is-merged found -- running merged-usr extract hook" >&2 + else + # The usr-is-merged must be virtual, so assume that nothing + # has to be done. This is the case with Debian Trixie or later + # or with Ubuntu Lunar or later + echo "usr-is-merged found but not real -- not running merged-usr extract hook" >&2 + exit 0 + fi else - echo "package usr-is-merged found -- running merged-usr extract hook" >&2 + # if the usr-is-merged package cannot be installed with apt, do nothing + echo "no package providing usr-is-merged found -- not running merged-usr extract hook" >&2 + exit 0 fi # resolve the script path using several methods in order: diff --git a/hooks/maybe-merged-usr/setup00.sh b/hooks/maybe-merged-usr/setup00.sh index a6bd712..6568af2 100755 --- a/hooks/maybe-merged-usr/setup00.sh +++ b/hooks/maybe-merged-usr/setup00.sh @@ -4,12 +4,22 @@ set -eu env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-get update --error-on=any -# if the usr-is-merged package cannot be installed with apt, do nothing -if ! env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged > /dev/null 2>&1; then - echo "no package called usr-is-merged found -- not running merged-usr setup hook" >&2 - exit 0 +if env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged > /dev/null 2>&1; then + # if apt-cache exited successfully, then usr-is-merged exists either as + # a real or virtual package + if env --chdir="$1" APT_CONFIG="$MMDEBSTRAP_APT_CONFIG" apt-cache show --no-all-versions usr-is-merged 2>/dev/null | grep -q "Package: usr-is-merged"; then + echo "usr-is-merged found -- running merged-usr setup hook" >&2 + else + # The usr-is-merged must be virtual, so assume that nothing + # has to be done. This is the case with Debian Trixie or later + # or with Ubuntu Lunar or later + echo "usr-is-merged found but not real -- not running merged-usr setup hook" >&2 + exit 0 + fi else - echo "package usr-is-merged found -- running merged-usr setup hook" >&2 + # if the usr-is-merged package cannot be installed with apt, do nothing + echo "no package providing usr-is-merged found -- not running merged-usr setup hook" >&2 + exit 0 fi # resolve the script path using several methods in order: diff --git a/make_mirror.sh b/make_mirror.sh index 8849ee3..3f8aae4 100755 --- a/make_mirror.sh +++ b/make_mirror.sh @@ -33,7 +33,7 @@ deletecache() { done # deleting artifacts from test "mmdebstrap" for variant in essential apt minbase buildd - standard; do - for format in tar ext2 squashfs; do + for format in tar ext2 ext4 squashfs; do if [ -e "$dir/mmdebstrap-$dist-$variant.$format" ]; then # attempt to delete for all dists because DEFAULT_DIST might've been different the last time rm "$dir/mmdebstrap-$dist-$variant.$format" @@ -236,7 +236,11 @@ END esac # shellcheck disable=SC2086 - APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get --yes install $pkgs + APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get --yes install $pkgs \ + || APT_CONFIG="$rootdir/etc/apt/apt.conf" apt-get --yes install \ + -oDebug::pkgProblemResolver=true -oDebug::pkgDepCache::Marker=1 \ + -oDebug::pkgDepCache::AutoInstall=1 \ + $pkgs rm "$rootdir/var/cache/apt/archives/lock" rmdir "$rootdir/var/cache/apt/archives/partial" @@ -449,10 +453,11 @@ 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,squashfs-tools-ng,genext2fs,linux-image-generic,passwd + 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,passwd,e2fsprogs,uuid-runtime if [ ! -e ./mmdebstrap ]; then pkgs="$pkgs,mmdebstrap" fi + pkgs="$pkgs,auditd" arches=$HOSTARCH if [ "$RUN_MA_SAME_TESTS" = "yes" ]; then case "$HOSTARCH" in diff --git a/mmdebstrap b/mmdebstrap index b7f11e6..538ea02 100755 --- a/mmdebstrap +++ b/mmdebstrap @@ -23,7 +23,7 @@ use strict; use warnings; -our $VERSION = '1.4.3'; +our $VERSION = '1.5.0'; use English; use Getopt::Long; @@ -46,6 +46,7 @@ use Socket; use Time::HiRes; use Math::BigInt; use Text::ParseWords; +use Digest::SHA; use version; ## no critic (InputOutput::RequireBriefOpen) @@ -66,13 +67,16 @@ use version; *MS_BIND = \0x1000; *MS_REC = \0x4000; *MNT_DETACH = \2; +# uuid_t NameSpace_DNS in rfc4122 +*UUID_NS_DNS = \'6ba7b810-9dad-11d1-80b4-00c04fd430c8'; our ( $CLONE_NEWNS, $CLONE_NEWUTS, $CLONE_NEWIPC, $CLONE_NEWUSER, $CLONE_NEWPID, $CLONE_NEWNET, $_LINUX_CAPABILITY_VERSION_3, $CAP_SYS_ADMIN, $PR_CAPBSET_READ, $MS_BIND, - $MS_REC, $MNT_DETACH + $MS_REC, $MNT_DETACH, + $UUID_NS_DNS ); #<<< @@ -215,11 +219,12 @@ sub minor { } sub can_execute { - my $tool = shift; - my $pid = open my $fh, '-|' // return 0; + my $tool = shift; + my $verbose = shift // '--version'; + my $pid = open my $fh, '-|' // return 0; if ($pid == 0) { open(STDERR, '>&', STDOUT) or die; - exec {$tool} $tool, '--version' or die; + exec {$tool} $tool, $verbose or die; } chomp( my $content = do { local $/; <$fh> } @@ -303,6 +308,28 @@ sub shellescape { return "'$string'"; } +sub create_v5_uuid { + use bytes; + my $ns_uuid = shift; + my $name = shift; + my $version = 0x50; + # convert the namespace uuid to binary + $ns_uuid =~ tr/-//d; + $ns_uuid = pack 'H*', $ns_uuid; + # concatenate namespace and name and take sha1 + my $digest = Digest::SHA->new(1); + $digest->add($ns_uuid); + $digest->add($name); + # only the first 16 bytes matter + my $uuid = substr($digest->digest(), 0, 16); + # set the version + substr $uuid, 6, 1, chr(ord(substr($uuid, 6, 1)) & 0x0f | $version); + substr $uuid, 8, 1, chr(ord(substr $uuid, 8, 1) & 0x3f | 0x80); + # convert binary back to uuid formatting + return join '-', map { unpack 'H*', $_ } + map { substr $uuid, 0, $_, '' } (4, 2, 2, 2, 6); +} + sub test_unshare_userns { my $verbose = shift; @@ -4380,15 +4407,16 @@ sub guess_sources_format { } sub approx_disk_usage { - my $directory = shift; + my $directory = shift; + my $block_size = shift; info "approximating disk usage..."; # the "du" utility reports different results depending on the underlying # filesystem, see https://bugs.debian.org/650077 for a discussion # # we use code similar to the one used by dpkg-gencontrol instead # - # Regular files are measured in number of 1024 byte blocks. All other - # entries are assumed to take one block of space. + # Regular files are measured in number of $block_size byte blocks. All + # other entries are assumed to take one block of space. # # We ignore /dev because depending on the mode, the directory might be # populated or not and we want consistent disk usage results independent @@ -4412,8 +4440,8 @@ sub approx_disk_usage { return if exists $hardlink{"$dev:$ino"}; # Track hardlinks to avoid repeated additions. $hardlink{"$dev:$ino"} = 1 if $nlink > 1; - # add file size in 1024 byte blocks, rounded up - $installed_size += int(((-s _) + 1024) / 1024); + # add file size in $block_size byte blocks, rounded up + $installed_size += int(((-s _) + $block_size) / $block_size); } else { # all other entries are assumed to only take up one block $installed_size += 1; @@ -4805,7 +4833,7 @@ sub main() { $options->{format} = 'directory'; } my @valid_formats - = ('auto', 'directory', 'tar', 'squashfs', 'ext2', 'null'); + = ('auto', 'directory', 'tar', 'squashfs', 'ext2', 'ext4', 'null'); if (none { $_ eq $options->{format} } @valid_formats) { error "invalid format. Choose from " . (join ', ', @valid_formats); } @@ -5670,6 +5698,30 @@ sub main() { if ($exitstatus != 0) { error "genext2fs failed with exit status: $exitstatus"; } + } elsif ($options->{target} =~ /\.ext4$/) { + $options->{format} = 'ext4'; + # check if the installed version of e2fsprogs supports tarballs on + # stdin + (undef, my $filename) = tempfile( + "mmdebstrap.ext4.XXXXXXXXXXXX", + OPEN => 0, + TMPDIR => 1 + ); + # creating file to suppress message "Creating regular file ..." + { open my $fh, '>', $filename; } + open my $fh, '|-', 'mke2fs', '-q', '-F', '-o', 'Linux', '-T', + 'ext4', '-b', '4096', '-d', '-', $filename, + '16384' // error "failed to fork(): $!"; + # write 10240 null-bytes to mke2fs -- this represents an empty + # tar archive + print $fh ("\0" x 10240) + or error "cannot write to mke2fs process"; + close $fh; + my $exitstatus = $?; + unlink $filename // die "cannot unlink $filename"; + if ($exitstatus != 0) { + error "mke2fs failed with exit status: $exitstatus"; + } } else { $options->{format} = 'directory'; } @@ -5687,21 +5739,30 @@ sub main() { info "ignoring target $options->{target} with null format"; } + my $blocksize = -1; if ($options->{format} eq 'ext2') { if (!can_execute 'genext2fs') { error "need genext2fs for ext2 format"; } + $blocksize = 1024; + } elsif ($options->{format} eq 'ext4') { + if (!can_execute 'mke2fs', '-V') { + error "need mke2fs for ext4 format"; + } + $blocksize = 4096; } elsif ($options->{format} eq 'squashfs') { if (!can_execute 'tar2sqfs') { error "need tar2sqfs binary from the squashfs-tools-ng package"; } + $blocksize = 1048576; } - if (any { $_ eq $options->{format} } ('tar', 'squashfs', 'ext2', 'null')) { + if (any { $_ eq $options->{format} } + ('tar', 'squashfs', 'ext2', 'ext4', '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" + info "creating a tarball, squashfs, ext2 or ext4 image in" . " fakechroot mode might fail in extract and" . " custom variants because there might be no tar inside the" . " chroot"; @@ -5885,7 +5946,7 @@ sub main() { # If both the above assertion change, we can stop creating /dev entries as # well. my $devtar = ''; - if (any { $_ eq $options->{format} } ('tar', 'squashfs', 'ext2')) { + if (any { $_ eq $options->{format} } ('tar', 'squashfs', 'ext2', 'ext4')) { foreach my $file (@devfiles) { my ($fname, $mode, $type, $linkname, $devmajor, $devminor) = @{$file}; @@ -5954,6 +6015,9 @@ sub main() { push @taropts, '--xattrs', '--xattrs-exclude=system.*'; } elsif ($options->{format} eq "ext2") { warning "genext2fs does not support extended attributes"; + warning "ext2 does not support sub-second precision timestamps"; + warning "ext2 does not support timestamps beyond 2038 January 18"; + warning "ext2 inode size of 128 prevents removing these limitations"; } else { push @taropts, '--xattrs'; } @@ -5963,8 +6027,6 @@ sub main() { my $sigset = POSIX::SigSet->new(SIGINT, SIGHUP, SIGPIPE, SIGTERM); POSIX::sigprocmask(SIG_BLOCK, $sigset) or error "Can't block signals: $!"; - my $pid; - # a pipe to transfer the final tarball from the child to the parent pipe my $rfh, my $wfh; @@ -5979,163 +6041,108 @@ sub main() { # b) it puts code writing the protocol outside of the helper/listener # c) the forked listener process cannot communicate to its parent pipe my $nblkreader, my $nblkwriter or error "pipe failed: $!"; - if ($options->{mode} eq 'unshare') { - $pid = get_unshare_cmd( - sub { - # child - local $SIG{'INT'} = 'DEFAULT'; - local $SIG{'HUP'} = 'DEFAULT'; - local $SIG{'PIPE'} = 'DEFAULT'; - local $SIG{'TERM'} = 'DEFAULT'; - # unblock all delayed signals (and possibly handle them) - POSIX::sigprocmask(SIG_UNBLOCK, $sigset) - or error "Can't unblock signals: $!"; + my $worker = sub { + # child + local $SIG{'INT'} = 'DEFAULT'; + local $SIG{'HUP'} = 'DEFAULT'; + local $SIG{'PIPE'} = 'DEFAULT'; + local $SIG{'TERM'} = 'DEFAULT'; - close $rfh; - close $parentsock; - open(STDOUT, '>&', STDERR) or error "cannot open STDOUT: $!"; + # unblock all delayed signals (and possibly handle them) + POSIX::sigprocmask(SIG_UNBLOCK, $sigset) + or error "Can't unblock signals: $!"; - setup($options); + close $rfh; + close $parentsock; + open(STDOUT, '>&', STDERR) or error "cannot open STDOUT: $!"; - print $childsock (pack('n', 0) . 'adios'); - $childsock->flush(); + setup($options); - close $childsock; + print $childsock (pack('n', 0) . 'adios'); + $childsock->flush(); - close $nblkreader; - if (!$options->{dryrun} && $options->{format} eq 'ext2') { - my $numblocks = approx_disk_usage($options->{root}); - print $nblkwriter "$numblocks\n"; - $nblkwriter->flush(); - } - close $nblkwriter; + close $childsock; - if ($options->{dryrun}) { - info "simulate creating tarball..."; - } elsif (any { $_ eq $options->{format} } - ('tar', 'squashfs', 'ext2')) { - info "creating tarball..."; - - # redirect tar output to the writing end of the pipe so - # that the parent process can capture the output - open(STDOUT, '>&', $wfh) or error "cannot open STDOUT: $!"; - - # Add ./dev as the first entries of the tar file. - # We cannot add them after calling tar, because there is no - # way to prevent tar from writing NULL entries at the end. - if (any { $_ eq 'output/dev' } @{ $options->{skip} }) { - info "skipping output/dev as requested"; - } else { - print $devtar; - } + close $nblkreader; + if (!$options->{dryrun} && any { $_ eq $options->{format} } + ('ext2', 'ext4')) { + my $numblocks = approx_disk_usage($options->{root}, $blocksize); + print $nblkwriter "$numblocks\n"; + $nblkwriter->flush(); + } + close $nblkwriter; - # pack everything except ./dev - 0 == system('tar', @taropts, '-C', $options->{root}, '.') - or error "tar failed: $?"; + if ($options->{dryrun}) { + info "simulate creating tarball..."; + } elsif (any { $_ eq $options->{format} } + ('tar', 'squashfs', 'ext2', 'ext4')) { + info "creating tarball..."; + + # redirect tar output to the writing end of the pipe so + # that the parent process can capture the output + open(STDOUT, '>&', $wfh) or error "cannot open STDOUT: $!"; + + # Add ./dev as the first entries of the tar file. + # We cannot add them after calling tar, because there is no + # way to prevent tar from writing NULL entries at the end. + if (any { $_ eq 'output/dev' } @{ $options->{skip} }) { + info "skipping output/dev as requested"; + } else { + print $devtar; + } - info "done"; - } elsif (any { $_ eq $options->{format} } - ('directory', 'null')) { - # nothing to do - } else { - error "unknown format: $options->{format}"; + if ($options->{mode} eq 'unshare') { + # pack everything except ./dev + 0 == system('tar', @taropts, '-C', $options->{root}, '.') + or error "tar failed: $?"; + } elsif ($options->{mode} eq 'fakechroot') { + # By default, FAKECHROOT_EXCLUDE_PATH includes /proc and /sys + # which means that the resulting tarball will contain the + # permission and ownership information of /proc and /sys from + # the outside, which we want to avoid. + ## no critic (Variables::RequireLocalizedPunctuationVars) + $ENV{FAKECHROOT_EXCLUDE_PATH} = "/dev"; + # Fakechroot requires tar to run inside the chroot or otherwise + # absolute symlinks will include the path to the root directory + 0 == system('chroot', $options->{root}, 'tar', + @taropts, '-C', '/', '.') + or error "tar failed: $?"; + } elsif (any { $_ eq $options->{mode} } ('root', 'chrootless')) { + # If the chroot directory is not owned by the root user, then + # we assume that no measure was taken to fake root permissions. + # Since the final tarball should contain entries with root + # ownership, we instruct tar to do so. + my @owneropts = (); + if ((stat $options->{root})[4] != 0) { + push @owneropts, '--owner=0', '--group=0', + '--numeric-owner'; } + 0 == system('tar', @taropts, @owneropts, '-C', + $options->{root}, '.') + or error "tar failed: $?"; + } else { + error "unknown mode: $options->{mode}"; + } - exit 0; - }, - \@idmap - ); + info "done"; + } elsif (any { $_ eq $options->{format} } ('directory', 'null')) { + # nothing to do + } else { + error "unknown format: $options->{format}"; + } + + exit 0; + }; + + my $pid; + if ($options->{mode} eq 'unshare') { + $pid = get_unshare_cmd($worker, \@idmap); } elsif (any { $_ eq $options->{mode} } ('root', 'fakechroot', 'chrootless')) { $pid = fork() // error "fork() failed: $!"; if ($pid == 0) { - local $SIG{'INT'} = 'DEFAULT'; - local $SIG{'HUP'} = 'DEFAULT'; - local $SIG{'PIPE'} = 'DEFAULT'; - local $SIG{'TERM'} = 'DEFAULT'; - - # unblock all delayed signals (and possibly handle them) - POSIX::sigprocmask(SIG_UNBLOCK, $sigset) - or error "Can't unblock signals: $!"; - - close $rfh; - close $parentsock; - open(STDOUT, '>&', STDERR) or error "cannot open STDOUT: $!"; - - setup($options); - - print $childsock (pack('n', 0) . 'adios'); - $childsock->flush(); - - close $childsock; - - close $nblkreader; - if (!$options->{dryrun} && $options->{format} eq 'ext2') { - my $numblocks = approx_disk_usage($options->{root}); - print $nblkwriter $numblocks; - $nblkwriter->flush(); - } - close $nblkwriter; - - if ($options->{dryrun}) { - info "simulate creating tarball..."; - } elsif (any { $_ eq $options->{format} } - ('tar', 'squashfs', 'ext2')) { - info "creating tarball..."; - - # redirect tar output to the writing end of the pipe so that - # the parent process can capture the output - open(STDOUT, '>&', $wfh) or error "cannot open STDOUT: $!"; - - # Add ./dev as the first entries of the tar file. - # We cannot add them after calling tar, because there is no way - # to prevent tar from writing NULL entries at the end. - if (any { $_ eq 'output/dev' } @{ $options->{skip} }) { - info "skipping output/dev as requested"; - } else { - print $devtar; - } - - if ($options->{mode} eq 'fakechroot') { - # By default, FAKECHROOT_EXCLUDE_PATH includes /proc and - # /sys which means that the resulting tarball will contain - # the permission and ownership information of /proc and - # /sys from the outside, which we want to avoid. - ## no critic (Variables::RequireLocalizedPunctuationVars) - $ENV{FAKECHROOT_EXCLUDE_PATH} = "/dev"; - # Fakechroot requires tar to run inside the chroot or - # otherwise absolute symlinks will include the path to the - # root directory - 0 == system('chroot', $options->{root}, 'tar', - @taropts, '-C', '/', '.') - or error "tar failed: $?"; - } elsif (any { $_ eq $options->{mode} } ('root', 'chrootless')) - { - # If the chroot directory is not owned by the root user, - # then we assume that no measure was taken to fake root - # permissions. Since the final tarball should contain - # entries with root ownership, we instruct tar to do so. - my @owneropts = (); - if ((stat $options->{root})[4] != 0) { - push @owneropts, '--owner=0', '--group=0', - '--numeric-owner'; - } - 0 == system('tar', @taropts, @owneropts, '-C', - $options->{root}, '.') - or error "tar failed: $?"; - } else { - error "unknown mode: $options->{mode}"; - } - - info "done"; - } elsif (any { $_ eq $options->{format} } ('directory', 'null')) { - # nothing to do - } else { - error "unknown format: $options->{format}"; - } - - exit 0; + $worker->(); } } else { error "unknown mode: $options->{mode}"; @@ -6190,7 +6197,8 @@ sub main() { my $numblocks = 0; close $nblkwriter; - if (!$options->{dryrun} && $options->{format} eq 'ext2') { + if (!$options->{dryrun} && any { $_ eq $options->{format} } + ('ext2', 'ext4')) { $numblocks = <$nblkreader>; if (defined $numblocks) { chomp $numblocks; @@ -6209,9 +6217,11 @@ sub main() { # nothing to do } elsif (any { $_ eq $options->{format} } ('directory', 'null')) { # nothing to do - } elsif ($options->{format} eq 'ext2' && $numblocks <= 0) { + } elsif ((any { $_ eq $options->{format} } ('ext2', 'ext4')) + && $numblocks <= 0) { # nothing to do because of invalid $numblocks - } elsif (any { $_ eq $options->{format} } ('tar', 'squashfs', 'ext2')) { + } elsif (any { $_ eq $options->{format} } + ('tar', 'squashfs', 'ext2', 'ext4')) { # we use eval() so that error() doesn't take this process down and # thus leaves the setup() process without a parent eval { @@ -6220,16 +6230,16 @@ sub main() { error "cannot copy to standard output: $!"; } } else { - if ( $options->{format} eq 'squashfs' - or $options->{format} eq 'ext2' - or defined $tar_compressor) { + if (any { $_ eq $options->{format} } + ('squashfs', 'ext2', 'ext4') + or defined $tar_compressor) { my @argv = (); if ($options->{format} eq 'squashfs') { push @argv, 'tar2sqfs', '--quiet', '--no-skip', '--force', '--exportable', '--compressor', 'xz', - '--block-size', '1048576', + '--block-size', $blocksize, $options->{target}; } elsif ($options->{format} eq 'ext2') { if ($numblocks <= 0) { @@ -6237,6 +6247,26 @@ sub main() { } push @argv, 'genext2fs', '-B', 1024, '-b', $numblocks, '-i', '16384', '-a', '-', $options->{target}; + } elsif ($options->{format} eq 'ext4') { + if ($numblocks <= 0) { + error "invalid number of blocks: $numblocks"; + } + push @argv, 'mke2fs', '-q', '-F', '-o', 'Linux', '-T', + 'ext4'; + if (exists $ENV{SOURCE_DATE_EPOCH}) { + # if SOURCE_DATE_EPOCH was set, make the image + # reproducible by setting a fixed uuid and + # hash_seed + my $uuid = create_v5_uuid( + create_v5_uuid( + $UUID_NS_DNS, "mister-muffin.de" + ), + $mtime + ); + push @argv, '-U', $uuid, '-E', "hash_seed=$uuid"; + } + push @argv, '-b', $blocksize, '-d', '-', + $options->{target}, $numblocks; } elsif ($options->{format} eq 'tar') { push @argv, @{$tar_compressor}; } else { @@ -6258,8 +6288,8 @@ sub main() { or error "Can't unblock signals: $!"; # redirect stdout to file or /dev/null - if ( $options->{format} eq 'squashfs' - or $options->{format} eq 'ext2') { + if (any { $_ eq $options->{format} } + ('squashfs', 'ext2', 'ext4')) { open(STDOUT, '>', '/dev/null') or error "cannot open /dev/null for writing: $!"; } elsif ($options->{format} eq 'tar') { @@ -6340,7 +6370,7 @@ sub main() { if (any { $_ eq $options->{format} } ('directory')) { # nothing to do } elsif (any { $_ eq $options->{format} } - ('tar', 'squashfs', 'ext2', 'null')) { + ('tar', 'squashfs', 'ext2', 'ext4', 'null')) { if (!-e $options->{root}) { error "$options->{root} does not exist"; } @@ -6439,12 +6469,12 @@ can be disabled by choosing the empty string for I. See the section B for more information. The I option may 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, a -FIFO, a character special device, or C<->. The I option is optional if -no I option is provided. If I is missing or if I is -C<->, an uncompressed tarball will be sent to standard output. Without the -B<--format> option, I will be used to choose the format. See the -section B for more information. +tarball filename, the path to a squashfs image, the path to an ext2 or ext4 +image, a FIFO, a character special device, or C<->. The I option is +optional if no I option is provided. If I is missing or if +I is C<->, an uncompressed tarball will be sent to standard output. +Without the B<--format> option, I will be used to choose the format. +See the section B for more information. The I option may either be provided as a URI, in apt one-line format, as a path to a file in apt's one-line or deb822-format, or C<->. If no @@ -6521,8 +6551,8 @@ information. =item B<--format>=I Choose the output format. Valid format Is are B, B, -B, B, B and B. The default format is B. See -the section B for more information. +B, B, B, B and B. The default format is +B. See the section B for more information. =item B<--aptopt>=I