#!/bin/bash # # lsinitcpio - dump the contents of an initramfs image # shopt -s extglob _list='--list' _optcolor=1 _f_functions=functions declare -A bsdcpio_options=( [list]='--list' [input]='-i' [quiet]='--quiet' ) usage() { cat<<USAGE lsinitcpio %VERSION% usage: ${0##*/} [action] [options] <initramfs> Actions: -a, --analyze analyze contents of image -c, --config show configuration file image was built with -l, --list list contents of the image (default) -x, --extract extract image to disk Options: -h, --help display this help -n, --nocolor disable colorized output -V, --version display version information -v, --verbose more verbose output USAGE } version() { cat<<EOF lsinitcpio %VERSION% EOF } decomp() { ${_compress:-cat} ${_compress:+-cd} "$@" } . "$_f_functions" # override the die method from functions die() { error "$@" exit 1 } size_to_human() { awk -v size="$1" ' BEGIN { suffix[1] = "B" suffix[2] = "KiB" suffix[3] = "MiB" suffix[4] = "GiB" suffix[5] = "TiB" count = 1 while (size > 1024) { size /= 1024 count++ } sizestr = sprintf("%.2f", size) sub(/\.?0+$/, "", sizestr) printf("%s %s", sizestr, suffix[count]) }' } detect_filetype() { case $(hexdump -n 6 -e '"%c"' "$1") in '070701') # no compression echo return ;; $'\xfd7zXZ') echo 'xz' return ;; esac if [[ $(hexdump -n 4 -e '"%c"' "$1") = $'\x89LZO' ]]; then echo 'lzop' return fi if [[ $(hexdump -n 2 -e '"%x"' "$1") = '8b1f' ]]; then echo 'gzip' return fi case $(hexdump -n 4 -e '"%x"' "$1") in 184d2204) error 'Newer lz4 stream format detected! This may not boot!' echo 'lz4' return ;; 184c2102) echo 'lz4 -l' return ;; esac if [[ $(hexdump -n 3 -e '"%c"' "$1") == '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. if [[ $(hexdump -n 3 -e '"%x"' "$1") = '5d' ]]; then echo 'lzma' return fi # still nothing? hrmm, maybe the user goofed and it's a kernel if kver "$1" >/dev/null; then die '%s is a kernel image, not an initramfs image!' "$1" fi # out of ideas, we're done here. die 'Unexpected file type. Are you sure %s is an initramfs?' "$1" } analyze_image() { local -a binaries explicitmod modules foundhooks hooks local kernver ratio columns=$(tput cols) image=$1 workdir=$(mktemp -d --tmpdir lsinitcpio.XXXXXX) trap 'rm -rf "$workdir"' EXIT # fallback in case tput failed us columns=${columns:-80} zsize=$(stat -c %s "$_image") # calculate compression ratio decomptime=$(TIMEFORMAT=%R; { time decomp "$_image" >/dev/null; } 2>&1) if [[ $_compress ]]; then fullsize=$(decomp "$_image" | wc -c) ratio=.$(( zsize * 1000 / fullsize % 1000 )) fi # decompress the image since we need to read from it multiple times. decomp "$_image" | bsdtar -C "$workdir" -xf - || die 'Failed to decompress image' # collect stats kernver=("$workdir"/usr/lib/modules/*/) kernver=${kernver%/} kernver=${kernver##*/} modules=("$workdir/usr/lib/modules/$kernver"/kernel/*.ko*) if [[ -f ${modules[0]} ]]; then modules=("${modules[@]##*/}") modules=("${modules[@]%.ko*}") else unset modules fi foundhooks=("$workdir"/hooks/*) [[ -f ${foundhooks[0]} ]] && foundhooks=("${foundhooks[@]##*/}") || unset foundhooks mapfile -t binaries < <(find "$workdir/usr/bin" -type f -printf %f\\n) read -r version < "$workdir/VERSION" # source and read config . "$workdir/config" explicitmod=($MODULES) # print results imagename=$_image [[ -L $_image ]] && imagename+=" -> $(readlink -e "$_image")" msg 'Image: %s %s' "$imagename" [[ $version ]] && msg 'Created with mkinitcpio %s' "$version" msg 'Kernel: %s' "${kernver:-unknown}" msg 'Size: %s' "$(size_to_human "$zsize")" if [[ $_compress ]]; then msg 'Compressed with: %s' "$_compress" msg2 'Uncompressed size: %s (%s ratio)' "$(size_to_human "$fullsize")" "$ratio" fi msg2 'Estimated extraction time: %ss' "$decomptime" printf '\n' if (( ${#modules[*]} )); then msg 'Included modules:' for mod in "${modules[@]}"; do printf ' %s' "$mod" in_array "${mod//_/-}" "${explicitmod[@]//_/-}" && printf ' [explicit]' printf '\n' done | sort | column -c$columns printf '\n' fi msg 'Included binaries:' printf ' %s\n' "${binaries[@]}" | sort | column -c$columns printf '\n' if [[ $EARLYHOOKS ]]; then msg 'Early hook run order:' printf ' %s\n' $EARLYHOOKS printf '\n' fi if [[ $HOOKS ]]; then msg 'Hook run order:' printf ' %s\n' $HOOKS printf '\n' fi if [[ $LATEHOOKS ]]; then msg 'Late hook run order:' printf ' %s\n' $LATEHOOKS printf '\n' fi if [[ $CLEANUPHOOKS ]]; then msg 'Cleanup hook run order:' printf ' %s\n' $CLEANUPHOOKS printf '\n' fi } _opt_short='achlnVvx' _opt_long=('analyze' 'config' 'help' 'list' 'nocolor' 'version' 'verbose' 'extract') parseopts "$_opt_short" "${_opt_long[@]}" -- "$@" || exit set -- "${OPTRET[@]}" unset _opt_short _opt_long OPTRET while :; do case $1 in -a|--analyze) _optanalyze=1 ;; -c|--config) _optshowconfig=1 ;; -h|--help) usage exit 0 ;; -l|--list) _optlistcontents=1 ;; -n|--nocolor) _optcolor=0 ;; -V|--version) version exit 0 ;; -v|--verbose) bsdcpio_options['verbose']='--verbose' ;; -x|--extract) unset 'bsdcpio_options[list]' ;; --) shift break 2 ;; esac shift done _image=$1 if [[ -t 1 ]] && (( _optcolor )); then try_enable_color fi [[ $_image ]] || die "No image specified (use -h for help)" [[ -f $_image ]] || die "No such file: %s" "$_image" case $(( _optanalyze + _optlistcontents + _optshowconfig )) in 0) # default action when none specified _optlistcontents=1 ;; [!1]) die "Only one action may be specified at a time" ;; esac # read compression type _compress=$(detect_filetype "$_image") || exit if (( _optanalyze )); then analyze_image "$_image" elif (( _optshowconfig )); then decomp "$_image" | bsdtar xOf - buildconfig 2>/dev/null || die 'Failed to extract config from image (mkinitcpio too old?)' else decomp "$_image" | bsdcpio "${bsdcpio_options[@]}" fi # vim: set ft=sh ts=4 sw=4 et: