diff options
author | Morten Linderud <morten@linderud.pw> | 2023-10-20 22:04:26 +0200 |
---|---|---|
committer | Morten Linderud <morten@linderud.pw> | 2023-10-20 22:04:26 +0200 |
commit | dfc30b91ea5d29c183bc5a1cfd5cadf297728253 (patch) | |
tree | 099ed856d49a5ca29f0ed0ace8e74acfe6e1c50b | |
parent | 8ea759c229245fc7056bc843f25283ccc488233f (diff) | |
parent | 73d806fa91586ff932a2586ecca4a8d625ae2d27 (diff) |
Merge remote-tracking branch 'origin/merge-requests/187'
* origin/merge-requests/187:
tests: add test case for kver_zimage
Get kernel version from generic EFI zboot image for bash completion
Get kernel version from generic EFI zboot image
-rw-r--r-- | functions | 73 | ||||
-rw-r--r-- | shell/bash-completion | 165 | ||||
-rw-r--r-- | test/cases/functions.bats | 76 | ||||
-rw-r--r-- | test/helpers/common.bash | 79 |
4 files changed, 376 insertions, 17 deletions
@@ -210,9 +210,71 @@ detect_compression() { return fi + read -rd '' bytes < <(od -An -j0x04 -t c -N4 "$1" | tr -dc '[:alnum:]') + if [[ "$bytes" == 'zimg' ]]; then + echo 'zimg' + return + fi + # out of ideas, assuming uncompressed } +kver_zimage() { + # Generic EFI zboot added since kernel 6.1 + # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/firmware/efi/libstub/Makefile.zboot?h=v6.1 + # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/firmware/efi/libstub/zboot-header.S?h=v6.1 + + local kver='' reader start size comp_type + + # Reading 4 bytes from address 0x08 is the starting offset of compressed data + start="$(od -An -j0x08 -t u4 -N4 "$1" | tr -dc '[:alnum:]')" + + # Reading 4 bytes from address 0x0c is the size of compressed data, + # but it needs to be corrected according to the compressed type. + size="$(od -An -j0x0c -t u4 -N4 "$1" | tr -dc '[:alnum:]')" + + # Read 36 bytes (before 0x3c) from address 0x18, + # which is a nul-terminated string representing the compressed type. + read -rd '' comp_type < <(od -An -j0x18 -t a -N32 "$1" | sed 's/ nul//g' | tr -dc '[:alnum:]') + + [[ "$start" =~ ^[0-9]+$ ]] || return 1 + [[ "$size" =~ ^[0-9]+$ ]] || return 1 + + case "$comp_type" in + 'gzip') + reader='zcat' + ;; + 'lz4') + reader='lz4cat' + size="$((size + 4))" + ;; + 'lzma') + reader='xzcat' + size="$((size + 4))" + ;; + 'lzo') + reader="lzop -d" + size="$((size + 4))" + ;; + 'xzkern') + reader='xzcat' + size="$((size + 4))" + ;; + 'zstd22') + reader='zstdcat' + size="$((size + 4))" + ;; + *) + reader="$comp_type" + size="$((size + 4))" + ;; + esac + + read -r _ _ kver _ < <(dd if="$1" bs=1 count="$size" skip="$start" 2>/dev/null | $reader - | grep -m1 -aoE 'Linux version .(\.[-[:alnum:]+]+)+') + + printf '%s' "$kver" +} + kver_generic() { # For unknown architectures, we can try to grep the uncompressed or gzipped # image for the boot banner. @@ -223,6 +285,17 @@ kver_generic() { # Loosely grep for `linux_banner`: # https://elixir.bootlin.com/linux/v5.7.2/source/init/version.c#L46 local kver='' reader='cat' + local comp_type='' + + comp_type="$(detect_compression "$1")" + + if [[ "$comp_type" == 'zimg' ]]; then + # Generic EFI zboot image + kver_zimage "$1" + return 0 + elif [[ "$comp_type" == 'gzip' ]]; then + reader='zcat' + fi [[ "$(detect_compression "$1")" == 'gzip' ]] && reader='zcat' diff --git a/shell/bash-completion b/shell/bash-completion index 80ceef9..850c4b9 100644 --- a/shell/bash-completion +++ b/shell/bash-completion @@ -1,12 +1,169 @@ #!/usr/bin/env bash # SPDX-License-Identifier: GPL-2.0-only +_detect_compression() { + local bytes + + bytes="$(od -An -t x1 -N6 "$1" | tr -dc '[:alnum:]')" + case "$bytes" in + 'fd377a585a00') + echo 'xz' + return + ;; + esac + + bytes="$(od -An -t x1 -N4 "$1" | tr -dc '[:alnum:]')" + if [[ "$bytes" == '894c5a4f' ]]; then + echo 'lzop' + return + fi + + bytes="$(od -An -t x2 -N2 "$1" | tr -dc '[:alnum:]')" + if [[ "$bytes" == '8b1f' ]]; then + echo 'gzip' + return + fi + + bytes="$(od -An -t x4 -N4 "$1" | tr -dc '[:alnum:]')" + case "$bytes" in + '184d2204') + error 'Newer lz4 stream format detected! This may not boot!' + echo 'lz4' + return + ;; + '184c2102') + echo 'lz4 -l' + return + ;; + 'fd2fb528') + echo 'zstd' + return + ;; + esac + + bytes="$(od -An -c -N3 "$1" | tr -dc '[:alnum:]')" + if [[ "$bytes" == 'BZh' ]]; then + echo 'bzip2' + return + fi + + # lzma detection sucks and there's really no good way to + # do it without reading large portions of the stream. this + # check is good enough for GNU tar, apparently, so it's good + # enough for me. + bytes="$(od -An -t x1 -N3 "$1" | tr -dc '[:alnum:]')" + if [[ "$bytes" == '5d0000' ]]; then + echo 'lzma' + return + fi + + read -rd '' bytes < <(od -An -j0x04 -t c -N4 "$1" | tr -dc '[:alnum:]') + if [[ "$bytes" == 'zimg' ]]; then + echo 'zimg' + return + fi + + # out of ideas, assuming uncompressed +} + +_kver_zimage() { + # Generic EFI zboot added since kernel 6.1 + # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/firmware/efi/libstub/Makefile.zboot?h=v6.1 + # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/firmware/efi/libstub/zboot-header.S?h=v6.1 + + local kver='' reader start size comp_type + + # Reading 4 bytes from address 0x08 is the starting offset of compressed data + start="$(od -An -j0x08 -t u4 -N4 "$1" | tr -dc '[:alnum:]')" + + # Reading 4 bytes from address 0x0c is the size of compressed data, + # but it needs to be corrected according to the compressed type. + size="$(od -An -j0x0c -t u4 -N4 "$1" | tr -dc '[:alnum:]')" + + # Read 36 bytes (before 0x3c) from address 0x18, + # which is a nul-terminated string representing the compressed type. + read -rd '' comp_type < <(od -An -j0x18 -t a -N32 "$1" | sed 's/ nul//g' | tr -dc '[:alnum:]') + + [[ "$start" =~ ^[0-9]+$ ]] || return 1 + [[ "$size" =~ ^[0-9]+$ ]] || return 1 + + case "$comp_type" in + 'gzip') + reader='zcat' + ;; + 'lz4') + reader='lz4cat' + size="$((size + 4))" + ;; + 'lzma') + reader='xzcat' + size="$((size + 4))" + ;; + 'lzo') + reader="lzop -d" + size="$((size + 4))" + ;; + 'xzkern') + reader='xzcat' + size="$((size + 4))" + ;; + 'zstd22') + reader='zstdcat' + size="$((size + 4))" + ;; + *) + reader="$comp_type" + size="$((size + 4))" + ;; + esac + + read -r _ _ kver _ < <(dd if="$1" bs=1 count="$size" skip="$start" 2>/dev/null | $reader - | grep -m1 -aoE 'Linux version .(\.[-[:alnum:]+]+)+') + + printf '%s' "$kver" +} + +_detect_generic_kver() { + # For unknown architectures, we can try to grep the uncompressed or gzipped + # image for the boot banner. + # This should work at least for ARM when run on /boot/Image, or RISC-V on + # gzipped /boot/vmlinuz-linuz. On other architectures it may be worth trying + # rather than bailing, and inform the user if none was found. + + # Loosely grep for `linux_banner`: + # https://elixir.bootlin.com/linux/v5.7.2/source/init/version.c#L46 + local kver='' reader='cat' + local comp_type='' + + comp_type="$(_detect_compression "$1")" + + if [[ "$comp_type" == 'zimg' ]]; then + # Generic EFI zboot image + _kver_zimage "$1" + return 0 + elif [[ "$comp_type" == 'gzip' ]]; then + reader='zcat' + fi + + [[ "$(_detect_compression "$1")" == 'gzip' ]] && reader='zcat' + + read -r _ _ kver _ < <($reader "$1" | grep -m1 -aoE 'Linux version .(\.[-[:alnum:]+]+)+') + + printf '%s' "$kver" +} + _detect_kver() { local kver_validator='^[[:digit:]]+(\.[[:digit:]]+)+' offset - offset="$(od -An -j0x20E -dN2 "$1")" || return - read -r kver _ < \ - <(dd if="$1" bs=1 count=127 skip=$(( offset + 0x200 )) 2>/dev/null) - [[ "$kver" =~ $kver_validator ]] && printf '%s' "$kver" + local arch + + arch="$(uname -m)" + if [[ $arch == @(i?86|x86_64) ]]; then + offset="$(od -An -j0x20E -dN2 "$1")" || return + read -r kver _ < \ + <(dd if="$1" bs=1 count=127 skip=$(( offset + 0x200 )) 2>/dev/null) + [[ "$kver" =~ $kver_validator ]] && printf '%s' "$kver" + else + _detect_generic_kver "$1" + fi } _lsinitcpio() { diff --git a/test/cases/functions.bats b/test/cases/functions.bats index 43e3b9d..c1f4e8b 100644 --- a/test/cases/functions.bats +++ b/test/cases/functions.bats @@ -12,28 +12,28 @@ setup() { } @test "detect_compression bzip2" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'bzip2')" run detect_compression "$tmp_img" assert_output "bzip2" } @test "detect_compression cat" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'cat')" run detect_compression "$tmp_img" assert_output "" } @test "detect_compression gzip" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'gzip')" run detect_compression "$tmp_img" assert_output "gzip" } @test "detect_compression lz4" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'lz4')" run detect_compression "$tmp_img" assert_output --partial "==> ERROR: Newer lz4 stream format detected! This may not boot!" @@ -41,21 +41,21 @@ setup() { } @test "detect_compression lz4 (legacy)" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'lz4' '-l')" run detect_compression "$tmp_img" assert_output "lz4 -l" } @test "detect_compression lzma" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'lzma')" run detect_compression "$tmp_img" assert_output "lzma" } @test "detect_compression lzop" { - local tmp_img + local tmp_img='' __check_binary "lzop" tmp_img="$(__gen_test_image 'lzop')" run detect_compression "$tmp_img" @@ -63,27 +63,83 @@ setup() { } @test "detect_compression xz" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'xz' '--check=crc32')" run detect_compression "$tmp_img" assert_output "xz" } @test "detect_compression zstd" { - local tmp_img + local tmp_img='' tmp_img="$(__gen_test_image 'zstd' '-T0')" run detect_compression "$tmp_img" assert_output "zstd" } +@test "detect_compression zimg" { + local tmp_img='' + + tmp_img="$(__gen_test_image 'zimg')" + run detect_compression "$tmp_img" + assert_output "zimg" +} + @test "kver_x86" { - local kernel_ver tmp_knl + local kernel_ver='' tmp_knl='' kernel_ver="6.0.9-arch1-1 #1 SMP PREEMPT_DYNAMIC Wed, 16 Nov 2022 17:01:17 +0000 x86_64 GNU/Linux" tmp_knl=$(__gen_test_kernel "$kernel_ver") run kver_x86 "$tmp_knl" assert_output "6.0.9-arch1-1" } +@test "kver_zimage gzip" { + local kernel_ver='' tmp_knl='' tmp_img='' + kernel_ver="Linux version 6.1.0-rc5-5 #1 SMP Sat, 17 Dec 2022 05:05:29 +0000 loongarch64 GNU/Linux" + tmp_img="$(__gen_test_zboot_kernel "$kernel_ver" 'gzip')" + run kver_zimage "$tmp_img" + assert_output "6.1.0-rc5-5" +} + +@test "kver_zimage lz4" { + local kernel_ver='' tmp_knl='' tmp_img='' + kernel_ver="Linux version 6.1.0-rc5-5 #1 SMP Sat, 17 Dec 2022 05:05:29 +0000 loongarch64 GNU/Linux" + tmp_img="$(__gen_test_zboot_kernel "$kernel_ver" 'lz4')" + run kver_zimage "$tmp_img" + assert_output "6.1.0-rc5-5" +} + +@test "kver_zimage lzma" { + local kernel_ver='' tmp_knl='' tmp_img='' + kernel_ver="Linux version 6.1.0-rc5-5 #1 SMP Sat, 17 Dec 2022 05:05:29 +0000 loongarch64 GNU/Linux" + tmp_img="$(__gen_test_zboot_kernel "$kernel_ver" 'lzma')" + run kver_zimage "$tmp_img" + assert_output "6.1.0-rc5-5" +} + +@test "kver_zimage lzo" { + local kernel_ver='' tmp_knl='' tmp_img='' + kernel_ver="Linux version 6.1.0-rc5-5 #1 SMP Sat, 17 Dec 2022 05:05:29 +0000 loongarch64 GNU/Linux" + tmp_img="$(__gen_test_zboot_kernel "$kernel_ver" 'lzo')" + run kver_zimage "$tmp_img" + assert_output "6.1.0-rc5-5" +} + +@test "kver_zimage xz" { + local kernel_ver='' tmp_knl='' tmp_img='' + kernel_ver="Linux version 6.1.0-rc5-5 #1 SMP Sat, 17 Dec 2022 05:05:29 +0000 loongarch64 GNU/Linux" + tmp_img="$(__gen_test_zboot_kernel "$kernel_ver" 'xzkern')" + run kver_zimage "$tmp_img" + assert_output "6.1.0-rc5-5" +} + +@test "kver_zimage zstd" { + local kernel_ver='' tmp_knl='' tmp_img='' + kernel_ver="Linux version 6.1.0-arch1-2 #1 SMP Sat, 17 Dec 2022 05:05:29 +0000 loongarch64 GNU/Linux" + tmp_img="$(__gen_test_zboot_kernel "$kernel_ver" 'zstd22')" + run kver_zimage "$tmp_img" + assert_output "6.1.0-arch1-2" +} + @test "add_binary script" { local tmp_bin BUILDROOT="${BATS_RUN_TMPDIR}/buildroot.${BATS_TEST_NAME}/" interpreter="/usr/local/${BATS_TEST_NAME}.${RANDOM}" _optquiet=1 diff --git a/test/helpers/common.bash b/test/helpers/common.bash index b6b2a22..fc954db 100644 --- a/test/helpers/common.bash +++ b/test/helpers/common.bash @@ -9,8 +9,15 @@ __gen_test_image() { tmp_file="$(mktemp --tmpdir="$BATS_RUN_TMPDIR" tmp_file.XXXXXX)" trap '{ rm -f -- "$tmp_img"; }' EXIT trap '{ rm -f -- "$tmp_file"; }' EXIT - echo "this is a test file" >"$tmp_file" - bsdtar -cf - "$tmp_file" | "${compress_opts[@]}" >"$tmp_img" + echo "this is a test file" > "$tmp_file" + + if [[ ${compress_opts[0]} == "zimg" ]]; then + bsdtar -cf - "$tmp_file" > "$tmp_img" + # write "zimg" into image at 0x04 + printf "zimg" | dd of="$tmp_img" seek=$((0x4)) bs=1 count=4 status=none conv=notrunc + else + bsdtar -cf - "$tmp_file" | "${compress_opts[@]}" > "$tmp_img" + fi rm -f -- "$tmp_file" echo "$tmp_img" } @@ -31,7 +38,73 @@ __gen_test_kernel() { echo "$tmp_knl" } -__check_binary() { +# Generates a temporary zboot dummy kernel, with the passed string as kernel version and specified compression type +__gen_test_zboot_kernel() { + local kernel_ver="$1" + local comp_type="$2" + local tmp_img tmp_file + local num count size start + local compress_opts + + tmp_img="$(mktemp --tmpdir="$BATS_RUN_TMPDIR" tmp_img.XXXXXX)" + tmp_file="$(mktemp --tmpdir="$BATS_RUN_TMPDIR" tmp_file.XXXXXX)" + + # generate image with random size between 256 to 1024, 4 bytes align + num=$(head -n 10 /dev/urandom | cksum | awk -F ' ' '{print $1}') + count=$((num%192*4+512)) + dd if=/dev/zero of="$tmp_img" count="$count" bs=1 status=none + + trap '{ rm -f -- "$tmp_img"; }' EXIT + trap '{ rm -f -- "$tmp_file"; }' EXIT + + case $comp_type in + gzip) + compress_opts=("gzip") + ;; + lz4) + compress_opts=("lz4") + ;; + lzma) + compress_opts=("lzma") + ;; + lzo) + compress_opts=("lzop") + ;; + xzkern) + compress_opts=("xz" "--check=crc32") + ;; + zstd22) + compress_opts=("zstd" "-T0") + ;; + *) + echo "Compress type is not supported" + return 1 + ;; + esac + + echo "$kernel_ver" | "${compress_opts[@]}" > "$tmp_file" + cat "$tmp_file" >> "$tmp_img" + size=$(stat -c %s "$tmp_file") + + # write "zimg" into image at 0x04 + printf "zimg" | dd of="$tmp_img" seek="$((0x4))" bs=1 count=4 status=none conv=notrunc + + # write COMP_TYPE string into image at 0x18 + printf '%s' "$comp_type" | dd of="$tmp_img" seek="$((0x18))" bs=1 count=36 status=none conv=notrunc + + # write compress data start offset at 0x08 + start=$(printf "%08x" "$count") + echo -n -e "\\x${start:6:2}\\x${start:4:2}\\x${start:2:2}\\x${start:0:2}" | dd of="$tmp_img" seek=$((0x8)) bs=1 count=4 status=none conv=notrunc + + # write compress data size offset at 0x0c + size=$(printf "%08x\n" "$size") + echo -n -e "\\x${size:6:2}\\x${size:4:2}\\x${size:2:2}\\x${size:0:2}" | dd of="$tmp_img" seek=$((0xc)) bs=1 count=4 status=none conv=notrunc + + rm -f -- "$tmp_file" + echo "$tmp_img" +} + +__check_binary(){ local binary="$1" if ! command -v "${binary}" &>/dev/null; then skip "${binary} not installed" |