Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tag: 0.2
Fetching contributors…

Cannot retrieve contributors at this time

executable file 547 lines (445 sloc) 15.066 kb
#!/bin/bash
#
# geninit - modular initramfs creation tool
#
# Copyright (C) 2011 by Dave Reisner <d@falconindy.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
shopt -s extglob
shopt -s nullglob
# error codes
declare -ri ENOENT=2
declare -ri EACCES=13
declare -ri EEXIST=17
declare -ri EINVAL=22
# tender vittles
declare -r _sysconfdir=/etc
declare -r _sharedir=.
declare -r builderdir=${_sharedir}/builders
declare -r baseconfig=${_sysconfdir}/geninit.conf
declare -r carch=$(uname -m)
# options
declare -i automods=0
declare -i color=1
declare -i dryrun=0
declare -i savebuildroot=0
declare -i verbose=0
declare kernver=$(uname -r)
declare basedir=
declare config=
declare imagename=
declare ocompress=
declare preset=
declare skipbuilders=
# -------------------------- #
## general util functions ##
# -------------------------- #
plain() { # {{{
local mesg=$1; shift
printf "$BOLD $mesg$NC\n" "$@" >&2
} # }}}
msg() { # {{{
local mesg=$1; shift
printf "$GREEN==>$NC$BOLD $mesg$NC\n" "$@" >&2
} # }}}
msg2() { # {{{
local mesg=$1; shift
printf "$BLUE ->$NC$BOLD $mesg$NC\n" "$@" >&2
} # }}}
warning() { # {{{
local mesg=$1; shift
printf "$YELLOW==> WARNING:$NC$BOLD $mesg$NC\n" "$@" >&2
} # }}}
error() { # {{{
local mesg=$1; shift
printf "$RED==> ERROR:$NC$BOLD $mesg$NC\n" "$@" >&2
} # }}}
die() { # {{{
error "$*"
cleanup 1
} # }}}
in_array() { # {{{
local item needle=$1; shift
for item; do
[[ "$item" == $needle ]] && return 0 # Found
done
return 1 # Not Found
} # }}}
cleanup() { # {{{
(( SHLVL == 2 )) && [[ "$2" ]] && error "$2"
if (( savebuildroot )) && [[ -d "$buildroot" ]]; then
msg "buildroot preserved at: $buildroot"
else
rm -rf "$tmpdir"
fi
exit $1
} # }}}
usage() { # {{{
cat<<USAGE
geninit %VERSION%
usage: geninit [options]
Options:
-b <path> Use base directory (default: /)
-c <file> Specify a different config file (default: $_sysconfdir/geninit.conf)
-g <path> Path and name of generated image (dry-run if unspecified)
-H <builder> Display help for a specified builder
-h Display this help message
-k <kver> Specify a kernel version (default: $kernver)
-L List available builders
-p <preset> Build using a preset file (any of $_sysconfdir/geninit.d/*.preset)
-S <names> Skip the specified builders during the build process
-s Preserve buildroot after image creation
-t <path> Specify an alternate location for temporary workspace
-z <method> Override compression method
USAGE
exit
} # }}}
builderlist() { # {{{
local -a builders=("$_sharedir"/builders/*)
(( ${#builders[*]} )) || die "no builders found"
msg "Available builders -- use -H <builder> for help with a specific builder"
printf '%s\n' "${builders[@]##*/}" | column -c$(tput cols)
cleanup 0
} # }}}
builderhelp() { # {{{
local builder=$1
[[ -f "$_sharedir/builders/$builder" ]] || die "no such builder: $builder"
. "$_sharedir/builders/$builder"
type -t helpmsg >/dev/null || die "no help for builder: $builder"
msg "$builder"
helpmsg
cleanup 0
} # }}}
# --------------- #
## private API ##
# --------------- #
__get_kernver() { # {{{
local kernel=$1
if [[ "${kernel:0:1}" != / ]]; then
echo "$kernel"
return 0
fi
[[ -r "$kernel" ]] || return $ENOENT
read _ kernver < <(file -b "$basedir$kernel" | grep -o 'version [^ ]\+')
if [[ "$kernver" ]]; then
echo "$kernver"
return 0
fi
return $EINVAL
} # }}}
__add_file() { # {{{
# add a file to $buildroot
# $1: pathname on initcpio
# $2: source on disk
# $3: mode
(( $# == 3 )) || return $EINVAL
[[ -e "$buildroot$1" ]] && return $EEXIST
(( verbose )) && plain "adding file: %s" "$1"
install -Dm$3 "$2" "$buildroot$1"
} # }}}
__add_dir() { # {{{
# add a directory (with parents) to $buildroot
# $1: pathname on initcpio
# $2: mode
(( $# == 2 )) || [[ "$1" == /?* ]] || return $EINVAL
[[ -e "$buildroot$1" ]] && return $EEXIST
(( verbose )) && plain "adding dir: %s" "$1"
install -dm$2 "$buildroot$1"
} # }}}
__add_pipe() { # {{{
# add a pipe to $buildroot
# $1: pathname on initcpio
# $2: mode
(( $# == 2 )) || return $EINVAL
[[ -e "$buildroot$1" ]] && return $EEXIST
(( verbose )) && plain "adding pipe: %s" "$1"
mkfifo -m$2 "$buildroot$1"
} # }}}
__add_slink() { # {{{
# add a symlink to $buildroot
# $1: name on initcpio
# $2: target of $1
(( $# == 2 )) || return $EINVAL
[[ -L "$buildroot$1" ]] && return $EEXIST
(( verbose )) && plain "adding symlink: %s -> %s" "$2" "$1"
ln -s "$2" "$buildroot$1"
} # }}}
__build_image() { # {{{
local -a zopts pipesave
local -i errors=0
local builder= module= line= mod= file= errmsg=
# check our kernel version
[[ -d "$basedir/lib/modules/$kernver" ]] || die "kernel \`$kernver' not found"
# this must be resolved to an absolute path
if [[ "$imagename" && "${imagename:0:1}" != / ]]; then
imagename=$(readlink -f "$imagename")
[[ "$imagename" ]] || die "Failed to resolve path to imagename"
fi
(( dryrun )) && msg "Starting dry run: %s" "$kernver" || msg "Starting build: %s" "$kernver"
# we always need the linker
file=$(readlink -e "$ld_so")
add_path_to_file "${ld_so#$basedir}" || (( ++errors ))
__add_slink "${ld_so#$basedir}" "${file#$basedir}" || (( ++errors ))
__add_file "${file#$basedir}" "${file#$basedir}" 755 || (( ++errors ))
# add extra modules from config
for mod in ${modules[@]}; do
add_module "$mod" || { (( ++errors)); error "Module not found: $mod"; }
done
# add extra files from config
for file in "${files[@]}"; do
local src=${file%%::*}
local dest=${file##*::}
[[ "$dest" ]] || dest=$src
add_binary "$src" "$dest" || { (( ++errors)); error "File not found: $file"; }
done
# parse builder array
for builder in "${builders[@]}"; do
in_array "$builder" "${skipbuilders[@]}" && continue
if [[ ! -f "$builderdir/$builder" ]]; then
error "cannot find builder '$builder': No such file"
(( ++errors ))
continue
fi
( # subshell to prevent namespace pollution
. "$builderdir/$builder"
if ! type -t build >/dev/null; then
error "no build function found in builder '$builder'"
exit 1
fi
msg2 "Building: [%s]" "$builder"
build
)
done
# only create depmod files if there's modules added
if [[ -d "$buildroot$moduledir" ]]; then
msg "Generating module dependencies"
depmod -b "$buildroot" "$kernver" || (( ++errors ))
# trim excess depmod files
rm "$buildroot$moduledir"/modules.!(dep|alias|symbols)
fi
{ # create delicious config
if (( ${#modules[*]} )); then
printf '%s %d' '%MODULES%' "${#modules[*]}"
printf ' %s' "${modules[@]}"
printf '\n'
fi
printf '%s' '%HOOKS%'
printf ' %s' "${builders[@]}"
printf '\n'
} > "$buildroot/config"
if (( dryrun )); then
msg "Dry run complete. Use -g <path> to create an initramfs."
return
fi
[[ "$compress" != cat ]] && zopts=(${compressquirks[$compress]} '-9')
msg "Creating$([[ "$compress" == cat ]] || printf " $compress") initramfs: $imagename"
pushd "$buildroot" &>/dev/null
(( verbose )) && plain "compress cmd: $compress ${zopts[@]}"
find . -print0 | bsdcpio -0oH newc | "$compress" "${zopts[@]}" > "$imagename.tmp"
pipesave=("${PIPESTATUS[@]}") # save immediately
popd &>/dev/null
(( pipesave[0] )) && errmsg="failed to create filelist (find reported error)"
(( pipesave[1] )) && errmsg="failed to create archive (bsdcpio reported error)"
(( pipesave[2] )) && errmsg="$compress reported error compressing image"
# check for fatal errors
[[ "$errmsg" ]] && { rm "$imagename.tmp"; die "$errmsg"; }
# less than fatal errors
if (( !errors )); then
msg "Image creation completed successfully"
else
warning "Errors were encountered during creation. The image may not be complete."
fi
mv "$imagename"{.tmp,}
return 0 # ignore error when (( errors )) evalutes to false
} # }}}
__build_preset() { # {{{
local preset= presetfile=$_sysconfdir/geninit.d/$1.preset
local var= cfg= opts= imagename=
local -a presets
if [[ ! -f "$basedir$presetfile" ]]; then
error "preset not found: $1"
return $ENOENT
fi
if ! . "$basedir$presetfile"; then
error "failed to read preset: $1"
return $EACCES
fi
# preset pulls in a known array 'presets'
for preset in "${presets[@]}"; do
if [[ ${preset:0:1} == ! ]]; then # marked disabled
continue;
fi
# resolve kernel version
if [[ -z "$ALL_kver" ]]; then
error "No kernel version defined for preset \`$1'"
return $EINVAL
fi
# resolve image name
var=${preset}_image
imagename=${!var}
if [[ -z "$imagename" ]]; then
error "No imagename defined for preset \`$1'"
return $EINVAL
fi
# use extra config, falling back on ALL_config, falling back on the default
var=${preset}_config
cfg=${!var:-${ALL_config:-$_sysconfigdir/geninit.conf}}
# extra options are optional
var=${preset}_options
opts=${!var}
# this should always resolve to something (i hope)
if [[ ! -f "$basedir$cfg" ]]; then
error "preset config file not found: \`$basedir$cfg'"
return $ENOENT
fi
# explicitly retain some options
(( verbose )) && opts+=' -v'
(( !color )) && opts+=' -C'
# we've got enough to relaunch geninit now
msg "Building image from preset: $1-$preset"
msg2 "-k $ALL_kver -c $cfg -g $basedir$imagename $opts"
"$0" -b "$basedir" -k "$ALL_kver" -c "$cfg" -g "$basedir$imagename" $opts || return 1
echo
done
} # }}}
__kmodinfo() { # {{{
modinfo -0k $kernver "$@"
} # }}}
__ldd() { # {{{
LD_TRACE_LOADED_OBJECTS=1 "$ld_so" "$@"
} # }}}
# its a trap!
trap 'cleanup 130 "Aborted by user! Exiting..."' SIGINT
trap 'cleanup 143 "TERM signal caught. Exiting..."' SIGTERM
# source public API
. "$_sharedir/geninit.api"
# source module quirks file
. "$_sharedir/geninit.quirks"
# ------------------ #
## option parsing ##
# ------------------ #
while getopts ':b:Cc:g:H:hk:LMp:S:st:vz:' flag; do
case $flag in
b) basedir=$OPTARG ;;
c) config=$OPTARG ;;
C) color=0 ;;
g) imagename=$OPTARG ;;
H) builderhelp $OPTARG ;;
h) usage ;;
k) kern=$OPTARG ;;
L) builderlist ;;
M) automods=1 ;;
p) preset=$OPTARG ;;
S) skipbuilders=(${OPTARG//,/ }) ;;
s) savebuildroot=1 ;;
t) otmpdir=$OPTARG ;;
v) verbose=1 ;;
z) ocompress=$OPTARG ;; # named differently to allow overriding
:) die "option requires an argument -- '$OPTARG'" ;;
\?) die "invalid option -- '$OPTARG'" ;;
esac
done
# Alter PATH. We want to make sure that /bin and /sbin are favored, since
# we specifically rely on GNU coreutils
PATH=/sbin:/bin:$PATH
if [[ -t 2 ]] && (( 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
if [[ "$preset" ]]; then
__build_preset "$preset"
exit $?
fi
# ----------------- #
## sanity checks ##
# ----------------- #
# if specified, does the basedir exist? trim any trailing slash
if [[ "$basedir" ]]; then
basedir=${basedir%/}
# if $basedir still exists (some noodle noggin might use -b /), make sure the
# dir can be resolved
[[ "$basedir" && ! -d $basedir ]] && die "basedir \`$basedir' not found"
fi
# make sure our config exists, and source it
config=${config:-$baseconfig}
[[ ! -f "$basedir$config" ]] && die "failed to find config file: \`$basedir$config'" || . "$basedir$config"
# declared as an array to force expansion. we have to be careful that the wrong
# linker isn't picked for mulitlib systems, so we branch on $carch.
case $carch in
i686) ld_so=("$basedir"/lib/ld-linux.so.?*) ;;
x86_64) ld_so=("$basedir"/lib/ld-linux-${carch//_/-}.so.?*) ;;
*) die "unknown architecture: $carch" ;;
esac
if (( ${#ld_so[*]} != 1 )); then # uh oh...
die "failed to resolve the location of /lib/ld.so. Please report this bug."
fi
# mktemp takes care of our error handling here
tmpdir=$(readlink -e "$(mktemp -d "${otmpdir:-/tmp}"/${0##*/}.XXXXXX)") || cleanup 1
# does the kernel exist inside the basedir? we may need to resolve it...
[[ "$kern" ]] && kernver=$(__get_kernver "$kern")
[[ "$kernver" ]] || die "Invalid kernel specifier: $kern"
# is our supplied compression method (if supplied) valid?
compress=${ocompress:-$compress}
if [[ -z $compress || $compress == none ]]; then
compress=cat # NOOP compressor
else
[[ "$compress" == @(gzip|bzip2|lzop|lzma|xz) ]] || die "unknown compression method: $compress"
type -P "$compress" >/dev/null || die "failed to find \`$compress' binary in PATH"
fi
# if $imagename is provided, its path needs to be valid
if [[ "$imagename" ]]; then
imagepath=$(readlink -f "$imagename")
[[ "$imagepath" ]] || die "invalid path to imagename: $imagename"
[[ -w ${imagepath%/*} ]] || die "no permission to write to specified path: \`${imagepath%/*}'"
else # no $imagename, so we're doing a dry run
dryrun=1
fi
# ------------ #
## int main ##
# ------------ #
# define a few more paths for convenience
declare -r buildroot=$tmpdir/root
declare -r autodetect_cache=$tmpdir/autodetect.cache
declare -r moduledir=/lib/modules/$kernver
if (( automods )); then
. "$_sharedir/builders/autodetect" || die "unable to source autodetect builder"
build
msg "Autodetected modules"
[[ -s "$autodetect_cache" ]] && cat "$autodetect_cache"
cleanup 0
fi
__build_image
cleanup $?
# vim: set et sw=2 ft=sh:
Jump to Line
Something went wrong with that request. Please try again.