Your message dated Tue, 03 Jun 2025 18:18:37 +0000
with message-id <e1umwdd-004oa9...@respighi.debian.org>
and subject line unblock initramfs-tools
has caused the Debian Bug report #1107203,
regarding unblock: initramfs-tools/0.148.1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact ow...@bugs.debian.org
immediately.)


-- 
1107203: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1107203
Debian Bug Tracking System
Contact ow...@bugs.debian.org with problems
--- Begin Message ---
Package: release.debian.org
Severity: normal
X-Debbugs-Cc: initramfs-to...@packages.debian.org, 
debian-ker...@lists.debian.org
Control: affects -1 + src:initramfs-tools
User: release.debian....@packages.debian.org
Usertags: unblock

Please unblock package initramfs-tools

[ Reason ]
The unmkinitramfs command is currently a shell script that has to run
multiple processes for each uncompressed cpio archive member in an
initramfs.

This was not too bad when there were usually only a few microcode
blobs in an uncompressed archive, but now that we put compressed
kernel modules in an uncompressed archive, it became very slow indeed.

I fixed this by rewriting it in C.

[ Impact ]
unmkinitramfs is currently very slow.

[ Tests ]
I added an autopkgtest script that tests unmkinitramfs with all
supported sequences of uncompressed and/or compressed cpio archives in
an initramfs image.

unmkinitramfs is also used in autopkgtest scripts for various packages
that integrate with initramfs-tools.

Since unmkinitramfs is supposed to be robust against invalid and
untrusted inputs, I also fuzz-tested it.  In 68 CPU-hours' fuzzing of
the standard build and a build with various sanitizers enabled, I did
not find any issues.

[ Risks ]
As with any rewrite, it's possible that the new implementation doesn't
behave quite like the previous one.  And as with any program written
in C, all kinds of nasty bugs are possible.  However, I think that the
autopkgtest coverage and fuzzing reduce these risks to an acceptable
level.

[ Checklist ]
  [X] all changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in testing

[ Other info ]

unblock initramfs-tools/0.148.1
diff -Nru initramfs-tools-0.147/Makefile initramfs-tools-0.148.1/Makefile
--- initramfs-tools-0.147/Makefile      1970-01-01 01:00:00.000000000 +0100
+++ initramfs-tools-0.148.1/Makefile    2025-05-13 05:22:04.000000000 +0200
@@ -0,0 +1,7 @@
+CFLAGS += -Wall -Wextra -O2
+
+all: unmkinitramfs
+clean:
+       rm -f unmkinitramfs
+
+.PHONY: all clean
diff -Nru initramfs-tools-0.147/debian/changelog 
initramfs-tools-0.148.1/debian/changelog
--- initramfs-tools-0.147/debian/changelog      2025-03-28 02:07:15.000000000 
+0100
+++ initramfs-tools-0.148.1/debian/changelog    2025-05-28 20:21:18.000000000 
+0200
@@ -1,3 +1,17 @@
+initramfs-tools (0.148.1) unstable; urgency=medium
+
+  * No-change source upload to allow propagation to testing
+
+ -- Ben Hutchings <b...@debian.org>  Wed, 28 May 2025 20:21:18 +0200
+
+initramfs-tools (0.148) unstable; urgency=medium
+
+  * [a797b42] unmkinitramfs: Rewrite in C to make it acceptably fast
+  * [9e37d8b] Move unmkinitramfs to new initramfs-tools-bin package
+  * [49f3bba] test: Add autopkgtest case for unmkinitramfs
+
+ -- Ben Hutchings <b...@debian.org>  Wed, 28 May 2025 19:09:21 +0200
+
 initramfs-tools (0.147) unstable; urgency=medium
 
   [ Ben Hutchings ]
diff -Nru initramfs-tools-0.147/debian/control 
initramfs-tools-0.148.1/debian/control
--- initramfs-tools-0.147/debian/control        2025-03-28 02:04:55.000000000 
+0100
+++ initramfs-tools-0.148.1/debian/control      2025-05-13 05:22:04.000000000 
+0200
@@ -30,6 +30,7 @@
 Depends: coreutils (>= 8.24),
          cpio (>= 2.12),
          dracut-install,
+         initramfs-tools-bin (>= ${source:Version}),
          klibc-utils (>= 2.0.4-8~),
          kmod,
          logsave | e2fsprogs (<< 1.45.3-1~),
@@ -42,3 +43,12 @@
  create a bootable initramfs for a Linux kernel.  The initramfs should
  be loaded along with the kernel and is then responsible for mounting
  the root filesystem and starting the main init system.
+
+Package: initramfs-tools-bin
+Architecture: any
+Multi-Arch: foreign
+Depends: ${misc:Depends}, ${shlibs:Depends}
+Replaces: initramfs-tools-core (<< 0.148~)
+Description: generic modular initramfs generator (binary tools)
+ This package contains the unmkinitramfs program that can be used to
+ unpack an initramfs image.
diff -Nru initramfs-tools-0.147/debian/initramfs-tools-bin.install 
initramfs-tools-0.148.1/debian/initramfs-tools-bin.install
--- initramfs-tools-0.147/debian/initramfs-tools-bin.install    1970-01-01 
01:00:00.000000000 +0100
+++ initramfs-tools-0.148.1/debian/initramfs-tools-bin.install  2025-05-13 
05:22:04.000000000 +0200
@@ -0,0 +1 @@
+unmkinitramfs          usr/bin
diff -Nru initramfs-tools-0.147/debian/initramfs-tools-bin.manpages 
initramfs-tools-0.148.1/debian/initramfs-tools-bin.manpages
--- initramfs-tools-0.147/debian/initramfs-tools-bin.manpages   1970-01-01 
01:00:00.000000000 +0100
+++ initramfs-tools-0.148.1/debian/initramfs-tools-bin.manpages 2025-05-13 
05:22:04.000000000 +0200
@@ -0,0 +1 @@
+unmkinitramfs.8
diff -Nru initramfs-tools-0.147/debian/initramfs-tools-core.install 
initramfs-tools-0.148.1/debian/initramfs-tools-core.install
--- initramfs-tools-0.147/debian/initramfs-tools-core.install   2025-03-16 
14:53:21.000000000 +0100
+++ initramfs-tools-0.148.1/debian/initramfs-tools-core.install 2025-05-13 
05:22:04.000000000 +0200
@@ -5,4 +5,3 @@
 init                   usr/share/initramfs-tools
 lsinitramfs            usr/bin
 scripts                        usr/share/initramfs-tools
-unmkinitramfs          usr/bin
diff -Nru initramfs-tools-0.147/debian/initramfs-tools-core.manpages 
initramfs-tools-0.148.1/debian/initramfs-tools-core.manpages
--- initramfs-tools-0.147/debian/initramfs-tools-core.manpages  2025-03-16 
14:53:21.000000000 +0100
+++ initramfs-tools-0.148.1/debian/initramfs-tools-core.manpages        
2025-05-13 05:22:04.000000000 +0200
@@ -2,4 +2,3 @@
 initramfs.conf.5
 lsinitramfs.8
 mkinitramfs.8
-unmkinitramfs.8
diff -Nru initramfs-tools-0.147/debian/rules 
initramfs-tools-0.148.1/debian/rules
--- initramfs-tools-0.147/debian/rules  2025-03-25 00:52:03.000000000 +0100
+++ initramfs-tools-0.148.1/debian/rules        2025-05-13 05:22:04.000000000 
+0200
@@ -14,6 +14,7 @@
        dh_gencontrol
 
 override_dh_install:
+       install -d debian/initramfs-tools-core/usr/sbin
        sed -e 's,@BUSYBOX_PACKAGES@,$(wordlist 2,100,$(BUSYBOX_PACKAGES:%=or 
%)),;s,@VERSION@,$(DEB_VERSION),' \
                mkinitramfs > debian/initramfs-tools-core/usr/sbin/mkinitramfs
        chmod 755 debian/initramfs-tools-core/usr/sbin/mkinitramfs
diff -Nru initramfs-tools-0.147/debian/salsa-ci.yml 
initramfs-tools-0.148.1/debian/salsa-ci.yml
--- initramfs-tools-0.147/debian/salsa-ci.yml   2025-03-16 14:53:21.000000000 
+0100
+++ initramfs-tools-0.148.1/debian/salsa-ci.yml 2025-05-13 05:22:04.000000000 
+0200
@@ -4,11 +4,6 @@
 
 variables:
   RELEASE: 'unstable'
-  # We only build arch:all packages
-  SALSA_CI_DISABLE_BLHC: 'true'
-  SALSA_CI_DISABLE_BUILD_PACKAGE_I386: 'true'
-  SALSA_CI_DISABLE_BUILD_PACKAGE_ANY: 'true'
-  SALSA_CI_DISABLE_CROSSBUILD_ARM64: 'true'
 
 shellcheck:
   stage: test
diff -Nru initramfs-tools-0.147/debian/tests/control 
initramfs-tools-0.148.1/debian/tests/control
--- initramfs-tools-0.147/debian/tests/control  2025-03-28 02:04:55.000000000 
+0100
+++ initramfs-tools-0.148.1/debian/tests/control        2025-05-28 
17:33:43.000000000 +0200
@@ -2,6 +2,10 @@
 Restrictions: needs-root, breaks-testbed, skip-not-installable, superficial, 
allow-stderr
 Depends: linux-image-generic, zstd, @
 
+Tests: unmkinitramfs
+Restrictions: superficial
+Depends: bzip2, xz-utils, lzop, lz4, zstd, @
+
 Tests: qemu-klibc
 Architecture: amd64 armhf s390x
 Restrictions: needs-root, breaks-testbed
diff -Nru initramfs-tools-0.147/debian/tests/unmkinitramfs 
initramfs-tools-0.148.1/debian/tests/unmkinitramfs
--- initramfs-tools-0.147/debian/tests/unmkinitramfs    1970-01-01 
01:00:00.000000000 +0100
+++ initramfs-tools-0.148.1/debian/tests/unmkinitramfs  2025-05-28 
20:21:18.000000000 +0200
@@ -0,0 +1,127 @@
+#!/bin/sh
+set -eu
+
+make_one_archive() {
+       local type="$1"
+       local i="$2"
+       local dir_name
+       local j
+
+       case "$type" in
+       early)
+               dir_name=kernel
+               ;;
+       main)
+               dir_name="dir$i"
+               ;;
+       *)
+               echo >&2 "E: Bad archive type $type"
+               exit 1
+               ;;
+       esac
+
+       mkdir -p "$AUTOPKGTEST_TMP/input/$type$i/$dir_name"
+       for j in $(seq 0 9); do
+               size=$((j * 987 + i * 654 + 321))
+               dd if=/dev/urandom 
of="$AUTOPKGTEST_TMP/input/$type$i/$dir_name/file$j" \
+                  bs="$size" count=1 status=none
+       done
+       (
+               cd "$AUTOPKGTEST_TMP/input/$type$i"
+               find . | cpio -H newc -o --quiet > ../"$type$i.cpio"
+       )
+}
+
+# Create input files and archives
+mkdir "$AUTOPKGTEST_TMP/input"
+for in_type in early main; do
+       for i in 0 1 2; do
+               make_one_archive "$in_type" "$i"
+       done
+done
+
+# Dummy compressor; wrapper for cat that ignores its argument (-c)
+no_compressor() {
+       # shellcheck disable=SC2317
+       cat
+}
+
+rc=0
+
+# Test up to 2 early and 2 main cpio archives, and all supported
+# compressors (including none) for the last main archive
+for n_early in 0 1 2; do
+       for n_main in 0 1 2; do
+               # There must be at least 1 archive
+               if [ $((n_early + n_main)) -eq 0 ]; then
+                       continue
+               fi
+
+               for compressor in no_compressor gzip bzip2 xz lzop 'lz4 -l' 
zstd; do
+                       # If last archive is early, it can't be compressed
+                       if [ $n_main -eq 0 ] && [ "$compressor" != 
no_compressor ]; then
+                               continue
+                       fi
+
+                       echo "I: Testing $n_early early + $n_main main 
archive(s) with $compressor"
+
+                       # Construct initramfs image
+                       : > "$AUTOPKGTEST_TMP/initrd.img"
+                       for i in $(seq 0 $((n_early - 1))); do
+                               cat "$AUTOPKGTEST_TMP/input/early$i.cpio" \
+                                   >> "$AUTOPKGTEST_TMP/initrd.img"
+                       done
+                       for i in $(seq 0 $((n_main - 2))); do
+                               cat "$AUTOPKGTEST_TMP/input/main$i.cpio" \
+                                   >> "$AUTOPKGTEST_TMP/initrd.img"
+                       done
+                       if [ $n_main -ge 1 ]; then
+                               $compressor -c < 
"$AUTOPKGTEST_TMP/input/main$((n_main - 1)).cpio" \
+                                           >> "$AUTOPKGTEST_TMP/initrd.img"
+                       fi
+
+                       # Unpack it
+                       rm -rf "$AUTOPKGTEST_TMP/output"
+                       unmkinitramfs "$AUTOPKGTEST_TMP/initrd.img" \
+                                     "$AUTOPKGTEST_TMP/output" \
+                               || {
+                               echo >&2 'E: unmkinitramfs failed'
+                               rc=1
+                               continue
+                       }
+
+                       # Construct what we expect output to look like
+                       rm -rf "$AUTOPKGTEST_TMP/reference"
+                       mkdir "$AUTOPKGTEST_TMP/reference"
+                       for i in $(seq 0 $((n_early - 1))); do
+                               if [ "$i" -eq 0 ]; then
+                                       dir_name=early
+                               else
+                                       dir_name=early$((i + 1))
+                               fi
+                               ln -s ../input/"early$i" \
+                                  "$AUTOPKGTEST_TMP/reference/$dir_name"
+                       done
+                       for i in $(seq 0 $((n_main - 1))); do
+                               if [ $n_early = 0 ]; then
+                                       ln -s ../input/"main$i/dir$i" \
+                                          "$AUTOPKGTEST_TMP/reference/dir$i"
+                               else
+                                       mkdir -p 
"$AUTOPKGTEST_TMP/reference/main"
+                                       ln -s ../../input/"main$i/dir$i" \
+                                          
"$AUTOPKGTEST_TMP/reference/main/dir$i"
+                               fi
+                       done
+
+                       # Compare reference and output
+                       diff -r "$AUTOPKGTEST_TMP/reference" \
+                            "$AUTOPKGTEST_TMP/output" \
+                               || {
+                               echo >&2 'E: Output files do not match input'
+                               rc=1
+                       }
+               done
+       done
+done
+
+exit $rc
diff -Nru initramfs-tools-0.147/unmkinitramfs 
initramfs-tools-0.148.1/unmkinitramfs
--- initramfs-tools-0.147/unmkinitramfs 2025-03-25 00:52:03.000000000 +0100
+++ initramfs-tools-0.148.1/unmkinitramfs       1970-01-01 01:00:00.000000000 
+0100
@@ -1,219 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-usage()
-{
-       cat << EOF
-
-Usage: unmkinitramfs [-v] initramfs-file directory
-
-Options:
-  -v   Display verbose messages about extraction
-
-See unmkinitramfs(8) for further details.
-
-EOF
-}
-
-usage_error()
-{
-       usage >&2
-       exit 2
-}
-
-# Extract a compressed cpio archive
-xcpio()
-{
-       archive_uncomp="$1"
-       archive="$2"
-       dir="$3"
-       shift 3
-
-       {
-               cat "$archive_uncomp"
-               if gzip -t "$archive" >/dev/null 2>&1 ; then
-                       gzip -c -d "$archive"
-               elif zstd -q -c -t "$archive" >/dev/null 2>&1 ; then
-                       zstd -q -c -d "$archive"
-               elif xzcat -t "$archive" >/dev/null 2>&1 ; then
-                       xzcat "$archive"
-               elif lz4cat -t < "$archive" >/dev/null 2>&1 ; then
-                       lz4cat "$archive"
-               elif bzip2 -t "$archive" >/dev/null 2>&1 ; then
-                       bzip2 -c -d "$archive"
-               elif lzop -t "$archive" >/dev/null 2>&1 ; then
-                       lzop -c -d "$archive"
-               # Ignoring other data, which may be garbage at the end of the 
file
-               fi
-       } | (
-               if [ -n "$dir" ]; then
-                       mkdir -p -- "$dir"
-                       cd -- "$dir"
-               fi
-               cpio "$@"
-       )
-}
-
-# Read bytes out of a file, checking that they are valid hex digits
-readhex()
-{
-       dd < "$1" bs=1 skip="$2" count="$3" 2> /dev/null | \
-               LANG=C grep -E "^[0-9A-Fa-f]{$3}\$"
-}
-
-# Check for a zero byte in a file
-checkzero()
-{
-       dd < "$1" bs=1 skip="$2" count=1 2> /dev/null | \
-               LANG=C grep -q -z '^$'
-}
-
-# Split an initramfs into archives and run cpio/xcpio to extract them
-splitinitramfs()
-{
-       initramfs="$1"
-       dir="$2"
-       shift 2
-
-       # Ensure this exists so we can use it unconditionally later
-       touch "$tempdir/main-uncomp.cpio"
-
-       count=0
-       start=0
-       while true; do
-               # There may be prepended uncompressed archives.  cpio
-               # won't tell us the true size of these so we have to
-               # parse the headers and padding ourselves.  This is
-               # very roughly based on linux/lib/earlycpio.c
-               end=$start
-               while true; do
-                       headoff=$end
-                       magic="$(readhex "$initramfs" $end 6)" || break
-                       test "$magic" = 070701 || test "$magic" = 070702 || 
break
-                       namesize=$((0x$(readhex "$initramfs" $((end + 94)) 8)))
-                       filesize=$((0x$(readhex "$initramfs" $((end + 54)) 8)))
-                       nameoff=$((end + 110))
-                       end=$(((nameoff + namesize + 3) & ~3))
-                       end=$(((end + filesize + 3) & ~3))
-
-                       # Check for EOF marker.  Note that namesize
-                       # includes a null terminator.
-                       if [ $namesize = 11 ] \
-                          && name="$(dd if="$initramfs" bs=1 skip=$nameoff 
count=$((namesize - 1)) 2> /dev/null)" \
-                          && [ "$name" = 'TRAILER!!!' ]; then
-                               # There might be more zero padding
-                               # before the next archive, so read
-                               # through all of it.
-                               while checkzero "$initramfs" $end; do
-                                       end=$((end + 4))
-                               done
-                               break
-                       fi
-               done
-
-               if [ $end -eq $start ]; then
-                       break
-               fi
-
-               # Check whether this should be treated as an "early"
-               # or "main" initramfs.  Currently all filenames the
-               # kernel looks for in an early initramfs begin with
-               # kernel/ subdirectory, but we should never create
-               # this in the main initramfs.
-               if dd < "$initramfs" skip=$start count=$((end - start)) \
-                       iflag=skip_bytes,count_bytes 2> /dev/null |
-                  cpio -i --list 2> /dev/null |
-                  grep -q ^kernel/; then
-                       # Extract to early, early2, ... subdirectories
-                       count=$((count + 1))
-                       if [ $count -eq 1 ]; then
-                               subdir=early
-                       else
-                               subdir=early$count
-                       fi
-                       dd < "$initramfs" skip=$start count=$((end - start)) \
-                               iflag=skip_bytes,count_bytes 2> /dev/null |
-                       (
-                               if [ -n "$dir" ]; then
-                                       mkdir -p -- "$dir/$subdir"
-                                       cd -- "$dir/$subdir"
-                               fi
-                               cpio -i "$@"
-                       )
-               else
-                       # Append to main-uncomp.cpio, excluding the
-                       # trailer so cpio won't stop before the
-                       # (de)compressed part.
-                       dd < "$initramfs" skip=$start \
-                               count=$((headoff - start)) \
-                               iflag=skip_bytes,count_bytes \
-                               >> "$tempdir/main-uncomp.cpio" 2> /dev/null
-               fi
-
-               start=$end
-       done
-
-       # Split out final archive if necessary
-       if [ "$end" -gt 0 ]; then
-               subarchive="$tempdir/main-comp.cpio"
-               dd < "$initramfs" skip="$end" iflag=skip_bytes 2> /dev/null \
-                       > "$subarchive"
-       else
-               subarchive="$initramfs"
-       fi
-
-       # If we found an early initramfs, extract main initramfs to
-       # main subdirectory.  Otherwise don't use a subdirectory (for
-       # backward compatibility).
-       if [ "$count" -gt 0 ]; then
-               subdir=main
-       else
-               subdir=.
-       fi
-
-       xcpio "$tempdir/main-uncomp.cpio" "$subarchive" \
-               "${dir:+$dir/$subdir}" -i "$@"
-}
-
-OPTIONS=$(getopt -o hv --long help,list,verbose -n "$0" -- "$@") || usage_error
-
-cpio_opts="--preserve-modification-time --no-absolute-filenames --quiet"
-expected_args=2
-eval set -- "$OPTIONS"
-
-while true; do
-       case "$1" in
-        -h|--help)
-               usage
-               exit 0
-       ;;
-       --list)
-               # For lsinitramfs
-               cpio_opts="${cpio_opts:+${cpio_opts} --list}"
-               expected_args=1
-               shift
-       ;;
-       -v|--verbose)
-               cpio_opts="${cpio_opts:+${cpio_opts} --verbose}"
-               shift
-       ;;
-       --)
-               shift
-               break
-       ;;
-       *)
-               echo "Internal error!" >&2
-               exit 1
-       esac
-done
-
-if [ $# -ne $expected_args ]; then
-       usage_error
-fi
-
-tempdir="$(mktemp -d "${TMPDIR:-/var/tmp}/unmkinitramfs_XXXXXX")"
-trap 'rm -rf "$tempdir"' EXIT
-
-# shellcheck disable=SC2086
-splitinitramfs "$1" "${2:-}" $cpio_opts
diff -Nru initramfs-tools-0.147/unmkinitramfs.8 
initramfs-tools-0.148.1/unmkinitramfs.8
--- initramfs-tools-0.147/unmkinitramfs.8       2025-03-25 00:52:03.000000000 
+0100
+++ initramfs-tools-0.148.1/unmkinitramfs.8     2025-05-28 20:21:18.000000000 
+0200
@@ -18,8 +18,8 @@
 and the appropriate decompressor command.
 
 The initramfs image may be a single compressed cpio archive, or the
-concatenation of any number of uncompressed cpio archives followed by
-a compressed cpio archive.
+concatenation of any number of uncompressed cpio archives optionally
+followed by a compressed cpio archive.
 
 When the initramfs image includes one or more "early initramfs"
 archives, that is uncompressed archives with microcode or other files
@@ -51,8 +51,8 @@
 
 .SH BUGS
 .BR unmkinitramfs
-does not support initramfs images with more or less than one
-compressed cpio archive.
+does not support initramfs images with more than one compressed cpio
+archive.
 
 .SH AUTHOR
 The initramfs-tools are written by Maximilian Attems <m...@debian.org>
diff -Nru initramfs-tools-0.147/unmkinitramfs.c 
initramfs-tools-0.148.1/unmkinitramfs.c
--- initramfs-tools-0.147/unmkinitramfs.c       1970-01-01 01:00:00.000000000 
+0100
+++ initramfs-tools-0.148.1/unmkinitramfs.c     2025-05-28 20:21:18.000000000 
+0200
@@ -0,0 +1,698 @@
+/* unmkinitramfs: Unpack an initramfs */
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <err.h>
+#include <getopt.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+/*
+ * The "new" cpio header supported by the kernel.  All fields after
+ * magic hold 32-bit values in hexadecimal.  This is immediately
+ * followed by the name, then contents.
+ */
+struct cpio_new {
+       char    c_magic[6];
+       char    c_ino[8];
+       char    c_mode[8];
+       char    c_uid[8];
+       char    c_gid[8];
+       char    c_nlink[8];
+       char    c_mtime[8];
+       char    c_filesize[8];
+       char    c_dev_maj[8];
+       char    c_dev_min[8];
+       char    c_rdev_maj[8];
+       char    c_rdev_min[8];
+       char    c_namesize[8];
+       char    c_chksum[8];
+} __attribute__((packed));
+
+#define CPIO_NEW_MAGIC         "070701"
+#define CPIO_NEW_CRC_MAGIC     "070702"
+#define CPIO_MAGIC_LEN         6
+
+/* Name of the last entry in a cpio archive */
+#define CPIO_NAME_TRAILER      "TRAILER!!!"
+
+struct cpio_entry {
+       const char *    name;
+       off_t           start, end;
+};
+
+struct cpio_handle {
+       FILE *          file;
+       const char *    name;
+       off_t           next_off;
+       char            name_buf[PATH_MAX];
+};
+
+struct cpio_proc {
+       int     pid;
+       int     pipe;
+};
+
+enum format {
+       FORMAT_CPIO_NEW,
+       FORMAT_GZIP,
+       FORMAT_BZIP2,
+       FORMAT_XZ,
+       FORMAT_LZO,
+       FORMAT_LZ4,
+       FORMAT_ZSTD,
+};
+
+#define MAX_MAGIC_LEN          12
+
+struct magic_entry {
+       unsigned int            magic_len;
+       unsigned char           magic[MAX_MAGIC_LEN];
+       enum format             format;
+};
+
+#define MAGIC_ENTRY(magic, format)     { sizeof(magic) - 1, magic, format }
+
+static const struct magic_entry magic_table[] = {
+       MAGIC_ENTRY(CPIO_NEW_MAGIC,             FORMAT_CPIO_NEW),
+       MAGIC_ENTRY(CPIO_NEW_CRC_MAGIC,         FORMAT_CPIO_NEW),
+       MAGIC_ENTRY("\x1f\x8b\x08",             FORMAT_GZIP),
+       MAGIC_ENTRY("BZh",                      FORMAT_BZIP2),
+       MAGIC_ENTRY("\xfd""7zXZ\0",             FORMAT_XZ),
+       MAGIC_ENTRY("\x89LZO\0\r\n\x1a\n",      FORMAT_LZO),
+       /* lz4 "legacy" format, the only version that the kernel supports */
+       MAGIC_ENTRY("\x02\x21\x4c\x18",         FORMAT_LZ4),
+       MAGIC_ENTRY("\x28\xb5\x2f\xfd",         FORMAT_ZSTD),
+       { 0 }
+};
+
+static const char *const decomp_table[][2] = {
+       [FORMAT_GZIP] =         { "gzip", "-cd" },
+       [FORMAT_BZIP2] =        { "bzip2", "-cd" },
+       [FORMAT_XZ] =           { "xzcat" },
+       [FORMAT_LZO] =          { "lzop", "-cd" },
+       [FORMAT_LZ4] =          { "lz4cat" },
+       [FORMAT_ZSTD] =         { "zstd", "-cdq" },
+};
+
+/* mkdir() but return success if name already exists as directory */
+static bool mkdir_allow_exist(const char *name, mode_t mode)
+{
+       struct stat st;
+       int orig_errno;
+
+       if (mkdir(name, mode) == 0)
+               return true;
+
+       orig_errno = errno;
+       if (orig_errno == EEXIST && stat(name, &st) == 0 && S_ISDIR(st.st_mode))
+               return true;
+
+       errno = orig_errno;
+       return false;
+}
+
+/* write() with loop in case of partial writes */
+static bool write_all(int fd, const void *buf, size_t len)
+{
+       size_t pos;
+       ssize_t ret;
+
+       pos = 0;
+       do {
+               ret = write(fd, (const char *)buf + pos, len - pos);
+               if (ret < 0)
+                       return false;
+               pos += ret;
+       } while (pos < len);
+
+       return true;
+}
+
+/*
+ * Warn about failure of fread.  This may be due to a file error
+ * reported in errno, or EOF which is not.
+ */
+static void warn_after_fread_failure(FILE *file, const char *name)
+{
+       if (ferror(file))
+               warn("%s", name);
+       else
+               warnx("%s: unexpected EOF", name);
+}
+
+/*
+ * Parse one of the hexadecimal fields.  Don't use strtoul() because
+ * it requires null-termination.
+ */
+static bool cpio_parse_hex(const char *field, uint32_t *value_p)
+{
+       const char digits[] = "0123456789ABCDEF", *p;
+       uint32_t value = 0;
+       unsigned int i;
+       bool found_digit = false;
+
+       /* Skip leading spaces */
+       for (i = 0; i < 8 && field[i] == ' '; ++i)
+               ;
+
+       /* Parse digits up to end of field or null */
+       for (; i < 8 && field[i] != 0; ++i) {
+               p = strchr(digits, field[i]);
+               if (!p)
+                       return false;
+               value = (value << 4) | (p - digits);
+               found_digit = true;
+       }
+
+       *value_p = value;
+       return found_digit;
+}
+
+/* Align offset of file contents or header */
+static off_t cpio_align(off_t off)
+{
+       return (off + 3) & ~3;
+}
+
+static struct cpio_handle *cpio_open(FILE *file, const char *name)
+{
+       struct cpio_handle *cpio;
+
+       cpio = calloc(1, sizeof(*cpio));
+       if (!cpio)
+               return NULL;
+
+       cpio->file = file;
+       cpio->name = name;
+       cpio->next_off = ftell(file);
+       return cpio;
+}
+
+/*
+ * Read next cpio header and name.
+ * Return:
+ * -1 on error
+ *  0 if entry is trailer
+ *  1 if entry is anything else
+ */
+static int cpio_get_next(struct cpio_handle *cpio, struct cpio_entry *entry)
+{
+       struct cpio_new header;
+       uint32_t file_size, name_size;
+
+       if (fseek(cpio->file, cpio->next_off, SEEK_SET) < 0 ||
+           fread(&header, sizeof(header), 1, cpio->file) != 1) {
+               warn_after_fread_failure(cpio->file, cpio->name);
+               return -1;
+       }
+
+       if ((memcmp(header.c_magic, CPIO_NEW_MAGIC, CPIO_MAGIC_LEN) != 0 &&
+            memcmp(header.c_magic, CPIO_NEW_CRC_MAGIC, CPIO_MAGIC_LEN) != 0) ||
+           !cpio_parse_hex(header.c_filesize, &file_size) ||
+           !cpio_parse_hex(header.c_namesize, &name_size)) {
+               warnx("%s: cpio archive has invalid header", cpio->name);
+               return -1;
+       }
+
+       entry->name = cpio->name_buf;
+       entry->start = cpio->next_off;
+
+       /* Calculate offset of the next header */
+       cpio->next_off = cpio_align(
+               cpio_align(cpio->next_off + sizeof(header) + name_size)
+               + file_size);
+       entry->end = cpio->next_off;
+
+       if (name_size > sizeof(cpio->name_buf)) {
+               warnx("%s: cpio member name is too long", cpio->name);
+               return -1;
+       }
+
+       if (fread(cpio->name_buf, name_size, 1, cpio->file) != 1) {
+               warn_after_fread_failure(cpio->file, cpio->name);
+               return -1;
+       }
+
+       if (name_size == 0 || cpio->name_buf[name_size - 1] != 0) {
+               warnx("%s: cpio member name is invalid", cpio->name);
+               return -1;
+       }
+
+       return strcmp(entry->name, CPIO_NAME_TRAILER) != 0;
+}
+
+static void cpio_close(struct cpio_handle *cpio)
+{
+       free(cpio);
+}
+
+static bool detect_early_initramfs(FILE *in_file, const char *in_filename)
+{
+       struct cpio_handle *cpio;
+       struct cpio_entry entry;
+       bool ret = false;
+       off_t start = ftell(in_file);
+
+       cpio = cpio_open(in_file, in_filename);
+       if (!cpio)
+               return false;
+
+       while (cpio_get_next(cpio, &entry) > 0) {
+               if (strncmp(entry.name, "kernel/", 7) == 0) {
+                       ret = true;
+                       break;
+               }
+       }
+
+       cpio_close(cpio);
+
+       fseek(in_file, start, SEEK_SET);
+       return ret;
+}
+
+static bool copy_to_pipe(FILE *in_file, const char *in_filename,
+                        off_t start, off_t end, int out_pipe)
+{
+       char buf[0x10000];
+       off_t in_pos;
+       size_t want_len, read_len;
+
+       /* Set input position */
+       fseek(in_file, start, SEEK_SET);
+       in_pos = start;
+
+       while (in_pos < end) {
+               /* How much do we want to copy? */
+               want_len = sizeof(buf);
+               if ((ssize_t)want_len > end - in_pos)
+                       want_len = end - in_pos;
+
+               /* Read to buffer; update input position */
+               read_len = fread(buf, 1, want_len, in_file);
+               if (!read_len) {
+                       warn_after_fread_failure(in_file, in_filename);
+                       return false;
+               }
+               in_pos += read_len;
+
+               /* Write to pipe */
+               if (!write_all(out_pipe, buf, read_len)) {
+                       warn("pipe write");
+                       return false;
+               }
+       }
+
+       return true;
+}
+
+static bool handle_uncompressed(FILE *in_file, const char *in_filename,
+                               int out_pipe)
+{
+       struct cpio_handle *cpio;
+       struct cpio_entry entry;
+       uint32_t pad;
+       int ret;
+
+       cpio = cpio_open(in_file, in_filename);
+       if (!cpio)
+               return false;
+
+       while ((ret = cpio_get_next(cpio, &entry)) > 0) {
+               if (!copy_to_pipe(in_file, in_filename, entry.start, entry.end,
+                                 out_pipe)) {
+                       ret = -1;
+                       break;
+               }
+       }
+
+       cpio_close(cpio);
+
+       if (ret < 0)
+               return false;
+
+       /* Skip trailer and any zero padidng */
+       fseek(in_file, entry.end, SEEK_SET);
+       while (fread(&pad, sizeof(pad), 1, in_file)) {
+               if (pad != 0) {
+                       fseek(in_file, -sizeof(pad), SEEK_CUR);
+                       break;
+               }
+       }
+
+       return true;
+}
+
+static bool handle_compressed(FILE *in_file, enum format format, int out_pipe)
+{
+       const char *const *argv = decomp_table[format];
+       int in_fd = fileno(in_file);
+       off_t in_pos = ftell(in_file);
+       int pid, wstatus;
+
+       pid = fork();
+       if (pid < 0)
+               return false;
+
+       /* Child */
+       if (pid == 0) {
+               /*
+                * Make in_file stdin.  Reset the position of the file
+                * descriptor because stdio will have read-ahead from
+                * the position it reported.
+                */
+               dup2(in_fd, 0);
+               close(in_fd);
+               lseek(0, in_pos, SEEK_SET);
+
+               /* Make out_pipe stdout */
+               dup2(out_pipe, 1);
+               close(out_pipe);
+
+               execlp(argv[0], argv[0], argv[1], NULL);
+               _exit(127);
+       }
+
+       /* Parent: wait for child */
+       if (waitpid(pid, &wstatus, 0) != pid ||
+           !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
+               warnx("%s failed", argv[0]);
+               return false;
+       }
+       return true;
+}
+
+static bool write_trailer(int out_pipe)
+{
+       struct {
+               struct cpio_new header;
+               char name[sizeof(CPIO_NAME_TRAILER)];
+               char pad[-(sizeof(struct cpio_new) + sizeof(CPIO_NAME_TRAILER))
+                        & 3];
+       } __attribute__((packed)) trailer;
+       char name_size[8 + 1];
+
+       static_assert((sizeof(trailer) & 3) == 0, "pad miscalculated");
+
+       memset(&trailer.header, '0', sizeof(trailer.header));
+       memcpy(trailer.header.c_magic, CPIO_NEW_MAGIC, CPIO_MAGIC_LEN);
+       sprintf(name_size, "%08zX", sizeof(CPIO_NAME_TRAILER));
+       memcpy(trailer.header.c_namesize, name_size,
+              sizeof(trailer.header.c_namesize));
+
+       strcpy(trailer.name, CPIO_NAME_TRAILER);
+
+       memset(&trailer.pad, 0, sizeof(trailer.pad));
+
+       if (!write_all(out_pipe, &trailer, sizeof(trailer))) {
+               warn("pipe write");
+               return false;
+       }
+
+       return true;
+}
+
+static bool spawn_cpio(int optc, const char **optv, struct cpio_proc *proc)
+{
+       const char *argv[10];
+       int pipe_fds[2], pid;
+       size_t argc;
+
+       /* Combine base cpio command with extra options */
+       argc = 0;
+       argv[argc++] = "cpio";
+       argv[argc++] = "-i";
+       argv[argc++] = "--preserve-modification-time";
+       argv[argc++] = "--no-absolute-filenames";
+       argv[argc++] = "--quiet";
+       assert(argc + optc < sizeof(argv) / sizeof(argv[0]));
+       while (optc--)
+               argv[argc++] = *optv++;
+       argv[argc] = NULL;
+
+       if (pipe(pipe_fds)) {
+               warn("pipe");
+               return false;
+       }
+       pid = fork();
+       if (pid < 0) {
+               warn("fork");
+               return false;
+       }
+
+       /* Child */
+       if (pid == 0) {
+               /*
+                * Close write end of the pipe; make the read end
+                * stdout.
+                */
+               close(pipe_fds[1]);
+               dup2(pipe_fds[0], 0);
+               close(pipe_fds[0]);
+
+               execvp("cpio", (char **)argv);
+               _exit(127);
+       }
+
+       /*
+        * Parent: close read end of the pipe; return child pid and
+        * write end of pipe.
+        */
+       close(pipe_fds[0]);
+       proc->pid = pid;
+       proc->pipe = pipe_fds[1];
+       return true;
+}
+
+static bool end_cpio(const struct cpio_proc *proc, bool ok)
+{
+       int wstatus;
+
+       close(proc->pipe);
+
+       if (ok) {
+               if (waitpid(proc->pid, &wstatus, 0) != proc->pid ||
+                   !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {
+                       warnx("cpio failed");
+                       return false;
+               }
+       } else {
+               kill(proc->pid, SIGTERM);
+       }
+
+       return true;
+}
+
+static const struct option long_opts[] = {
+       { "help",       no_argument,    NULL,   'h' },
+       { "list",       no_argument,    NULL,   'l' },
+       { "verbose",    no_argument,    NULL,   'v' },
+       { NULL }
+};
+
+static void usage(FILE *stream)
+{
+       fprintf(stream, "\
+\n\
+Usage: unmkinitramfs [-v|--verbose] INITRAMFS-FILE DIRECTORY\n\
+\n\
+Options:\n\
+  -v | --verbose   Display verbose messages about extraction\n\
+\n\
+See unmkinitramfs(8) for further details.\n\
+\n"
+               );
+}
+
+int main(int argc, char **argv)
+{
+       int opt;
+       bool do_list = false;
+       bool verbose = false;
+       const char *in_filename;
+       FILE *in_file;
+       const char *out_dirname = NULL;
+       char *out_subdirname = NULL;
+       const char *cpio_optv[3];
+       int cpio_optc;
+       struct cpio_proc cpio_proc = { 0 };
+       unsigned int early_count = 0;
+       bool ok;
+
+       /* Parse options */
+       opterr = 0;
+       while ((opt = getopt_long(argc, argv, "hv", long_opts, NULL)) >= 0) {
+               switch (opt) {
+               case '?':
+                       usage(stderr);
+                       return 2;
+               case 'h':
+                       usage(stdout);
+                       return 0;
+               case 'l':
+                       do_list = true;
+                       break;
+               case 'v':
+                       verbose = true;
+                       break;
+               }
+       }
+
+       /* Check number of non-option arguments */
+       if (argc - optind != (do_list ? 1 : 2)) {
+               usage(stderr);
+               return 2;
+       }
+
+       /* Set up input file and output directory */
+       in_filename = argv[optind];
+       in_file = fopen(in_filename, "rb");
+       if (!in_file)
+               err(1, "%s", in_filename);
+       if (!do_list) {
+               out_dirname = argv[optind + 1];
+               if (!mkdir_allow_exist(out_dirname, 0777))
+                       err(1, "%s", out_dirname);
+               out_subdirname = malloc(strlen(out_dirname) + 20);
+               if (!out_subdirname)
+                       err(1, "malloc");
+       }
+
+       /* Set up extra options for cpio */
+       cpio_optc = 0;
+       if (do_list) {
+               cpio_optv[cpio_optc++] = "--list";
+       } else {
+               cpio_optv[cpio_optc++] = "-D";
+               cpio_optv[cpio_optc++] = out_subdirname;
+       }
+       if (verbose)
+               cpio_optv[cpio_optc++] = "-v";
+
+       /* Iterate over archives within the initramfs */
+       for (;;) {
+               unsigned char magic_buf[MAX_MAGIC_LEN];
+               size_t read_len;
+               const struct magic_entry *me;
+
+               /* Peek at first bytes of next archive; handle EOF */
+               read_len = fread(magic_buf, 1, sizeof(magic_buf), in_file);
+               if (read_len == 0) {
+                       /*
+                        * EOF with no compresed archive.  Add back a
+                        * trailer to keep cpio happy.
+                        */
+                       if (ferror(in_file)) {
+                               warn("%s", in_filename);
+                               ok = false;
+                       }
+                       if (cpio_proc.pid && !write_trailer(cpio_proc.pipe))
+                               ok = false;
+                       break;
+               }
+               fseek(in_file, -(long)read_len, SEEK_CUR);
+
+               /* Identify format */
+               for (me = magic_table; me->magic_len; ++me)
+                       if (read_len >= me->magic_len &&
+                           memcmp(magic_buf, me->magic, me->magic_len) == 0)
+                               break;
+               if (me->magic_len == 0) {
+                       warnx("%s: unrecognised compression or corrupted file",
+                             in_filename);
+                       ok = false;
+                       break;
+               }
+
+               /*
+                * Extract the archive to an "early" or "early<n>"
+                * subdirectory if:
+                * - We have not already started the main cpio process
+                * - We are not listing
+                * - This looks like an early initramfs (uncompressed
+                *   and contains files under kernel/)
+                */
+               if (!cpio_proc.pid && !do_list &&
+                   me->format == FORMAT_CPIO_NEW &&
+                   detect_early_initramfs(in_file, in_filename)) {
+                       struct cpio_proc early_cpio_proc;
+
+                       if (++early_count == 1)
+                               sprintf(out_subdirname, "%s/early",
+                                       out_dirname);
+                       else
+                               sprintf(out_subdirname, "%s/early%u",
+                                       out_dirname, early_count);
+                       if (!mkdir_allow_exist(out_subdirname, 0777)) {
+                               warn("%s", out_subdirname);
+                               ok = false;
+                               break;
+                       }
+                       if (!spawn_cpio(cpio_optc, cpio_optv,
+                                       &early_cpio_proc)) {
+                               ok = false;
+                               break;
+                       }
+                       ok = handle_uncompressed(in_file, in_filename,
+                                                early_cpio_proc.pipe)
+                               && write_trailer(early_cpio_proc.pipe);
+                       if (!end_cpio(&early_cpio_proc, ok))
+                               ok = false;
+                       if (!ok)
+                               break;
+               } else {
+                       /*
+                        * Otherwise, extract to either the base
+                        * output directory or a "main" subdirectory,
+                        * depending on whether we already created
+                        * subdirectories.
+                        */
+                       if (!cpio_proc.pid) {
+                               if (do_list) {
+                                       ;
+                               } else if (early_count) {
+                                       sprintf(out_subdirname, "%s/main",
+                                               out_dirname);
+                                       if (!mkdir_allow_exist(out_subdirname,
+                                                              0777)) {
+                                               warn("%s", out_subdirname);
+                                               ok = false;
+                                               break;
+                                       }
+                               } else {
+                                       strcpy(out_subdirname, out_dirname);
+                               }
+                               if (!spawn_cpio(cpio_optc, cpio_optv,
+                                               &cpio_proc)) {
+                                       ok = false;
+                                       break;
+                               }
+                       }
+                       if (me->format == FORMAT_CPIO_NEW) {
+                               ok = handle_uncompressed(in_file, in_filename,
+                                                        cpio_proc.pipe);
+                               if (!ok)
+                                       break;
+                       } else {
+                               ok = handle_compressed(in_file, me->format,
+                                                      cpio_proc.pipe);
+                               break;
+                       }
+               }
+       }
+
+       fclose(in_file);
+
+       if (cpio_proc.pid && !end_cpio(&cpio_proc, ok))
+               ok = false;
+
+       return !ok;
+}

--- End Message ---
--- Begin Message ---
Unblocked initramfs-tools.

--- End Message ---

Reply via email to