diff --git a/unmkinitramfs b/unmkinitramfs index 2258e69..9bce38a 100755 --- a/unmkinitramfs +++ b/unmkinitramfs @@ -25,24 +25,28 @@ usage_error() # Extract a compressed cpio archive xcpio() { - archive="$1" - dir="$2" - shift 2 + archive_uncomp="$1" + archive="$2" + dir="$3" + shift 3 - 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 | ( + { + 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" @@ -65,13 +69,16 @@ checkzero() LANG=C grep -q -z '^$' } -# Split an initramfs into archives and call xcpio on each +# 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 @@ -81,55 +88,92 @@ splitinitramfs() # very roughly based on linux/lib/earlycpio.c end=$start while true; do - if checkzero "$initramfs" $end; then - # This is the EOF marker. There might - # be more zero padding before the next - # archive, so read through all of it. - end=$((end + 4)) + 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 - 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) - end=$((end + 110)) - end=$(((end + namesize + 3) & ~3)) - end=$(((end + filesize + 3) & ~3)) done + if [ $end -eq $start ]; then break fi - # Extract to cpio1, cpio2, ... subdirectories - count=$((count + 1)) - subdir=cpio$count - 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" + # 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 - cpio -i "$@" - ) + 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 - # Extract to main subdirectory - subarchive=$(mktemp "${TMPDIR:-/var/tmp}/unmkinitramfs_XXXXXX") - trap 'rm -f "$subarchive"' EXIT + subarchive="$tempdir/main-comp.cpio" dd < "$initramfs" skip="$end" iflag=skip_bytes 2> /dev/null \ > "$subarchive" - count=$((count + 1)) - subdir=cpio$count - xcpio "$subarchive" "${dir:+$dir/$subdir}" -i "$@" else - # Don't use subdirectories (for backward compatibility) - xcpio "$initramfs" "$dir" -i "$@" + 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 @@ -168,5 +212,8 @@ 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 --git a/unmkinitramfs.8 b/unmkinitramfs.8 index ae9252d..d0965af 100644 --- a/unmkinitramfs.8 +++ b/unmkinitramfs.8 @@ -1,4 +1,4 @@ -.TH UNMKINITRAMFS 8 "2016/12/15" "initramfs\-tools" "System Administration" +.TH UNMKINITRAMFS 8 "2025/03/20" "initramfs\-tools" "System Administration" .SH NAME unmkinitramfs \- extract content from an initramfs image @@ -14,10 +14,21 @@ unmkinitramfs \- extract content from an initramfs image The .B unmkinitramfs command extracts the content of a given initramfs image using -.BR cpio . -If the image contains multiple segments, each are passed to .B cpio -in order. +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. + +When the initramfs image includes one or more "early initramfs" +archives, that is uncompressed archives with microcode or other files +that the kernel uses directly, these are extracted to sub\-directories +named "\fBearly\fR", "\fBearly2\fR", etc., and the other archives are +extracted to a sub\-directory named "\fBmain\fR". + +In a future version of initramfs\-tools this behaviour may change so +that all archives are extracted directly into the given directory. .SH OPTIONS @@ -40,9 +51,8 @@ Extract initramfs content of current running kernel: .SH BUGS .BR unmkinitramfs -cannot deal with multiple-segmented initramfs images, except where an -early (uncompressed) initramfs with system firmware is prepended to -the regular compressed initramfs. +does not support initramfs images with more or less than one +compressed cpio archive. .SH AUTHOR The initramfs-tools are written by Maximilian Attems