#!/bin/bash # mkinitcpio - modular tool for building an init ramfs cpio image # # IMPORTANT: We need to keep a common base syntax here because # some of these hooks/scripts need to run under busybox's ash - # therefore, the following constraints should be enforced: # variables should be quoted and bracketed "${SOMEVAR}" # inline execution should be done with $() instead of backticks # use -z "${var}" to test for nulls/empty strings # in case of embedded spaces, quote all path names and string comparisons # declare -r version=%VERSION% shopt -s extglob # Settings FUNCTIONS=functions CONFIG=mkinitcpio.conf HOOKDIR=(hooks /usr/lib/initcpio/hooks /lib/initcpio/hooks) INSTDIR=(install /usr/lib/initcpio/install /lib/initcpio/install) PRESETDIR=mkinitcpio.d COMPRESSION=gzip declare MODULE_FILE= GENIMG= PRESET= COMPRESSION_OPTIONS= BUILDROOT= declare NC= BOLD= BLUE= GREEN= RED= YELLOW= declare -i QUIET=1 SHOW_AUTOMODS=0 SAVELIST=0 COLOR=1 declare -a SKIPHOOKS ADDED_MODULES MODPATHS declare -A RUNHOOKS # 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 unset GREP_OPTIONS CDPATH usage() { cat < Add specified hooks, comma separated, to image -c, --config Use alternate config file. (default: /etc/mkinitcpio.conf) -g, --generate Generate cpio image and write to specified path -H, --hookhelp Display help for given hook and exit -h, --help Display this message and exit -k, --kernel 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 Build specified preset from /etc/mkinitcpio.d -S, --skiphooks Skip specified hooks, comma-separated, during build -s, --save Save build directory. (default: no) -t, --builddir Use DIR as the temporary build directory -v, --verbose Verbose output (default: no) -z, --compress Use an alternate compressor on the image EOF } cleanup() { if [[ $workdir ]]; then # when PRESET is set, we're in the main loop, not a worker process if (( SAVELIST )) && [[ -z $PRESET ]]; then msg "build directory saved in %s" "$workdir" else rm -rf "$workdir" fi fi exit ${1:0} } resolve_kernver() { local kernver= kernel=$1 if [[ -z $kernel ]]; then uname -r return 0 fi if [[ ${kernel:0:1} != / ]]; then echo "$kernel" return 0 fi if [[ ! -e $kernel ]]; then error "specified kernel image does not exist: \`%s'" "$kernel" return 1 fi if file -Lb "$kernel" | grep -oP '(?<=version )[^ ]+'; then return 0 fi error "invalid kernel specified: \`%s'" "$optkver" return 1 } find_moduledir() { local d for d in {/usr,}/lib/modules; do if [[ -d $d/$1/ ]]; then printf '%s' "$d/$1/" return 0 fi done error "unable to locate module directory for kernel \`%s'" "$1" return 1 } compute_hookset() { for h in $HOOKS "${ADDHOOKS[@]}"; do in_array "$h" "${SKIPHOOKS[@]}" && continue hooks+=("$h") done } . "$FUNCTIONS" trap 'cleanup 130' INT trap 'cleanup 143' TERM OPT_SHORT='A:c:g:H:hk:mnLMp:S:st:vz:' OPT_LONG=('add:' 'config:' 'generate:' 'hookhelp:' 'help' 'kernel:' 'listhooks' 'automods' 'nocolor' 'preset:' 'skiphooks:' 'save' 'builddir:' 'verbose' 'compress:') if ! parseopts "$OPT_SHORT" "${OPT_LONG[@]}" -- "$@"; then exit 1 fi set -- "${OPTRET[@]}" unset OPT_SHORT OPT_LONG OPTRET while :; do case $1 in -A|--add) shift IFS=, read -r -a add <<< "$1" ADDHOOKS+=("${add[@]}") unset add ;; -c|--config) shift CONFIG=$1 ;; -k|--kernel) shift optkver=$1 ;; -s|--save) SAVELIST=1 ;; -g|--generate) shift [[ -d $1 ]] && die "Invalid image path -- must not be a directory" if ! GENIMG=$(readlink -f "$1") || [[ ! -e ${GENIMG%/*} ]]; then die "Unable to write to path: \`%s'" "$1" fi ;; -h|--help) usage cleanup 0 ;; -p|--preset) shift PRESET=$1 ;; -n|--nocolor) COLOR=0 ;; -v|--verbose) QUIET=0 ;; -S|--skiphooks) shift IFS=, read -r -a skip <<< "$1" SKIPHOOKS+=("${skip[@]}") unset skip ;; -H|--hookhelp) shift if script=$(find_in_dirs "$1" "${INSTDIR[@]}"); then . "$script" if [[ $(type -t help) != function ]]; then error "No help for hook $1" exit 1 fi else error "No hook '$1'" exit 1 fi msg "Help for hook '$1':" help list_hookpoints "$script" exit 0 ;; -L|--listhooks) msg "Available hooks" for dir in "${INSTDIR[@]}"; do ( cd "$dir" &>/dev/null && printf ' %s\n' * ) done | sort -u | column -c$(tput cols) exit 0 ;; -M|--automods) SHOW_AUTOMODS=1 ;; -t|--builddir) shift TMPDIR=$1 ;; -z|--compress) shift optcompress=$1 ;; --) shift break 2 ;; esac shift done if [[ -t 1 ]] && (( COLOR )); then # prefer terminal safe colored and bold text when tput is supported if tput setaf 0 &>/dev/null; then NC="$(tput sgr0)" BOLD="$(tput bold)" BLUE="$BOLD$(tput setaf 4)" GREEN="$BOLD$(tput setaf 2)" RED="$BOLD$(tput setaf 1)" YELLOW="$BOLD$(tput setaf 3)" else NC="\e[1;0m" BOLD="\e[1;1m" BLUE="$BOLD\e[1;34m" GREEN="$BOLD\e[1;32m" RED="$BOLD\e[1;31m" YELLOW="$BOLD\e[1;33m" fi fi readonly NC BOLD BLUE GREEN RED YELLOW # 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!" KERNELVERSION=$(resolve_kernver "$optkver") || cleanup 1 if [[ $TMPDIR ]]; then if [[ ! -d $TMPDIR ]]; then error "Temporary directory does not exist or is not a directory: \`%s'" "$TMPDIR" cleanup 1 fi if [[ ! -w $TMPDIR ]]; then error "Temporary directory is not writeable: \`%s'" "$TMPDIR" cleanup 1 fi fi workdir=$(TMPDIR=$TMPDIR mktemp -d --tmpdir mkinitcpio.XXXXXX) BUILDROOT=$workdir/root # explicitly create the buildroot mkdir -p "$BUILDROOT/usr/lib/modules/$KERNELVERSION/kernel" # use preset $PRESET if [[ $PRESET ]]; then # allow absolute path to preset file, else resolve it if [[ ${PRESET:0:1} != '/' ]]; then printf -v PRESET '%s/%s.preset' "$PRESETDIR" "$PRESET" fi if [[ -f $PRESET ]]; then # Use -b, -m and -v options specified earlier declare -a preset_mkopts preset_cmd (( QUIET )) || preset_mkopts+=(-v) # Build all images . "$PRESET" for p in "${PRESETS[@]}"; do msg "Building image from 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[*]}" "$0" "${preset_cmd[@]}" done cleanup 0 else die "Preset not found: \`%s'" "$PRESET" fi fi if [[ ! -f $CONFIG ]]; then die "Config file does not exist: \`%s'" "$CONFIG" fi . "$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 MODULEDIR=$(find_moduledir "$KERNELVERSION") || cleanup 1 if [[ ! -d $MODULEDIR ]]; then die "'$MODULEDIR' is not a valid kernel module directory" fi if (( SHOW_AUTOMODS )); then msg "Modules autodetected" autodetect=$(find_in_dirs 'autodetect' "${INSTDIR[@]}") . "$autodetect" build cat "$MODULE_FILE" cleanup 0 fi if [[ -z $GENIMG ]]; then msg "Starting dry run: %s" "$KERNELVERSION" else COMPRESSION=${optcompress:-$COMPRESSION} if ! type -P "$COMPRESSION" >/dev/null; then die "Unable to locate compression method: %s" "$COMPRESSION" fi msg "Starting build: %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 for hook in "${hooks[@]}"; do run_build_hook "$hook" || (( ++builderrors )) done # process config file parse_hook write_image_config # switch out the error handler to catch all errors trap -- RETURN trap '(( ++builderrors ))' ERR if (( ${#ADDED_MODULES[*]} )); then printf '%s\0' "${MODPATHS[@]}" | sort -zu | xargs -0 cp -t "$BUILDROOT/usr/lib/modules/$KERNELVERSION/kernel" # unzip modules prior to recompression gzip -dr "$BUILDROOT/usr/lib/modules/$KERNELVERSION/kernel" msg "Generating module dependencies" install -m644 -t "$BUILDROOT/usr/lib/modules/$KERNELVERSION" \ "$MODULEDIR"/modules.{builtin,order} depmod -b "$BUILDROOT" "$KERNELVERSION" # remove all non-binary module.* files (except devname for on-demand module loading) rm "$BUILDROOT/usr/lib/modules/$KERNELVERSION"/modules.!(*.bin|devname) else warning "No modules were added to the image. This is probably not what you want." fi # unset errtrace and trap set +o functrace trap -- ERR declare -i status=0 if [[ $GENIMG ]]; then msg "Creating $COMPRESSION initcpio image: %s" "$GENIMG" case $COMPRESSION in xz) COMPRESSION_OPTIONS+=' --check=crc32' ;; esac cpio_opts=('-R' '0:0' '-0' '-o' '-H' 'newc') if (( QUIET )); then cpio_opts+=('--quiet') fi # write version stamp printf '%s' "$version" > "$BUILDROOT/VERSION" pushd "$BUILDROOT" >/dev/null find . -print0 | bsdcpio "${cpio_opts[@]}" | $COMPRESSION $COMPRESSION_OPTIONS > "$GENIMG" 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="$COMPRESSION reported an error" fi if (( builderrors )); then warning "errors were encountered during the build. The image may not be complete." status=1 fi if [[ $errmsg ]]; then error "Image generation FAILED: %s" "$errmsg" status=1 elif (( builderrors == 0 )); then msg "Image generation successful" fi else msg "Dry run complete, use -g IMAGE to generate a real image" fi cleanup $status # vim: set ft=sh ts=4 sw=4 et: