From d9dc638956a46982f2545010b5ff1c119452430e Mon Sep 17 00:00:00 2001 From: Tobias Powalowski Date: Thu, 16 Feb 2023 18:26:40 +0100 Subject: [PATCH] add mkinitcpio speed functions --- usr/lib/archboot/container.sh | 4 + usr/lib/archboot/run/container.sh | 1 + usr/share/archboot/patches/functions | 999 +++++++++++++++++++++++++++ 3 files changed, 1004 insertions(+) create mode 100644 usr/share/archboot/patches/functions diff --git a/usr/lib/archboot/container.sh b/usr/lib/archboot/container.sh index 51556bfb7..ed735361b 100644 --- a/usr/lib/archboot/container.sh +++ b/usr/lib/archboot/container.sh @@ -190,4 +190,8 @@ _set_hostname() { echo "Setting hostname to archboot..." echo 'archboot' > "${1}/etc/hostname" } + +_speed_patch() { + cp /usr/share/archboot/patches/functions "${1}"/usr/lib/initcpio/ +} # vim: set ft=sh ts=4 sw=4 et: diff --git a/usr/lib/archboot/run/container.sh b/usr/lib/archboot/run/container.sh index 5d50ec2e9..7746bde87 100755 --- a/usr/lib/archboot/run/container.sh +++ b/usr/lib/archboot/run/container.sh @@ -56,5 +56,6 @@ fi _change_pacman_conf "${1}" || exit 1 _reproducibility "${1}" _set_hostname "${1}" || exit 1 +_speed_patch echo "Finished container setup in ${1}." # vim: set ft=sh ts=4 sw=4 et: diff --git a/usr/share/archboot/patches/functions b/usr/share/archboot/patches/functions new file mode 100644 index 000000000..80eaa803b --- /dev/null +++ b/usr/share/archboot/patches/functions @@ -0,0 +1,999 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: GPL-2.0-only + +parseopts() { + local opt= optarg= i= shortopts=$1 + local -a longopts=() unused_argv=() + + shift + while [[ $1 && $1 != '--' ]]; do + longopts+=("$1") + shift + done + shift + + longoptmatch() { + local o longmatch=() + for o in "${longopts[@]}"; do + if [[ ${o%:} = "$1" ]]; then + longmatch=("$o") + break + fi + [[ ${o%:} = "$1"* ]] && longmatch+=("$o") + done + + case ${#longmatch[*]} in + 1) + # success, override with opt and return arg req (0 == none, 1 == required) + opt=${longmatch%:} + if [[ $longmatch = *: ]]; then + return 1 + else + return 0 + fi ;; + 0) + # fail, no match found + return 255 ;; + *) + # fail, ambiguous match + printf "%s: option '%s' is ambiguous; possibilities:%s\n" "${0##*/}" \ + "--$1" "$(printf " '%s'" "${longmatch[@]%:}")" + return 254 ;; + esac + } + + while (( $# )); do + case $1 in + --) # explicit end of options + shift + break + ;; + -[!-]*) # short option + for (( i = 1; i < ${#1}; i++ )); do + opt=${1:i:1} + + # option doesn't exist + if [[ $shortopts != *$opt* ]]; then + printf "%s: invalid option -- '%s'\n" "${0##*/}" "$opt" + OPTRET=(--) + return 1 + fi + + OPTRET+=("-$opt") + # option requires optarg + if [[ $shortopts = *$opt:* ]]; then + # if we're not at the end of the option chunk, the rest is the optarg + if (( i < ${#1} - 1 )); then + OPTRET+=("${1:i+1}") + break + # if we're at the end, grab the the next positional, if it exists + elif (( i == ${#1} - 1 )) && [[ $2 ]]; then + OPTRET+=("$2") + shift + break + # parse failure + else + printf "%s: option '%s' requires an argument\n" "${0##*/}" "-$opt" + OPTRET=(--) + return 1 + fi + fi + done + ;; + --?*=*|--?*) # long option + IFS='=' read -r opt optarg <<< "${1#--}" + longoptmatch "$opt" + case $? in + 0) + if [[ $optarg ]]; then + printf "%s: option '--%s' doesn't allow an argument\n" "${0##*/}" "$opt" + OPTRET=(--) + return 1 + else + OPTRET+=("--$opt") + fi + ;; + 1) + # --longopt=optarg + if [[ $optarg ]]; then + OPTRET+=("--$opt" "$optarg") + # --longopt optarg + elif [[ $2 ]]; then + OPTRET+=("--$opt" "$2" ) + shift + else + printf "%s: option '--%s' requires an argument\n" "${0##*/}" "$opt" + OPTRET=(--) + return 1 + fi + ;; + 254) + # ambiguous option -- error was reported for us by longoptmatch() + OPTRET=(--) + return 1 + ;; + 255) + # parse failure + printf "%s: unrecognized option '%s'\n" "${0##*/}" "--$opt" + OPTRET=(--) + return 1 + ;; + esac + ;; + *) # non-option arg encountered, add it as a parameter + unused_argv+=("$1") + ;; + esac + shift + done + + # add end-of-opt terminator and any leftover positional parameters + OPTRET+=('--' "${unused_argv[@]}" "$@") + unset longoptmatch + + return 0 +} + +kver_x86() { + # scrape the version out of the kernel image. locate the offset + # to the version string by reading 2 bytes out of image at at + # address 0x20E. this leads us to a string of, at most, 128 bytes. + # read the first word from this string as the kernel version. + local kver offset=$(hexdump -s 526 -n 2 -e '"%0d"' "$1") + [[ $offset = +([0-9]) ]] || return 1 + + read kver _ < \ + <(dd if="$1" bs=1 count=127 skip=$(( offset + 0x200 )) 2>/dev/null) + + printf '%s' "$kver" +} + +detect_compression() { + local bytes + + read -rd '' bytes < <(hexdump -n 6 -e '"%c"' "$1") + case $bytes in + $'\xfd7zXZ') + echo 'xz' + return + ;; + esac + + read -rd '' bytes < <(hexdump -n 4 -e '"%c"' "$1") + if [[ $bytes = $'\x89LZO' ]]; then + echo 'lzop' + return + fi + + read -rd '' bytes < <(hexdump -n 2 -e '"%x"' "$1") + if [[ $bytes = '8b1f' ]]; then + echo 'gzip' + return + fi + + read -rd '' bytes < <(hexdump -n 4 -e '"%x"' "$1") + 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 + + read -rd '' bytes < <(hexdump -n 3 -e '"%c"' "$1") + 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. + read -rd '' bytes < <(hexdump -n 3 -e '"%x"' "$1") + if [[ $bytes = '5d' ]]; then + echo 'lzma' + return + fi + + # out of ideas, assuming uncompressed +} + +kver_generic() { + # 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 + + [[ $(detect_compression "$1") == 'gzip' ]] && reader=zcat + + read _ _ kver _ < <($reader "$1" | grep -m1 -aoE 'Linux version .(\.[-[:alnum:]+]+)+') + + printf '%s' "$kver" +} + +kver() { + # this is intentionally very loose. only ensure that we're + # dealing with some sort of string that starts with something + # resembling dotted decimal notation. remember that there's no + # requirement for CONFIG_LOCALVERSION to be set. + local kver re='^[[:digit:]]+(\.[[:digit:]]+)+' + + local arch=$(uname -m) + if [[ $arch == @(i?86|x86_64) ]]; then + kver=$(kver_x86 "$1") + else + kver=$(kver_generic "$1") + fi + + [[ $kver =~ $re ]] || return 1 + + printf '%s' "$kver" +} + +plain() { + local mesg=$1; shift + printf " $_color_bold$mesg$_color_none\n" "$@" >&1 +} + +quiet() { + (( _optquiet )) || plain "$@" +} + +msg() { + local mesg=$1; shift + printf "$_color_green==>$_color_none $_color_bold$mesg$_color_none\n" "$@" >&1 +} + +msg2() { + local mesg=$1; shift + printf " $_color_blue->$_color_none $_color_bold$mesg$_color_none\n" "$@" >&1 +} + +warning() { + local mesg=$1; shift + printf "$_color_yellow==> WARNING:$_color_none $_color_bold$mesg$_color_none\n" "$@" >&2 +} + +error() { + local mesg=$1; shift + printf "$_color_red==> ERROR:$_color_none $_color_bold$mesg$_color_none\n" "$@" >&2 + return 1 +} + +die() { + error "$@" + cleanup 1 +} + +map() { + local r=0 + for _ in "${@:2}"; do + "$1" "$_" || (( $# > 255 ? r=1 : ++r )) + done + return $r +} + +arrayize_config() { + set -f + [[ ${MODULES@a} != *a* ]] && MODULES=($MODULES) + [[ ${BINARIES@a} != *a* ]] && BINARIES=($BINARIES) + [[ ${FILES@a} != *a* ]] && FILES=($FILES) + [[ ${HOOKS@a} != *a* ]] && HOOKS=($HOOKS) + [[ ${COMPRESSION_OPTIONS@a} != *a* ]] && COMPRESSION_OPTIONS=($COMPRESSION_OPTIONS) + set +f +} + +in_array() { + # Search for an element in an array. + # $1: needle + # ${@:2}: haystack + + local item= needle=$1; shift + + for item in "$@"; do + [[ $item = $needle ]] && return 0 # Found + done + return 1 # Not Found +} + +index_of() { + # get the array index of an item. sets the global var _idx with + # index and returns 0 if found, otherwise returns 1. + local item=$1; shift + + for (( _idx=1; _idx <= $#; _idx++ )); do + if [[ $item = ${!_idx} ]]; then + (( --_idx )) + return 0 + fi + done + + # not found + unset _idx + return 1 +} + +funcgrep() { + awk -v funcmatch="$1" ' + /^[[:space:]]*[[:alnum:]_]+[[:space:]]*\([[:space:]]*\)/ { + match($1, funcmatch) + print substr($1, RSTART, RLENGTH) + }' "$2" +} + +list_hookpoints() { + local funcs script + + script=$(PATH=$_d_hooks type -P "$1") || return 0 + + mapfile -t funcs < <(funcgrep '^run_[[:alnum:]_]+' "$script") + + echo + msg "This hook has runtime scripts:" + in_array run_earlyhook "${funcs[@]}" && msg2 "early hook" + in_array run_hook "${funcs[@]}" && msg2 "pre-mount hook" + in_array run_latehook "${funcs[@]}" && msg2 "post-mount hook" + in_array run_cleanuphook "${funcs[@]}" && msg2 "cleanup hook" +} + +modprobe() { + command modprobe -d "$_optmoduleroot" -S "$KERNELVERSION" "$@" +} + +auto_modules() { + # Perform auto detection of modules via sysfs. + + local mods= + + mapfile -t mods < <(find /sys/devices -name uevent \ + -exec sort -u {} + | awk -F= '$1 == "MODALIAS" && !_[$0]++') + mapfile -t mods < <(modprobe -S "$1" -qaR "${mods[@]#MODALIAS=}") + + (( ${#mods[*]} )) && printf "%s\n" "${mods[@]//-/_}" +} + +all_modules() { + # Add modules to the initcpio, filtered by grep. + # $@: filter arguments to grep + # -f FILTER: ERE to filter found modules + + local -i count=0 + local mod= OPTIND= OPTARG= filter=() + + while getopts ':f:' flag; do + case $flag in f) filter+=("$OPTARG") ;; esac + done + shift $(( OPTIND - 1 )) + + while read -r -d '' mod; do + (( ++count )) + + for f in "${filter[@]}"; do + [[ $mod =~ $f ]] && continue 2 + done + + mod=${mod##*/} + mod="${mod%.ko*}" + printf '%s\n' "${mod//-/_}" + done < <(find "$_d_kmoduledir" -name '*.ko*' -print0 2>/dev/null | grep -EZz "$@") + + (( count )) +} + +add_all_modules() { + # Add modules to the initcpio. + # $@: arguments to all_modules + + local mod mods + + mapfile -t mods < <(all_modules "$@") + map add_module "${mods[@]}" + + return $(( !${#mods[*]} )) +} + +add_checked_modules() { + # Add modules to the initcpio, filtered by the list of autodetected + # modules. + # $@: arguments to all_modules + + local mod mods + + if (( ${#_autodetect_cache[*]} )); then + mapfile -t mods < <(all_modules "$@" | grep -xFf <(printf '%s\n' "${!_autodetect_cache[@]}")) + else + mapfile -t mods < <(all_modules "$@") + fi + + map add_module "${mods[@]}" + + return $(( !${#mods[*]} )) +} + +add_firmware() { + # add a firmware file to the image. + # $1: firmware path fragment + + local fw fwpath r=1 + + for fw; do + for fwpath in "${_d_firmware[@]}"; do + if [[ -f $fwpath/$fw.xz ]]; then + add_file "$fwpath/$fw.xz" "$fwpath/$fw.xz" && r=0 + break + elif [[ -f "$fwpath/$fw.zst" ]]; then + add_file "$fwpath/$fw.zst" "$fwpath/$fw.zst" && r=0 + break + elif [[ -f $fwpath/$fw ]]; then + add_file "$fwpath/$fw" "$fwpath/$fw" && r=0 + break + fi + done + done + + return $r +} + +add_module() { + # Add a kernel module to the initcpio image. Dependencies will be + # discovered and added. + # $1: module name + + local target= module= softdeps= deps= field= value= firmware=() + local ign_errors=0 found=0 + + [[ $KERNELVERSION == none ]] && return 0 + + if [[ $1 = *\? ]]; then + ign_errors=1 + set -- "${1%?}" + fi + + target=${1%.ko*} target=${target//-/_} + + # skip expensive stuff if this module has already been added + (( _addedmodules["$target"] == 1 )) && return + + while IFS=':= ' read -r -d '' field value; do + case "$field" in + filename) + # Only add modules with filenames that look like paths (e.g. + # it might be reported as "(builtin)"). We'll defer actually + # checking whether or not the file exists -- any errors can be + # handled during module install time. + if [[ $value = /* ]]; then + found=1 + module=${value##*/} module=${module%.ko*} + quiet "adding module: %s (%s)" "$module" "$value" + _modpaths["$value"]=1 + _addedmodules["${module//-/_}"]=1 + fi + ;; + depends) + IFS=',' read -r -a deps <<< "$value" + map add_module "${deps[@]}" + ;; + firmware) + firmware+=("$value") + ;; + softdep) + read -ra softdeps <<<"$value" + for module in "${softdeps[@]}"; do + [[ $module == *: ]] && continue + add_module "$module?" + done + ;; + esac + done < <(modinfo -b "$_optmoduleroot" -k "$KERNELVERSION" -0 "$target" 2>/dev/null) + + if (( !found )); then + (( ign_errors || _addedmodules["$target"] )) && return 0 + error "module not found: \`%s'" "$target" + return 1 + fi + + if (( ${#firmware[*]} )); then + add_firmware "${firmware[@]}" || + warning 'Possibly missing firmware for module: %s' "$target" + fi + + # handle module quirks + case $target in + fat) + add_module "nls_ascii?" # from CONFIG_FAT_DEFAULT_IOCHARSET + add_module "nls_cp437?" # from CONFIG_FAT_DEFAULT_CODEPAGE + ;; + ocfs2) + add_module "configfs?" + ;; + btrfs) + add_module "libcrc32c?" + ;; + f2fs) + add_module "crypto-crc32?" + ;; + ext4) + add_module "crypto-crc32c?" + ;; + esac +} + +add_full_dir() { + # Add a directory and all its contents, recursively, to the initcpio image. + # No parsing is performed and the contents of the directory is added as is. + # $1: path to directory + # $2: glob pattern to filter file additions (optional) + # $3: path prefix that will be stripped off from the image path (optional) + + local f= filter=${2:-*} strip_prefix=$3 + + if [[ -n $1 && -d $1 ]]; then + add_dir "$1" + + for f in "$1"/*; do + if [[ -L $f ]]; then + if [[ $f = $filter ]]; then + add_symlink "${f#$strip_prefix}" "$(readlink "$f")" + fi + elif [[ -d $f ]]; then + add_full_dir "$f" "$filter" "$strip_prefix" + elif [[ -f $f ]]; then + if [[ $f = $filter ]]; then + add_file "$f" "${f#$strip_prefix}" + fi + fi + done + fi +} + +add_dir() { + # add a directory (with parents) to $BUILDROOT + # $1: pathname on initcpio + # $2: mode (optional) + + if [[ -z $1 || $1 != /?* ]]; then + return 1 + fi + + local path=$1 mode=${2:-755} + + if [[ -d $BUILDROOT$1 ]]; then + # ignore dir already exists + return 0 + fi + + quiet "adding dir: %s" "$path" + command install -dm$mode "$BUILDROOT$path" +} + +add_symlink() { + # Add a symlink to the initcpio image. There is no checking done + # to ensure that the target of the symlink exists. + # $1: pathname of symlink on image + # $2: absolute path to target of symlink (optional, can be read from $1) + + local name="$1" target="${2:-$1}" linkobject + + (( $# == 1 || $# == 2 )) || return 1 + + # find out the link target + if [[ "$name" == "$target" ]]; then + linkobject="$(find "$target" -prune -printf '%l')" + # use relative path if the target is a file in the same directory as the link + # anything more would lead to the insanity of parsing each element in its path + if [[ "$linkobject" != *'/'* && ! -L "${name%/*}/${linkobject}" ]]; then + target="$linkobject" + else + target="$(realpath -eq -- "$target")" + fi + elif [[ -L "$target" ]]; then + target="$(realpath -eq -- "$target")" + fi + if [[ -z "$target" ]]; then + error 'invalid symlink: %s' "$name" + return 1 + fi + + add_dir "${name%/*}" + + if [[ -L $BUILDROOT$1 ]]; then + quiet "overwriting symlink %s -> %s" "$name" "$target" + else + quiet "adding symlink: %s -> %s" "$name" "$target" + fi + ln -sfn "$target" "$BUILDROOT$name" +} + +add_file() { + # Add a plain file to the initcpio image. No parsing is performed and only + # the singular file is added. + # $1: path to file + # $2: destination on initcpio (optional, defaults to same as source) + # $3: mode + + (( $# )) || return 1 + + # determine source and destination + local src="$1" dest="${2:-$1}" mode="$3" srcrealpath + + if [[ ! -f $src ]]; then + error "file not found: \`%s'" "$src" + return 1 + fi + if [[ ! -e "${BUILDROOT}${dest}" ]]; then + quiet 'adding file: %s' "$dest" + if [[ "$src" != "$dest" ]]; then + command tar --transform="s|"$src"|"$dest"|" -C / -cpf - ."$src" | tar -C "${BUILDROOT}" -xpf - + else + command tar -C / -cpf - ."$src" | tar -C "${BUILDROOT}" -xpf - + fi + if [[ -L "$src" ]]; then + srcrealpath="$(realpath -- "$src")" + add_file "$srcrealpath" "$srcrealpath" "$mode" + else + if [[ -n $mode ]]; then + chmod "$3" ${BUILDROOT}${dest} + fi + fi + fi +} + +add_runscript() { + # Adds a runtime script to the initcpio image. The name is derived from the + # script which calls it as the basename of the caller. + + local funcs fn script hookname=${BASH_SOURCE[1]##*/} + + if ! script=$(PATH=$_d_hooks type -P "$hookname"); then + error "runtime script for \`%s' not found" "$hookname" + return + fi + + if [[ -L "$script" ]]; then + script="$(realpath -- "$script")" + fi + add_file "$script" "/hooks/$hookname" 755 + + mapfile -t funcs < <(funcgrep '^run_[[:alnum:]_]+' "$script") + + for fn in "${funcs[@]}"; do + case $fn in + run_earlyhook) + _runhooks['early']+=" $hookname" + ;; + run_hook) + _runhooks['hooks']+=" $hookname" + ;; + run_latehook) + _runhooks['late']+=" $hookname" + ;; + run_cleanuphook) + _runhooks['cleanup']="$hookname ${_runhooks['cleanup']}" + ;; + esac + done +} + +add_binary() { + # Add a binary file to the initcpio image. library dependencies will + # be discovered and added. + # $1: path to binary + # $2: destination on initcpio (optional, defaults to same as source) + + local -a sodeps + local line='' regex='' binary='' dest='' mode='' sodep='' resolved='' shebang='' interpreter='' + + if [[ ${1:0:1} != '/' ]]; then + binary=$(type -P "$1") + else + binary=$1 + fi + + if [[ ! -f $binary ]]; then + error "file not found: \`%s'" "$1" + return 1 + fi + + dest=${2:-$binary} + + add_file "$binary" "$dest" + + # non-binaries + if ! lddout="$(ldd "$binary" 2>/dev/null)"; then + # detect if the file has a shebang + if IFS='' LC_ALL=C read -rn2 -d '' shebang < "$binary" && [[ "$shebang" == '#!' ]]; then + read -r shebang < "$binary" + interpreter="${shebang##\#\!}" + # strip /usr/bin/env and warn if it is missing + if [[ "$interpreter" == '/usr/bin/env'* ]]; then + [[ -e "${BUILDROOT}/usr/bin/env" ]] || warning "Possibly missing '/usr/bin/env' for script: %s" "$binary" + interpreter="${interpreter##'/usr/bin/env'[[:space:]]}" + fi + # strip parameters + interpreter="${interpreter%%[[:space:]]*}" + # lookup the interpreter in PATH on BUILDROOT + if [[ "$interpreter" != '/'* ]] && PATH="${BUILDROOT}/usr/local/sbin:${BUILDROOT}/usr/local/bin:${BUILDROOT}/usr/bin" type -P "$interpreter" &>/dev/null; then + interpreter="$(PATH="${BUILDROOT}/usr/local/sbin:${BUILDROOT}/usr/local/bin:${BUILDROOT}/usr/bin" type -P "$interpreter")" + fi + + # check if the interpreter exists in BUILDROOT + if [[ ! -e "${BUILDROOT}/${interpreter}" ]]; then + warning "Possibly missing '%s' for script: %s" "$interpreter" "$binary" + fi + fi + return 0 + fi + + # resolve sodeps + regex='^(|.+ )(/.+) \(0x[a-fA-F0-9]+\)' + while read -r line; do + if [[ $line =~ $regex ]]; then + sodep=${BASH_REMATCH[2]} + elif [[ $line = *'not found' ]]; then + error "binary dependency \`%s' not found for \`%s'" "${line%% *}" "$1" + (( ++_builderrors )) + continue + fi + + if [[ -f $sodep && ! -e $BUILDROOT$sodep ]]; then + add_file "$sodep" "$sodep" + fi + done <<< "$lddout" + + return 0 +} + +add_udev_rule() { + # Add an udev rules file to the initcpio image. Dependencies on binaries + # will be discovered and added. + # $1: path to rules file (or name of rules file) + + local rules="$1" rule= key= value= binary= + + if [[ ${rules:0:1} != '/' ]]; then + rules=$(PATH=/usr/lib/udev/rules.d:/lib/udev/rules.d type -P "$rules") + fi + if [[ -z $rules ]]; then + # complain about not found rules + return 1 + fi + + add_file "$rules" /usr/lib/udev/rules.d/"${rules##*/}" + + while IFS=, read -ra rule; do + # skip empty lines, comments + [[ -z $rule || $rule = @(+([[:space:]])|#*) ]] && continue + + for pair in "${rule[@]}"; do + IFS=' =' read -r key value <<< "$pair" + case $key in + RUN{program}|RUN+|IMPORT{program}|ENV{REMOVE_CMD}) + # strip quotes + binary=${value//[\"\']/} + # just take the first word as the binary name + binary=${binary%% *} + [[ ${binary:0:1} == '$' ]] && continue + if [[ ${binary:0:1} != '/' ]]; then + binary=$(PATH=/usr/lib/udev:/lib/udev type -P "$binary") + fi + add_binary "$binary" + ;; + esac + done + done <"$rules" +} + +parse_config() { + # parse key global variables set by the config file. + + map add_module "${MODULES[@]}" + map add_binary "${BINARIES[@]}" + map add_file "${FILES[@]}" + + tee "$BUILDROOT/buildconfig" < "$1" | { + # When MODULES is not an array (but instead implicitly converted at + # startup), sourcing the config causes the string value of MODULES + # to be assigned as MODULES[0]. Avoid this by explicitly unsetting + # MODULES before re-sourcing the config. + unset MODULES + + . /dev/stdin + + # arrayize MODULES if necessary. + [[ ${MODULES@a} != *a* ]] && read -ra MODULES <<<"${MODULES//-/_}" + + for mod in "${MODULES[@]%\?}"; do + mod=${mod//-/_} + # only add real modules (2 == builtin) + (( _addedmodules["$mod"] == 1 )) && add+=("$mod") + done + (( ${#add[*]} )) && printf 'MODULES="%s"\n' "${add[*]}" + + printf '%s="%s"\n' \ + 'EARLYHOOKS' "${_runhooks['early']# }" \ + 'HOOKS' "${_runhooks['hooks']# }" \ + 'LATEHOOKS' "${_runhooks['late']# }" \ + 'CLEANUPHOOKS' "${_runhooks['cleanup']% }" + } >"$BUILDROOT/config" +} + +initialize_buildroot() { + # creates a temporary directory for the buildroot and initialize it with a + # basic set of necessary directories and symlinks + + local workdir= kernver=$1 arch=$(uname -m) buildroot osreleasefile + + if ! workdir=$(mktemp -d --tmpdir mkinitcpio.XXXXXX); then + error 'Failed to create temporary working directory in %s' "${TMPDIR:-/tmp}" + return 1 + fi + buildroot=${2:-$workdir/root} + + if [[ ! -w ${2:-$workdir} ]]; then + error 'Unable to write to build root: %s' "$buildroot" + return 1 + fi + + # base directory structure + install -dm755 "$buildroot"/{new_root,proc,sys,dev,run,tmp,var,etc,usr/{local{,/bin,/sbin,/lib},lib,bin}} + ln -s "usr/lib" "$buildroot/lib" + ln -s "bin" "$buildroot/usr/sbin" + ln -s "usr/bin" "$buildroot/bin" + ln -s "usr/bin" "$buildroot/sbin" + ln -s "/run" "$buildroot/var/run" + + case $arch in + x86_64) + ln -s "lib" "$buildroot/usr/lib64" + ln -s "usr/lib" "$buildroot/lib64" + ;; + esac + + # mkinitcpio version stamp + printf '%s' "$version" >"$buildroot/VERSION" + + # kernel module dir + [[ $kernver != none ]] && install -dm755 "$buildroot/usr/lib/modules/$kernver/kernel" + + # mount tables + ln -s ../proc/self/mounts "$buildroot/etc/mtab" + >"$buildroot/etc/fstab" + + # add os-release and initrd-release for systemd + if [[ -e /etc/os-release ]]; then + if [[ -L /etc/os-release ]]; then + osreleasefile="$(realpath -- /etc/os-release)" + install -Dm0644 "$osreleasefile" "${buildroot}${osreleasefile}" + cp -adT /etc/os-release "${buildroot}/etc/os-release" + cp -adT /etc/os-release "${buildroot}/etc/initrd-release" + else + install -Dm0644 /etc/os-release "${buildroot}/etc/os-release" + ln -sT os-release "${buildroot}/etc/initrd-release" + fi + else + >"$buildroot/etc/initrd-release" + fi + + # add a blank ld.so.conf to keep ldconfig happy + >"$buildroot/etc/ld.so.conf" + + printf '%s' "$workdir" +} + +run_build_hook() { + local hook=$1 script= resolved= + local MODULES=() BINARIES=() FILES=() SCRIPT= + + # find script in install dirs + if ! script=$(PATH=$_d_install type -P "$hook"); then + error "Hook '$hook' cannot be found" + return 1 + fi + + # check for deprecation + if resolved=$(readlink -e "$script") && [[ ${script##*/} != "${resolved##*/}" ]]; then + warning "Hook '%s' is deprecated. Replace it with '%s' in your config" \ + "${script##*/}" "${resolved##*/}" + script=$resolved + fi + + # source + unset -f build + if ! . "$script"; then + error 'Failed to read %s' "$script" + return 1 + fi + + if ! declare -f build >/dev/null; then + error 'Hook '$script' has no build function' + return 1 + fi + + # run + if (( _optquiet )); then + msg2 "Running build hook: [%s]" "${script##*/}" + else + msg2 "Running build hook: [%s]" "$script" + fi + build + + # if we made it this far, return successfully. Hooks can + # do their own error catching if it's severe enough, and + # we already capture errors from the add_* functions. + return 0 +} + +try_enable_color() { + local colors + + if ! colors=$(tput colors 2>/dev/null); then + warning "Failed to enable color. Check your TERM environment variable" + return + fi + + if (( colors > 0 )) && tput setaf 0 &>/dev/null; then + _color_none=$(tput sgr0) + _color_bold=$(tput bold) + _color_blue=$_color_bold$(tput setaf 4) + _color_green=$_color_bold$(tput setaf 2) + _color_red=$_color_bold$(tput setaf 1) + _color_yellow=$_color_bold$(tput setaf 3) + fi +} + +install_modules() { + local m + local -a xz_comp gz_comp zst_comp + + [[ $KERNELVERSION == none ]] && return 0 + + if (( $# == 0 )); then + warning "No modules were added to the image. This is probably not what you want." + return 0 + fi + + for m in "$@"; do + add_file "$m" "$m" + # unzip modules prior to recompression + if [[ "$MODULES_DECOMPRESS" == 'yes' ]]; then + case "$m" in + *.xz) + xz_comp+=("$BUILDROOT/$m") + ;; + *.gz) + gz_comp+=("$BUILDROOT/$m") + ;; + *.zst) + zst_comp+=("$BUILDROOT/$m") + ;; + esac + fi + done + + (( ${#xz_comp[*]} )) && xz -d "${xz_comp[@]}" + (( ${#gz_comp[*]} )) && gzip -d "${gz_comp[@]}" + (( ${#zst_comp[*]} )) && zstd -d --rm -q "${zst_comp[@]}" + + msg "Generating module dependencies" + map add_file "$_d_kmoduledir"/modules.{builtin,builtin.modinfo,order} + depmod -b "$BUILDROOT" "$KERNELVERSION" + + # remove all non-binary module.* files (except devname for on-demand module loading) + rm "${BUILDROOT}${_d_kmoduledir}"/modules.!(*.bin|devname|softdep) +} + +# vim: set ft=sh ts=4 sw=4 et: