Created
May 26, 2019 06:43
-
-
Save oberon-manjaro/8d34a12a60c47336435d1945086d8858 to your computer and use it in GitHub Desktop.
mkinitcpio
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# | |
# mkinitcpio - modular tool for building an initramfs images | |
# | |
declare -r version=25 | |
shopt -s extglob | |
### globals within mkinitcpio, but not intended to be used by hooks | |
# needed files/directories | |
_f_functions=/usr/lib/initcpio/functions | |
_f_config=/etc/mkinitcpio.conf | |
_d_hooks=/etc/initcpio/hooks:/usr/lib/initcpio/hooks | |
_d_install=/etc/initcpio/install:/usr/lib/initcpio/install | |
_d_firmware=({/usr,}/lib/firmware/updates {/usr,}/lib/firmware) | |
_d_presets=/etc/mkinitcpio.d | |
# options and runtime data | |
_optmoduleroot= _optgenimg= | |
_optcompress= _opttargetdir= | |
_optshowautomods=0 _optsavetree=0 _optshowmods=0 | |
_optquiet=1 _optcolor=1 | |
_optskiphooks=() _optaddhooks=() _hooks=() _optpreset=() | |
declare -A _runhooks _addedmodules _modpaths _autodetect_cache | |
# export a sane PATH | |
export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' | |
# Sanitize environment further | |
# GREP_OPTIONS="--color=always" will break everything | |
# CDPATH can affect cd and pushd | |
# LIBMOUNT_* options can affect findmnt and other tools | |
unset GREP_OPTIONS CDPATH "${!LIBMOUNT_@}" | |
usage() { | |
cat <<EOF | |
mkinitcpio $version | |
usage: ${0##*/} [options] | |
Options: | |
-A, --addhooks <hooks> Add specified hooks, comma separated, to image | |
-c, --config <config> Use alternate config file. (default: /etc/mkinitcpio.conf) | |
-g, --generate <path> Generate cpio image and write to specified path | |
-H, --hookhelp <hookname> Display help for given hook and exit | |
-h, --help Display this message and exit | |
-k, --kernel <kernelver> Use specified kernel version (default: $(uname -r)) | |
-L, --listhooks List all available hooks | |
-M, --automods Display modules found via autodetection | |
-n, --nocolor Disable colorized output messages | |
-p, --preset <file> Build specified preset from /etc/mkinitcpio.d | |
-P, --allpresets Process all preset files in /etc/mkinitcpio.d | |
-r, --moduleroot <dir> Root directory for modules (default: /) | |
-S, --skiphooks <hooks> Skip specified hooks, comma-separated, during build | |
-s, --save Save build directory. (default: no) | |
-d, --generatedir <dir> Write generated image into <dir> | |
-t, --builddir <dir> Use DIR as the temporary build directory | |
-V, --version Display version information and exit | |
-v, --verbose Verbose output (default: no) | |
-z, --compress <program> Use an alternate compressor on the image | |
EOF | |
} | |
version() { | |
cat <<EOF | |
mkinitcpio $version | |
EOF | |
} | |
cleanup() { | |
local err=${1:-$?} | |
if [[ $_d_workdir ]]; then | |
# when _optpreset is set, we're in the main loop, not a worker process | |
if (( _optsavetree )) && [[ -z ${_optpreset[*]} ]]; then | |
printf '%s\n' "${!_autodetect_cache[@]}" > "$_d_workdir/autodetect_modules" | |
msg "build directory saved in %s" "$_d_workdir" | |
else | |
rm -rf "$_d_workdir" | |
fi | |
fi | |
exit "$err" | |
} | |
resolve_kernver() { | |
local kernel=$1 arch= | |
if [[ -z $kernel ]]; then | |
uname -r | |
return 0 | |
fi | |
if [[ ${kernel:0:1} != / ]]; then | |
echo "$kernel" | |
return 0 | |
fi | |
arch=$(uname -m) | |
if [[ $arch != @(i?86|x86_64) ]]; then | |
error "kernel version extraction from image not supported for \`%s' architecture" "$arch" | |
return 1 | |
fi | |
if [[ ! -e $kernel ]]; then | |
error "specified kernel image does not exist: \`%s'" "$kernel" | |
return 1 | |
fi | |
kver "$kernel" && return | |
error "invalid kernel specified: \`%s'" "$1" | |
return 1 | |
} | |
hook_help() { | |
local resolved script=$(PATH=$_d_install type -p "$1") | |
# this will be true for broken symlinks as well | |
if [[ -z $script ]]; then | |
error "Hook '%s' not found" "$1" | |
return 1 | |
fi | |
if resolved=$(readlink "$script") && [[ ${script##*/} != "${resolved##*/}" ]]; then | |
msg "This hook is deprecated. See the '%s' hook" "${resolved##*/}" | |
return 0 | |
fi | |
. "$script" | |
if ! declare -f help >/dev/null; then | |
error "No help for hook $1" | |
return 1 | |
fi | |
msg "Help for hook '$1':" | |
help | |
list_hookpoints "$1" | |
} | |
hook_list() { | |
local p hook resolved | |
local -a paths hooklist depr | |
local ss_ordinals=(¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹) | |
IFS=: read -ra paths <<<"$_d_install" | |
for path in "${paths[@]}"; do | |
for hook in "$path"/*; do | |
[[ -e $hook || -L $hook ]] || continue | |
# handle deprecated hooks and point to replacement | |
if resolved=$(readlink "$hook") && [[ ${hook##*/} != "${resolved##*/}" ]]; then | |
resolved=${resolved##*/} | |
if ! index_of "$resolved" "${depr[@]}"; then | |
# deprecated hook | |
depr+=("$resolved") | |
_idx=$(( ${#depr[*]} - 1 )) | |
fi | |
hook+=${ss_ordinals[_idx]} | |
fi | |
hooklist+=("${hook##*/}") | |
done | |
done | |
msg "Available hooks" | |
printf '%s\n' "${hooklist[@]}" | sort -u | column -c"$(tput cols)" | |
if (( ${#depr[*]} )); then | |
echo | |
for p in "${!depr[@]}"; do | |
printf $'%s This hook is deprecated in favor of \'%s\'\n' \ | |
"${ss_ordinals[p]}" "${depr[p]}" | |
done | |
fi | |
} | |
compute_hookset() { | |
local h | |
for h in "${HOOKS[@]}" "${_optaddhooks[@]}"; do | |
in_array "$h" "${_optskiphooks[@]}" && continue | |
_hooks+=("$h") | |
done | |
} | |
build_image() { | |
local out=$1 compress=$2 errmsg= | |
local -a pipesave cpio_opts | |
case $compress in | |
cat) | |
msg "Creating uncompressed initcpio image: %s" "$out" | |
unset COMPRESSION_OPTIONS | |
;; | |
*) | |
msg "Creating %s-compressed initcpio image: %s" "$compress" "$out" | |
;;& | |
xz) | |
COMPRESSION_OPTIONS+=('--check=crc32') | |
;; | |
lz4) | |
COMPRESSION_OPTIONS+=('-l') | |
;; | |
esac | |
cpio_opts=('-0' '-o' '-H' 'newc') | |
(( _optquiet )) && cpio_opts+=('--quiet') | |
if (( EUID != 0 )); then | |
warning 'Not building as root, ownership cannot be preserved' | |
cpio_opts+=('-R' '0:0') | |
fi | |
pushd "$BUILDROOT" >/dev/null | |
find . -mindepth 1 -printf '%P\0' | | |
LANG=C bsdcpio "${cpio_opts[@]}" | | |
$compress "${COMPRESSION_OPTIONS[@]}" > "$out" | |
pipesave=("${PIPESTATUS[@]}") # save immediately | |
popd >/dev/null | |
if (( pipesave[0] )); then | |
errmsg="find reported an error" | |
elif (( pipesave[1] )); then | |
errmsg="bsdcpio reported an error" | |
elif (( pipesave[2] )); then | |
errmsg="$compress reported an error" | |
fi | |
if (( _builderrors )); then | |
warning "errors were encountered during the build. The image may not be complete." | |
fi | |
if [[ $errmsg ]]; then | |
error "Image generation FAILED: %s" "$errmsg" | |
elif (( _builderrors == 0 )); then | |
msg "Image generation successful" | |
fi | |
} | |
process_preset() ( | |
local preset=$1 preset_image= preset_options= | |
local -a preset_mkopts preset_cmd | |
if (( MKINITCPIO_PROCESS_PRESET )); then | |
error "You appear to be calling a preset from a preset. This is a configuration error." | |
cleanup 1 | |
fi | |
# allow path to preset file, else resolve it in $_d_presets | |
if [[ $preset != */* ]]; then | |
printf -v preset '%s/%s.preset' "$_d_presets" "$preset" | |
fi | |
. "$preset" || die "Failed to load preset: \`%s'" "$preset" | |
# Use -m and -v options specified earlier | |
(( _optquiet )) || preset_mkopts+=(-v) | |
(( _optcolor )) || preset_mkopts+=(-n) | |
(( _optsavetree )) && preset_mkopts+=(-s) | |
ret=0 | |
for p in "${PRESETS[@]}"; do | |
msg "Building image from preset: $preset: '$p'" | |
preset_cmd=("${preset_mkopts[@]}") | |
preset_kver=${p}_kver | |
if [[ ${!preset_kver:-$ALL_kver} ]]; then | |
preset_cmd+=(-k "${!preset_kver:-$ALL_kver}") | |
else | |
warning "No kernel version specified. Skipping image \`%s'" "$p" | |
continue | |
fi | |
preset_config=${p}_config | |
if [[ ${!preset_config:-$ALL_config} ]]; then | |
preset_cmd+=(-c "${!preset_config:-$ALL_config}") | |
else | |
warning "No configuration file specified. Skipping image \`%s'" "$p" | |
continue | |
fi | |
preset_image=${p}_image | |
if [[ ${!preset_image} ]]; then | |
preset_cmd+=(-g "${!preset_image}") | |
else | |
warning "No image file specified. Skipping image \`%s'" "$p" | |
continue | |
fi | |
preset_options=${p}_options | |
if [[ ${!preset_options} ]]; then | |
preset_cmd+=(${!preset_options}) # intentional word splitting | |
fi | |
msg2 "${preset_cmd[*]}" | |
MKINITCPIO_PROCESS_PRESET=1 "$0" "${preset_cmd[@]}" | |
(( $? )) && ret=1 | |
done | |
exit $ret | |
) | |
. "$_f_functions" | |
trap 'cleanup 130' INT | |
trap 'cleanup 143' TERM | |
_opt_short='A:c:g:H:hk:nLMPp:r:S:sd:t:Vvz:' | |
_opt_long=('add:' 'addhooks:' 'config:' 'generate:' 'hookhelp:' 'help' | |
'kernel:' 'listhooks' 'automods' 'moduleroot:' 'nocolor' 'allpresets' | |
'preset:' 'skiphooks:' 'save' 'generatedir:' 'builddir:' 'version' 'verbose' 'compress:') | |
parseopts "$_opt_short" "${_opt_long[@]}" -- "$@" || exit 1 | |
set -- "${OPTRET[@]}" | |
unset _opt_short _opt_long OPTRET | |
while :; do | |
case $1 in | |
# --add remains for backwards compat | |
-A|--add|--addhooks) | |
shift | |
IFS=, read -r -a add <<< "$1" | |
_optaddhooks+=("${add[@]}") | |
unset add | |
;; | |
-c|--config) | |
shift | |
_f_config=$1 | |
;; | |
-k|--kernel) | |
shift | |
KERNELVERSION=$1 | |
;; | |
-s|--save) | |
_optsavetree=1 | |
;; | |
-d|--generatedir) | |
shift | |
_opttargetdir=$1 | |
;; | |
-g|--generate) | |
shift | |
[[ -d $1 ]] && die "Invalid image path -- must not be a directory" | |
if ! _optgenimg=$(readlink -f "$1") || [[ ! -e ${_optgenimg%/*} ]]; then | |
die "Unable to write to path: \`%s'" "$1" | |
fi | |
;; | |
-h|--help) | |
usage | |
cleanup 0 | |
;; | |
-V|--version) | |
version | |
cleanup 0 | |
;; | |
-p|--preset) | |
shift | |
_optpreset+=("$1") | |
;; | |
-n|--nocolor) | |
_optcolor=0 | |
;; | |
-v|--verbose) | |
_optquiet=0 | |
;; | |
-S|--skiphooks) | |
shift | |
IFS=, read -r -a skip <<< "$1" | |
_optskiphooks+=("${skip[@]}") | |
unset skip | |
;; | |
-H|--hookhelp) | |
shift | |
hook_help "$1" | |
exit | |
;; | |
-L|--listhooks) | |
hook_list | |
exit 0 | |
;; | |
-M|--automods) | |
_optshowautomods=1 | |
;; | |
-P|--allpresets) | |
_optpreset=("$_d_presets"/*.preset) | |
[[ -e ${_optpreset[0]} ]] || die "No presets found in $_d_presets" | |
;; | |
-t|--builddir) | |
shift | |
export TMPDIR=$1 | |
;; | |
-z|--compress) | |
shift | |
_optcompress=$1 | |
;; | |
-r|--moduleroot) | |
shift | |
_optmoduleroot=$1 | |
;; | |
--) | |
shift | |
break 2 | |
;; | |
esac | |
shift | |
done | |
if [[ -t 1 ]] && (( _optcolor )); then | |
try_enable_color | |
fi | |
# insist that /proc and /dev be mounted (important for chroots) | |
# NOTE: avoid using mountpoint for this -- look for the paths that we actually | |
# use in mkinitcpio. Avoids issues like FS#26344. | |
[[ -e /proc/self/mountinfo ]] || die "/proc must be mounted!" | |
[[ -e /dev/fd ]] || die "/dev must be mounted!" | |
# use preset $_optpreset (exits after processing) | |
if (( ${#_optpreset[*]} )); then | |
map process_preset "${_optpreset[@]}" | |
exit | |
fi | |
if [[ $KERNELVERSION != 'none' ]]; then | |
KERNELVERSION=$(resolve_kernver "$KERNELVERSION") || cleanup 1 | |
_d_kmoduledir=$_optmoduleroot/lib/modules/$KERNELVERSION | |
[[ -d $_d_kmoduledir ]] || die "'$_d_kmoduledir' is not a valid kernel module directory" | |
fi | |
_d_workdir=$(initialize_buildroot "$KERNELVERSION" "$_opttargetdir") || cleanup 1 | |
BUILDROOT=${_opttargetdir:-$_d_workdir/root} | |
. "$_f_config" || die "Failed to read configuration \`%s'" "$_f_config" | |
arrayize_config | |
# after returning, hooks are populated into the array '_hooks' | |
# HOOKS should not be referenced from here on | |
compute_hookset | |
if (( ${#_hooks[*]} == 0 )); then | |
die "Invalid config: No hooks found" | |
fi | |
if (( _optshowautomods )); then | |
msg "Modules autodetected" | |
PATH=$_d_install . 'autodetect' | |
build | |
printf '%s\n' "${!_autodetect_cache[@]}" | sort | |
cleanup 0 | |
fi | |
if [[ $_optgenimg ]]; then | |
# check for permissions. if the image doesn't already exist, | |
# then check the directory | |
if [[ ( -e $_optgenimg && ! -w $_optgenimg ) || | |
( ! -d ${_optgenimg%/*} || ! -w ${_optgenimg%/*} ) ]]; then | |
die 'Unable to write to %s' "$_optgenimg" | |
fi | |
_optcompress=${_optcompress:-${COMPRESSION:-gzip}} | |
if ! type -P "$_optcompress" >/dev/null; then | |
warning "Unable to locate compression method: %s" "$_optcompress" | |
_optcompress=cat | |
fi | |
msg "Starting build: %s" "$KERNELVERSION" | |
elif [[ $_opttargetdir ]]; then | |
msg "Starting build: %s" "$KERNELVERSION" | |
else | |
msg "Starting dry run: %s" "$KERNELVERSION" | |
fi | |
# set functrace and trap to catch errors in add_* functions | |
declare -i _builderrors=0 | |
set -o functrace | |
trap '(( $? )) && [[ $FUNCNAME = add_* ]] && (( ++_builderrors ))' RETURN | |
# prime the _addedmodules list with the builtins for this kernel | |
if [[ -r $_d_kmoduledir/modules.builtin ]]; then | |
while IFS=/ read -a path; do | |
modname=${path[-1]%.ko} | |
_addedmodules["${modname//-/_}"]=2 | |
done <"$_d_kmoduledir/modules.builtin" | |
unset modname path | |
fi | |
map run_build_hook "${_hooks[@]}" || (( ++_builderrors )) | |
# process config file | |
parse_config "$_f_config" | |
# switch out the error handler to catch all errors | |
trap -- RETURN | |
trap '(( ++_builderrors ))' ERR | |
set -o errtrace | |
install_modules "${!_modpaths[@]}" | |
# unset errtrace and trap | |
set +o functrace | |
set +o errtrace | |
trap -- ERR | |
# this is simply a nice-to-have -- it doesn't matter if it fails. | |
ldconfig -r "$BUILDROOT" &>/dev/null | |
if [[ $_optgenimg ]]; then | |
build_image "$_optgenimg" "$_optcompress" | |
elif [[ $_opttargetdir ]]; then | |
msg "Build complete." | |
else | |
msg "Dry run complete, use -g IMAGE to generate a real image" | |
fi | |
cleanup $(( !!_builderrors )) | |
# vim: set ft=sh ts=4 sw=4 et: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment