Files
romscope/cli
Michał Kopeć cde9388f25 Add licensing info
Signed-off-by: Michał Kopeć <michal.kopec@3mdeb.com>
2024-07-23 11:32:42 +02:00

621 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
# This script was generated by bashly 1.1.10 (https://bashly.dannyb.co)
# Modifying it manually is not recommended
# :wrapper.bash3_bouncer
if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then
printf "bash version 4 or higher is required\n" >&2
exit 1
fi
# :command.master_script
# :command.version_command
version_command() {
echo "$version"
}
# :command.usage
cli_usage() {
if [[ -n $long_usage ]]; then
printf "cli - Sample application\n"
echo
else
printf "cli - Sample application\n"
echo
fi
printf "%s\n" "Usage:"
printf " cli COMMAND\n"
printf " cli [COMMAND] --help | -h\n"
printf " cli --version | -v\n"
echo
# :command.usage_commands
printf "%s\n" "Commands:"
printf " %s Extract subregions from a firmware binary\n" "extract"
printf " %s Compare two binaries\n" "compare"
echo
# :command.long_usage
if [[ -n $long_usage ]]; then
printf "%s\n" "Options:"
# :command.usage_fixed_flags
printf " %s\n" "--help, -h"
printf " Show this help\n"
echo
printf " %s\n" "--version, -v"
printf " Show version number\n"
echo
fi
}
# :command.usage
cli_extract_usage() {
if [[ -n $long_usage ]]; then
printf "cli extract - Extract subregions from a firmware binary\n"
echo
else
printf "cli extract - Extract subregions from a firmware binary\n"
echo
fi
printf "Alias: e\n"
echo
printf "%s\n" "Usage:"
printf " cli extract ROM [OUTPUT]\n"
printf " cli extract --help | -h\n"
echo
# :command.long_usage
if [[ -n $long_usage ]]; then
printf "%s\n" "Options:"
# :command.usage_fixed_flags
printf " %s\n" "--help, -h"
printf " Show this help\n"
echo
# :command.usage_args
printf "%s\n" "Arguments:"
# :argument.usage
printf " %s\n" "ROM"
printf " Path to coreboot binary\n"
echo
# :argument.usage
printf " %s\n" "OUTPUT"
printf " Directory to extract to (default: [romname].extracted)\n"
echo
# :command.usage_examples
printf "%s\n" "Examples:"
printf " cli extract coreboot.rom\n"
echo
fi
}
# :command.usage
cli_compare_usage() {
if [[ -n $long_usage ]]; then
printf "cli compare - Compare two binaries\n"
echo
else
printf "cli compare - Compare two binaries\n"
echo
fi
printf "Alias: c\n"
echo
printf "%s\n" "Usage:"
printf " cli compare ROM1 ROM2\n"
printf " cli compare --help | -h\n"
echo
# :command.long_usage
if [[ -n $long_usage ]]; then
printf "%s\n" "Options:"
# :command.usage_fixed_flags
printf " %s\n" "--help, -h"
printf " Show this help\n"
echo
# :command.usage_args
printf "%s\n" "Arguments:"
# :argument.usage
printf " %s\n" "ROM1"
printf " First ROM to compare\n"
echo
# :argument.usage
printf " %s\n" "ROM2"
printf " Second ROM to compare\n"
echo
fi
}
# :command.normalize_input
# :command.normalize_input_function
normalize_input() {
local arg flags passthru
passthru=false
while [[ $# -gt 0 ]]; do
arg="$1"
if [[ $passthru == true ]]; then
input+=("$arg")
elif [[ $arg =~ ^(--[a-zA-Z0-9_\-]+)=(.+)$ ]]; then
input+=("${BASH_REMATCH[1]}")
input+=("${BASH_REMATCH[2]}")
elif [[ $arg =~ ^(-[a-zA-Z0-9])=(.+)$ ]]; then
input+=("${BASH_REMATCH[1]}")
input+=("${BASH_REMATCH[2]}")
elif [[ $arg =~ ^-([a-zA-Z0-9][a-zA-Z0-9]+)$ ]]; then
flags="${BASH_REMATCH[1]}"
for ((i = 0; i < ${#flags}; i++)); do
input+=("-${flags:i:1}")
done
elif [[ "$arg" == "--" ]]; then
passthru=true
input+=("$arg")
else
input+=("$arg")
fi
shift
done
}
# :command.inspect_args
inspect_args() {
if ((${#args[@]})); then
readarray -t sorted_keys < <(printf '%s\n' "${!args[@]}" | sort)
echo args:
for k in "${sorted_keys[@]}"; do
echo "- \${args[$k]} = ${args[$k]}"
done
else
echo args: none
fi
if ((${#other_args[@]})); then
echo
echo other_args:
echo "- \${other_args[*]} = ${other_args[*]}"
for i in "${!other_args[@]}"; do
echo "- \${other_args[$i]} = ${other_args[$i]}"
done
fi
if ((${#deps[@]})); then
readarray -t sorted_keys < <(printf '%s\n' "${!deps[@]}" | sort)
echo
echo deps:
for k in "${sorted_keys[@]}"; do
echo "- \${deps[$k]} = ${deps[$k]}"
done
fi
if ((${#env_var_names[@]})); then
readarray -t sorted_names < <(printf '%s\n' "${env_var_names[@]}" | sort)
echo
echo "environment variables:"
for k in "${sorted_names[@]}"; do
echo "- \$$k = ${!k:-}"
done
fi
}
# :command.user_lib
# src/lib/extract.sh
# SPDX-FileCopyrightText: 2024 3mdeb Sp. z o. o.
#
# SPDX-License-Identifier: MIT
is_ifd_rom() {
filetype=$(file -b $1)
[[ $filetype = "Intel serial flash for PCH ROM" ]]
}
is_fmap_rom() {
grep -obUaP "__FMAP__" $1 &> /dev/null
[[ $? = 0 ]]
}
is_cbfs_rom() {
cbfstool $1 print &> /dev/null
[[ $? = 0 ]]
}
rom_extract() {
set +e # bleh
rom_file=$1
output_dir=$2
if [ ! -f $rom_file ]; then
echo "File $rom_file does not exist. Please input a valid ROM file path."
return 1
fi
if [ -z "$output_dir" ]; then
output_dir=$rom_file.extracted
fi
mkdir -p $output_dir
if is_ifd_rom $rom_file; then
echo "ROM contains an Intel Flash Descriptor"
mkdir -p $output_dir/regions/ifd
pushd $output_dir/regions/ifd > /dev/null
ifdtool -x $rom_file &> /dev/null
ls | fmt -s | sed "s/^/[IFD] /"
popd > /dev/null
echo
fi
if is_fmap_rom $rom_file; then
echo "ROM contains a coreboot FMAP"
regions=$(cbfstool $rom_file layout -w \
| grep \' \
| cut -d \' -f 2 \
| grep -v -e "SI_ALL" -e "SI_BIOS" -e "RW_SECTION_A" -e "RW_SECTION_B" -e "RW_MISC" -e "UNIFIED_MRC_CACHE" -e "RW_SHARED" -e "WP_RO" -e "RO_SECTION"
)
mkdir -p $output_dir/regions/fmap
pushd $output_dir/regions/fmap > /dev/null
for region in $regions; do
echo "[FMAP] $region.bin"
cbfstool $rom_file read -r $region -f $region.bin
if is_cbfs_rom $region.bin; then
echo "Region contains a CBFS"
mkdir -p $region.cbfs
cbfs_files=$(cbfstool $rom_file print -r $region | cut -d ' ' -f 1 | tail -n +3 | grep -v "(empty)")
pushd $region.cbfs > /dev/null
for file in $cbfs_files; do
echo " [CBFS] $file"
cbfstool $rom_file extract -r $region -f $(echo $file | tr \/ -) -n $file -m x86 &> /dev/null # TODO FIXME etc
done
popd > /dev/null
fi
done
popd > /dev/null
echo
fi
}
# :command.command_functions
# :command.function
cli_extract_command() {
# src/extract_command.sh
# SPDX-FileCopyrightText: 2024 3mdeb Sp. z o. o.
#
# SPDX-License-Identifier: MIT
ROM_FILE=$(realpath "${args[rom]}")
OUTPUT_DIR="${args[output]}"
rom_extract $ROM_FILE $OUTPUT_DIR
}
# :command.function
cli_compare_command() {
# src/compare_command.sh
# SPDX-FileCopyrightText: 2024 3mdeb Sp. z o. o.
#
# SPDX-License-Identifier: MIT
ROM1_FILE=$(realpath "${args[rom1]}")
ROM2_FILE=$(realpath "${args[rom2]}")
OUTPUT_DIR="${args[output]}"
WORKDIR=/tmp/romscope
main () {
if [ -e $WORKDIR ]; then
rm -r $WORKDIR
fi
echo "Extracting file $ROM1_FILE"
mkdir -p $WORKDIR/a
rom_extract $ROM1_FILE $WORKDIR/a > /dev/null
pushd $WORKDIR/a > /dev/null
find regions -type f -exec printf '%s\n' {} + > manifest
popd > /dev/null
echo "Extracting file $ROM2_FILE"
mkdir -p $WORKDIR/b
rom_extract $ROM2_FILE $WORKDIR/b > /dev/null
pushd $WORKDIR/b > /dev/null
find regions -type f -exec printf '%s\n' {} + > manifest
popd > /dev/null
pushd $WORKDIR > /dev/null
files=$(cat a/manifest \
| grep -v -e "regions/fmap/FW_MAIN_A.bin" -e "regions/fmap/FW_MAIN_B.bin" -e "regions/fmap/COREBOOT.bin" -e "regions/fmap/VBLOCK_A.bin" -e "regions/fmap/VBLOCK_B.bin" -e "regions/ifd/flashregion" \
)
for file in $files; do
echo "Generating report for $file"
diffoscope a/$file b/$file --html $(echo $file | tr \/ -).html &> /dev/null
done
popd > /dev/null
if [ -z "$OUTPUT_DIR" ]; then
OUTPUT_DIR="report"
fi
mkdir -p $OUTPUT_DIR
cp $WORKDIR/*.html $OUTPUT_DIR/
echo "Report placed in folder: '$OUTPUT_DIR'"
rm -r $WORKDIR
}
main
}
# :command.parse_requirements
parse_requirements() {
# :command.fixed_flags_filter
while [[ $# -gt 0 ]]; do
case "${1:-}" in
--version | -v)
version_command
exit
;;
--help | -h)
long_usage=yes
cli_usage
exit
;;
*)
break
;;
esac
done
# :command.dependencies_filter
if command -v cbfstool >/dev/null 2>&1; then
deps['cbfstool']="$(command -v cbfstool | head -n1)"
else
printf "missing dependency: cbfstool\n" >&2
printf "%s\n" "You can install from your distribution's package repositories or build from source code: https://review.coreboot.org/coreboot.git\n" >&2
exit 1
fi
if command -v ifdtool >/dev/null 2>&1; then
deps['ifdtool']="$(command -v ifdtool | head -n1)"
else
printf "missing dependency: ifdtool\n" >&2
printf "%s\n" "You can install from your distribution's package repositories or build from source code: https://review.coreboot.org/coreboot.git\n" >&2
exit 1
fi
if command -v ifdtool >/dev/null 2>&1; then
deps['diffoscope']="$(command -v ifdtool | head -n1)"
else
printf "missing dependency: diffoscope\n" >&2
printf "%s\n" "You can install from your distribution's package repositories or build from source code: https://diffoscope.org/\n" >&2
exit 1
fi
# :command.command_filter
action=${1:-}
case $action in
-*) ;;
extract | e)
action="extract"
shift
cli_extract_parse_requirements "$@"
shift $#
;;
compare | c)
action="compare"
shift
cli_compare_parse_requirements "$@"
shift $#
;;
# :command.command_fallback
"")
cli_usage >&2
exit 1
;;
*)
printf "invalid command: %s\n" "$action" >&2
exit 1
;;
esac
# :command.parse_requirements_while
while [[ $# -gt 0 ]]; do
key="$1"
case "$key" in
-?*)
printf "invalid option: %s\n" "$key" >&2
exit 1
;;
*)
# :command.parse_requirements_case
# :command.parse_requirements_case_simple
printf "invalid argument: %s\n" "$key" >&2
exit 1
;;
esac
done
}
# :command.parse_requirements
cli_extract_parse_requirements() {
# :command.fixed_flags_filter
while [[ $# -gt 0 ]]; do
case "${1:-}" in
--help | -h)
long_usage=yes
cli_extract_usage
exit
;;
*)
break
;;
esac
done
# :command.command_filter
action="extract"
# :command.parse_requirements_while
while [[ $# -gt 0 ]]; do
key="$1"
case "$key" in
-?*)
printf "invalid option: %s\n" "$key" >&2
exit 1
;;
*)
# :command.parse_requirements_case
# :command.parse_requirements_case_simple
# :argument.case
if [[ -z ${args['rom']+x} ]]; then
args['rom']=$1
shift
# :argument.case
elif [[ -z ${args['output']+x} ]]; then
args['output']=$1
shift
else
printf "invalid argument: %s\n" "$key" >&2
exit 1
fi
;;
esac
done
# :command.required_args_filter
if [[ -z ${args['rom']+x} ]]; then
printf "missing required argument: ROM\nusage: cli extract ROM [OUTPUT]\n" >&2
exit 1
fi
}
# :command.parse_requirements
cli_compare_parse_requirements() {
# :command.fixed_flags_filter
while [[ $# -gt 0 ]]; do
case "${1:-}" in
--help | -h)
long_usage=yes
cli_compare_usage
exit
;;
*)
break
;;
esac
done
# :command.command_filter
action="compare"
# :command.parse_requirements_while
while [[ $# -gt 0 ]]; do
key="$1"
case "$key" in
-?*)
printf "invalid option: %s\n" "$key" >&2
exit 1
;;
*)
# :command.parse_requirements_case
# :command.parse_requirements_case_simple
# :argument.case
if [[ -z ${args['rom1']+x} ]]; then
args['rom1']=$1
shift
# :argument.case
elif [[ -z ${args['rom2']+x} ]]; then
args['rom2']=$1
shift
else
printf "invalid argument: %s\n" "$key" >&2
exit 1
fi
;;
esac
done
# :command.required_args_filter
if [[ -z ${args['rom1']+x} ]]; then
printf "missing required argument: ROM1\nusage: cli compare ROM1 ROM2\n" >&2
exit 1
fi
if [[ -z ${args['rom2']+x} ]]; then
printf "missing required argument: ROM2\nusage: cli compare ROM1 ROM2\n" >&2
exit 1
fi
}
# :command.initialize
initialize() {
version="0.1.0"
long_usage=''
set -e
}
# :command.run
run() {
declare -A args=()
declare -A deps=()
declare -a other_args=()
declare -a env_var_names=()
declare -a input=()
normalize_input "$@"
parse_requirements "${input[@]}"
case "$action" in
"extract") cli_extract_command ;;
"compare") cli_compare_command ;;
esac
}
initialize
run "$@"