Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
cli-shell-utils / usr / local / lib / cli-shell-utils / bin / unpack-initrd
Size: Mime:
#!/bin/bash
#------------------------------------------------------------------------------
# unpack-initrd
#
# Copyright 2016 -- 2019  Paul Banham <antiX@operamail.com>
#
# Thanks to fehlix for help with the microcode!
#
# See (in Linux source tree):
#  Documentation/x86/boot.txt
#  Documentation/x86/microcode.txt
#------------------------------------------------------------------------------

     VERSION="3.11"
VERSION_DATE="Thu 21 Nov 2019 10:16:41 PM MST"

ME=${0##*/}

INITRD_FILE="/live/boot-dev/antiX/initrd.gz"
INITRD_DIR="./initrd"
REPACK_ARGS="-o -H newc --owner root:root --quiet"
UNPACK_ARGS="-idum --quiet"

 BZ2_MAGIC="5a42"
GZIP_MAGIC="8b1f"
 LZ4_MAGIC="2204"
LZIP_MAGIC="5a4c"
LZMA_MAGIC="005d"
  XZ_MAGIC="37fd"

CPIO_MAGIC="3730"

MICROCODE_FNAME=".microcode"
 COMPRESS_FNAME=".compress"

DEFAULT_COMPRESS="gzip"

MAX_PREAMBS=10

BLOCK_SIZE=512

MAX_XTRA_SIZE=20
INITRD_XTRA_DIR=

usage() {
    local ret=${1:-0}

    cat <<Usage
Usage:  $ME [options]

Unpack and repack the live initrd.gz for antiX and MX.  Also unpack
and repack Debian initramfs files.  Supports the following forms of
compression: bz2, gzip, lz4, lzip, lzma, xz (assuming the associated
(de)compression tool is available.  Also supports microcode
preambles.

Options:
  -C  --clear            Delete directory before unpacking
  -c  --compress=<prog>  Use <prog> to recompress the initrd
  -d  --dir=<dir>        Unpack in <dir> instead of $INITRD_DIR
  -f  --file=<file>      Unpack <file> instead of:
                            $INITRD_FILE
      --from=<file>      Same as --file
  -l  --level=<N>        Set compression level: 1 (fastest) -- 9 (best)
                         (only applies to certain compression methods)
  -h  --help             Show this usage
  -n  --no-md5           Don't make a .md5 file in --repack mode
  -N  --no-microcode     Do not repack microcode
  -p  --pretend          Show commands without running them
  -r  --repack           Repack the initrd file
  -s  --silent           Only show error messages
  -t  --template         Remove binaries and modules when repacking
  -q  --quiet            Say very little
  -V  --verbose          Show commands in addition to running them
  -v  --version          Show the version then exit
  -x  --xtra=<dir>       Copy files under <dir> into main file system
Usage

    exit $ret
}

#------------------------------------------------------------------------------
# Process a single command line argument with possibly a parameter
#------------------------------------------------------------------------------
eval_argument() {
    local arg=$1 val=$2
        case $arg in
             -clear|C) DO_CLEAR=true                   ;;
          -compress|c) COMPRESS_PROG=$val              ;;
          -compress=*) COMPRESS_PROG=$val              ;;
               -dir|d) INITRD_DIR=$val                 ;;
               -dir=*) INITRD_DIR=$val                 ;;
              -help|h) usage                           ;;
             -level|l) COMP_LEVEL=$val                 ;;
             -level=*) COMP_LEVEL=$val                 ;;
              -file|f) INITRD_FILE=$val                ;;
              -file=*) INITRD_FILE=$val                ;;
              -from|f) INITRD_FILE=$val                ;;
              -from=*) INITRD_FILE=$val                ;;
      -no-microcode|N) NO_MICROCODE=true               ;;
            -no-md5|n) NO_MD5=true                     ;;
           -pretend|p) PRETEND=true                    ;;
            -repack|r) REPACK=true                     ;;
            -silent|s) SILENT=true                     ;;
          -template|t) TEMPLATE=true                   ;;
             -quiet|q) QUIET=true                      ;;
           -verbose|V) VERBOSE=true                    ;;
           -version|v) show_version                    ;;
              -xtra|x) XTRA_DIR=$val                   ;;
              -xtra=*) XTRA_DIR=$val                   ;;
                    *) fatal "Unknown parameter -$arg" ;;
    esac
}

#------------------------------------------------------------------------------
# Which options need to be followed by a parameter
#------------------------------------------------------------------------------
takes_param() {
    case $1 in
     -compress|c) return 0 ;;
          -dir|d) return 0 ;;
         -from|f) return 0 ;;
         -file|f) return 0 ;;
        -level|l) return 0 ;;
         -xtra|x) return 0 ;;
    esac
    return 1
}

#------------------------------------------------------------------------------
# Start reading here
#------------------------------------------------------------------------------
main() {
    local SHIFT  SHORT_STACK="CcdhlfNnprsqVvx"

    local COMPRESS_PROG COMP_LEVEL DO_CLEAR NO_MD5 NO_MICROCODE
    local PRETEND QUIET REPACK VERBOSE SILENT

    read_params "$@"
    shift $SHIFT
    [ $# -gt 0 ] && fatal "Unexpected arguments: %s" "$*"

    case $COMP_LEVEL in
           "") ;;
        [0-9]) COMP_LEVEL="-$COMP_LEVEL" ;;
            *) fatal "Compression level must be 0 -- 9";;
    esac

    if [ "$REPACK" ]; then
        ssay "Repack: %s --> %s" "$(pq ${INITRD_DIR%/}/)" "$(pq $INITRD_FILE)"
        repack_initrd "$INITRD_FILE" "$INITRD_DIR"
    else
        ssay "Unpack: %s --> %s" "$(pq $INITRD_FILE)" "$(pq ${INITRD_DIR%/}/)"
        unpack_initrd "$INITRD_FILE" "$INITRD_DIR"
    fi

    exit 0
}

#------------------------------------------------------------------------------
# Unpack an initrd file.  If there are multiple microcode preambles then we
# call microcode_and_unpack() recursively.
#------------------------------------------------------------------------------
unpack_initrd() {
    local file=$1  dir=$2

    [ -n "$XTRA_DIR" ] && warn "Xtra dir %s ignored during unpack" "$(pqw "$XTRA_DIR")"
    [ -n "$TEMPLATE" ] && warn "Template mode ignored during unpack"

    test -e "$file" || fatal "File %s not found" "$file"
    test -r "$file" || fatal "Cannot read file %s" "$file"

    if test -d "$dir"; then
        if ! is_empty_dir "$dir"; then
            if [ "$DO_CLEAR" ]; then
                qsay "Clearing %s" "$(pq $dir)"
                cmd rm -rf "$dir"
            elif [ "$PRETEND" ]; then
                :
            else
                fatal "The target directory %s is not empty" "$dir"
            fi
        fi
    else
        test -e "$dir"  && fatal "%s is not a directory" "$dir"
    fi

    local magic=$(get_magic_num $file)
    local compress

    case $magic in
         $BZ2_MAGIC) compress=bzip2        ;;
        $GZIP_MAGIC) compress=gzip         ;;
         $LZ4_MAGIC) compress=lz4          ;;
        $LZIP_MAGIC) compress=lzip         ;;
        $LZMA_MAGIC) compress=lzma         ;;
          $XZ_MAGIC) compress=xz           ;;
        $CPIO_MAGIC) compress=cpio         ;;
                  *) fatal "Unknown file format %s" $magic ;;
    esac

    cmd mkdir -p "$dir" || fatal "Could not make directory %s" "$dir"

    case $compress in
        cpio) microcode_and_unpack "$file" "$dir"             ;;
           *) simple_unpack        "$file" "$dir" "$compress" ;;
    esac
}

#------------------------------------------------------------------------------
# Will be called recursively if there is more than one microcode preamble.
#
# $offset_blocks is how many cpio blocks we have already processed.  Assumes an
# uncompressed cpio archive starts at $offset_blocks + 1.  Every uncompressed
# archive, except the last archive in the file, will be treated as microcode.
#------------------------------------------------------------------------------
microcode_and_unpack() {
    local file=$1  dir=$2  offset_blocks=${3:-0}  depth=${4:-1}

    # Limit recursion in case something goes horribly wrong
    [ "$depth" -ge "$MAX_PREAMBS" ] && fatal "Exceeded the maximum number of preambles %s" "$(pqw $MAX_PREAMBS)"

    local cpio_blocks=$(get_cpio_blocks "$file" $offset_blocks)
    [ -z "$cpio_blocks"    ] && fatal "Unable to get length of cpio archive"
    [ "$cpio_blocks" = '0' ] && fatal "Stuck at zero length cpio archive"

    local preamb_offset=$((offset_blocks + cpio_blocks))

    # size checks and display
    local total_size=$(stat -c %s "$file")
    local cpio_size=$((cpio_blocks * $BLOCK_SIZE))
    local human_size=$(get_human_size "$cpio_size")

    # Total number of bytes until the next archive.  Use bytes so we can
    # compare with "stat" output.
    local preamb_size=$(( (offset_blocks + cpio_blocks) * $BLOCK_SIZE ))

    if [ "$preamb_size" -gt "$total_size" ]; then
        warn "The cpio archive is incomplete"
        return

    elif [ "$preamb_size" -eq "$total_size" ]; then

        # This is the final archive and it is not compressed so we unpack it
        # anyway as per the spec.
        cmd_dd if="$file" bs=$BLOCK_SIZE skip=$offset_blocks | simple_unpack - "$dir" "cpio"
        return
    fi

    #-- must be: [ "$preamb_size" -lt "$total_size" ]
    # so we save this preamble and look for what follows it

    local mc_file="$dir/$MICROCODE_FNAME"

    qsay "Microcode %s (%s) -->> %s"  "$depth"  "$(nq $human_size)"  "$(pq $MICROCODE_FNAME)"

    # Append this cpio archive to the .microcode file
    dd_append "$file" "$offset_blocks" "$cpio_blocks" "$mc_file"

    # What's the next archive?
    local magic_2=$(get_magic_num "$file" "$preamb_size")

    local compress
    case $magic_2 in

                 # Recurse to strip off and save multiple preambles
    $CPIO_MAGIC) microcode_and_unpack "$file" "$dir" "$preamb_offset" $((depth + 1))
                 return            ;;

     $BZ2_MAGIC) compress=bzip2    ;;
    $GZIP_MAGIC) compress=gzip     ;;
     $LZ4_MAGIC) compress=lz4      ;;
    $LZIP_MAGIC) compress=lzip     ;;
    $LZMA_MAGIC) compress=lzma     ;;
      $XZ_MAGIC) compress=xz       ;;
              *) fatal "Unknown file format %s" $magic_2 ;;
    esac

    cmd_dd if="$file" bs=$BLOCK_SIZE skip=$preamb_offset | simple_unpack - "$dir" "$compress"
}

#------------------------------------------------------------------------------
# Unpack a (usually) compressed cpio archive.  Use "cat" if there was no
# compression.
#------------------------------------------------------------------------------
simple_unpack() {
    local file=$1  dir=$2  prog=$3  args="--stdout --decompress"

    qsay "Unpack %s archive" "$(pq $prog)"
    need_prog "$prog"

    case $prog in
        cpio) prog=cat;  args= ;;
    esac

    cmd $prog $args "$file" | (cmd cd "$dir" && cmd cpio $UNPACK_ARGS) || fatal "Unpack failed"
    echo "$prog" | cmd write_file $dir/$COMPRESS_FNAME
}

#------------------------------------------------------------------------------
#  Repack a cpio archive, prepend with .microcode file and use compression in
# the .compress file (unless overridden).
#------------------------------------------------------------------------------
repack_initrd() {
    local file=$1  dir=$2
    test -e "$dir" || fatal "The directory %s does not exist" "$dir"
    test -d "$dir" || fatal "%s is not a directory" "$dir"

    [ -n "$XTRA_DIR" -a $(id -u) -ne 0 ] && fatal  "Must be root to use --xtra option"

    local mc_file="$dir/$MICROCODE_FNAME";
    local prog=${COMPRESS_PROG:-$(cat "$dir/$COMPRESS_FNAME" 2>/dev/null)}

    if [ -z "$prog" ]; then
        prog=$DEFAULT_COMPRESS
        qsay "Did not find file %s.  Will use %s compression" "$(pq $COMPRESS_FNAME)" "$(pq $prog)"
    else
        qsay "Using %s compression" "$(pq $prog)"
    fi

    local args="-c $COMP_LEVEL"

    # Don't let user run just any program that was in .compress file
    # And clear out compression args if "cat" is used.
    case $prog in
        gzip|bzip2|lz4)          ;;
          lzma|xz|lzip)          ;;
                   cat) args=    ;;

        *) fatal "Refusing to run unrecognized program %s" "$prog"  ;;
    esac

    need_prog "$prog"

    local targ_dir=$(dirname "$file")
    cmd mkdir -p "$targ_dir"

    if [ -z "$NO_MICROCODE" -a -e "$mc_file" ]; then
        qsay "Found %s archive" "$(pq 'microcode')"
        cmd cp "$mc_file" "$file"
    else
        # Since we always append we must erase an existing file
        cmd rm -f "$file"
    fi

    local INITRD_XTRA_DIR="$dir/live/xtra"
    if [ -n "$XTRA_DIR" ]; then
        test -d "$XTRA_DIR" || fatal "Xtra dir %s is not a directory" "$(pqw "$XTRA_DIR")"
        local xtra_size=$(du -scm "$XTRA_DIR" | tail -n1 | cut -f1)
        [ $xtra_size -gt $MAX_XTRA_SIZE ] && fatal "Size of xtra directory %s is too large" "$(nqw $xtra_size MiB)"
        qsay "Bind mount xtra dir %s" "$(pq "$XTRA_DIR")"
        trap on_exit  EXIT
        mkdir -p "$INITRD_XTRA_DIR"
        mount --bind "$XTRA_DIR" "$INITRD_XTRA_DIR"
        mountpoint -q "$INITRD_XTRA_DIR" || fatal "Failed to bind mount xtra directory"
    fi
    local excluder=exclude_files
    [ "$TEMPLATE" ] && excluder=exclude_files_and_template
    # Create the cpio archive, ignore our two meta-data files and then compress
    (cd "$dir" && cmd find . | $excluder | cmd cpio $REPACK_ARGS) \
        | cmd $prog $args | cmd append_file "$file"

    [ "$NO_MD5" ] && return

    on_exit

    # Create .md5 file
    local fname=$(basename $file)
    (cd "$targ_dir" && cmd md5sum "$(basename "$fname")" | cmd write_file "$fname.md5")
}

#------------------------------------------------------------------------------
# Exclude commmon editor backup files, git stuff and my Private directory
#------------------------------------------------------------------------------
exclude_files() {
    grep -E -v \
        -e '^\./\.(compress|microcode)$' \
        -e '^\./Private/'   \
        -e '^\./Private$'   \
        -e '^\./\.git'      \
        -e '/\.[^/]*\.swp$' \
        -e '/\.[^/]*~$'
}

#------------------------------------------------------------------------------
# Do the normal exclude and then also exclude modules, binaries, binary libs
#
#------------------------------------------------------------------------------
exclude_files_and_template() {
    exclude_files | grep -E -v \
        -e '^\./bin/ntfs-3g|eject|cryptsetup)'  \
        -e '^\./lib/modules'                    \
        -e '^\./lib/x86_64-linux-gnu'           \
        -e '^\./lib.*/(lib|ld).*\.so'
}

#------------------------------------------------------------------------------
# Get then number of cpio blocks from cpio archive, that may be offset in the
# original file by $offset blocks.  Thanks fehlix!
#------------------------------------------------------------------------------
get_cpio_blocks() {
    local file=$1  offset=${2:-0}

    cmd_dd if="$file" bs=$BLOCK_SIZE skip=$offset | cpio -t 7>&1 1>/dev/null 2>&7 | grep -o "^[0-9]\+"
}

#------------------------------------------------------------------------------
# Display first 2 bytes of a file as hex with an optional offset
#------------------------------------------------------------------------------
get_magic_num() {
    local file=$1 offset=${2:-0}
    dd status=none if=$file bs=1 count=2 skip=$offset | od -An -x | tr -d ' '
}

#------------------------------------------------------------------------------
# Look for non-hidden files in the directory
#------------------------------------------------------------------------------
is_empty_dir() {
    local dir=$1
    test -d "$dir" || return 0
    local cnt=$(ls "$dir" | grep -v "^lost+found$" | wc -l)
    [ $cnt -eq 0 ]
    return $?
}

#------------------------------------------------------------------------------
# Convenience routine
#------------------------------------------------------------------------------
cmd_dd() { cmd dd status=none "$@" ; }

#------------------------------------------------------------------------------
# Copy from $from starting at block offset $offset to file $to.  This is only
# used now for appending to the .microcode file.
#------------------------------------------------------------------------------
dd_append() {
    local from=$1 offset=$2  count=$3  to=$4
    cmd_dd if="$from" bs=$BLOCK_SIZE skip=$offset count=$count oflag=append conv=notrunc of="$to"
}

#------------------------------------------------------------------------------
# Redirecting to 6 means we can still use this inside of functions where the
# stdout is captured or used.
#------------------------------------------------------------------------------
cmd() {
    [ "$PRETEND$VERBOSE" ] && echo "$@" >&6
    [ "$PRETEND" ] && return 0
    "$@"
}

#------------------------------------------------------------------------------
# Used instead of ">" so we can use cmd() to enable pretend mode
#------------------------------------------------------------------------------
write_file() { cat > "$1" ; }

#------------------------------------------------------------------------------
# Used instead of ">>" so we can use cmd() to enable pretend mode
#------------------------------------------------------------------------------
append_file() {  cat >> "$1" ; }

#------------------------------------------------------------------------------
# Error out if $prog is not on the PATH
#------------------------------------------------------------------------------
need_prog() {
    local prog=$1
    which $prog &>/dev/null || fatal "Could not find program %s" "$prog"
}

#------------------------------------------------------------------------------
# Convert bytes to a more human-readable form.  Assumes reasonable limits for
# microcode preambles.
#------------------------------------------------------------------------------
get_human_size() {
    local n=$1  meg=$((1024 * 1024))

    if [ $n -le $meg ]; then
        echo "$((n / 1024))K"

    elif [ $n -le $(( 10 * meg)) ]; then
        echo "$(awk "BEGIN{printf \"%2.1f\", ($n / $meg)}")M"

    else
        echo "$((n / meg))M"
    fi
}

#------------------------------------------------------------------------------
# Show version information and then exit
#------------------------------------------------------------------------------
show_version() {
    local fmt="%20s version %s (%s)\n"
    printf "$fmt" "$ME"        "$VERSION"      "$VERSION_DATE"
    exit 0
}

#-------------------------------------------------------------------------------
# Send "$@".  Expects
#
#   SHORT_STACK               variable, list of single chars that stack
#   fatal(msg)                routine,  fatal("error message")
#   takes_param(arg)          routine,  true if arg takes a value
#   eval_argument(arg, [val]) routine,  do whatever you want with $arg and $val
#
# Sets "global" variable SHIFT to the number of arguments that have been read.
#-------------------------------------------------------------------------------
read_params() {
    # Most of this code is boiler-plate for parsing cmdline args
    SHIFT=0
    # These are the single-char options that can stack

    local arg val

    # Loop through the cmdline args
    while [ $# -gt 0 -a ${#1} -gt 0 -a -z "${1##-*}" ]; do
        arg=${1#-}
        shift
        SHIFT=$((SHIFT + 1))

        # Expand stacked single-char arguments
        case $arg in
            [$SHORT_STACK][$SHORT_STACK]*)
                if echo "$arg" | grep -q "^[$SHORT_STACK]\+$"; then
                    local old_cnt=$#
                    set -- $(echo $arg | sed -r 's/([a-zA-Z])/ -\1 /g') "$@"
                    SHIFT=$((SHIFT - $# + old_cnt))
                    continue
                fi;;
        esac

        # Deal with all options that take a parameter
        if takes_param "$arg"; then
            [ $# -lt 1 ] && fatal "Expected a parameter after: -$arg"
            val=$1
            [ -n "$val" -a -z "${val##-*}" ] \
                && fatal "Suspicious argument after -$arg: $val"
            SHIFT=$((SHIFT + 1))
            shift
        else
            case $arg in
                *=*)  val=${arg#*=} ;;
                  *)  val="???"     ;;
            esac
        fi

        eval_argument "$arg" "$val"
    done
}

#------------------------------------------------------------------------------
# Display the message unless --silent
#------------------------------------------------------------------------------
ssay() {
    [ "$SILENT" ] && return
    local fmt=$1; shift
    printf "$m_co$fmt$nc_co\n" "$@"
}

#------------------------------------------------------------------------------
# Display the message unless --silent or --quiet
#------------------------------------------------------------------------------
qsay() {
    [ "$QUIET$SILENT" ] && return
    local fmt=$1; shift
    printf "$m_co$fmt$nc_co\n" "$@"
}

#------------------------------------------------------------------------------
# Display error message on stderr and exit
#------------------------------------------------------------------------------
fatal() {
    local fmt=$1; shift
    printf "$ME$err_co fatal error:$warn_co $fmt\n" "$@" >&2
    exit 2
}

#------------------------------------------------------------------------------
# Display warning on stderr but don't exit
#------------------------------------------------------------------------------
warn() {
    local fmt=$1; shift
    printf "$ME$warn_co warning:$m_co $fmt\n" "$@" >&2
}

#------------------------------------------------------------------------------
# Convenience routines for "quoting" text using color
#------------------------------------------------------------------------------
pq()   { echo "$hi_co$*$m_co"      ;}
nq()   { echo "$num_co$*$m_co"     ;}
nqw()  { echo "$num_co$*$warn_co"  ;}
pqw()  { echo "$hi_co$*$warn_co"   ;}


#------------------------------------------------------------------------------
# Define color variables.  These are globals but are lower case in order to
# be less obtrusive.
#------------------------------------------------------------------------------
set_colors() {
   local e=$(printf "\e")

         black="$e[0;30m" ;    blue="$e[0;34m" ;    green="$e[0;32m" ;    cyan="$e[0;36m" ;
           red="$e[0;31m" ;  purple="$e[0;35m" ;    brown="$e[0;33m" ; lt_gray="$e[0;37m" ;
       dk_gray="$e[1;30m" ; lt_blue="$e[1;34m" ; lt_green="$e[1;32m" ; lt_cyan="$e[1;36m" ;
        lt_red="$e[1;31m" ; magenta="$e[1;35m" ;   yellow="$e[1;33m" ;   white="$e[1;37m" ;
         nc_co="$e[0m"    ;   brown="$e[0;33m" ;

           m_co=$cyan
          hi_co=$white
          err_co=$red
         bold_co=$yellow
         warn_co=$yellow
          num_co=$magenta
}

#------------------------------------------------------------------------------
#
#------------------------------------------------------------------------------
on_exit() {
    local dir=$INITRD_XTRA_DIR
    [ -z "$dir" ] && return
    test -d "$dir" || return

    qsay "Unmount xtra dir %s" "$(pq "$XTRA_DIR")"

    mountpoint -q "$dir" && umount "$dir"
    rmdir -p --ignore-fail-on-non-empty "$dir"
}

set_colors

# Note: see redirection in cmd() above
main "$@" 6>&1