Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(assure_command_shell): assure proper installation of a shell #2368

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/HACKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,18 @@ dracut_install_dir/modules.d/
which rely on the network module to detect and configure network
interfaces.

`postprocess()`:
This function of module-setup.sh is invoked by dracut in a
postprocessing loop (after all module files have been installed and all
other module scripts have run) if the variable _$mods_to_postprocess_
is enrolled with module information structured in a space-separated list
like `" module:moddir[@action@...] ... "`. For example, the squash
module employs this feature with action 'installpost' like this,
`mods_to_postprocess+=" squash:$moddir@installpost@ "`, in order to move
files into a `../squash/root/` directory in preparation for squashing
the image. A second call to `postprocess()` is made after striping
object files when the image is ready for squashing.

Any other files in the module will not be touched by dracut directly.

You are encouraged to provide a README that describes what the module is for.
Expand Down
33 changes: 27 additions & 6 deletions dracut-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,22 @@ module_installkernel() {
fi
}

# [action=<action>] module_postprocess <dracut module> <module path>
# Execute the postprocess() function of module-setup.sh of <dracut module> with
# optional action directive.
module_postprocess() {
local _moddir=$2
local _ret
unset -f 'postprocess'
postprocess() { true; }
# shellcheck disable=SC1090
. "$_moddir"/module-setup.sh
moddir=$_moddir postprocess
_ret=$?
unset -f 'postprocess'
return $_ret
}

# check_mount <dracut module> [<use_as_dep>] [<module path>]
# check_mount checks, if a dracut module is needed for the given
# device and filesystem types in "${host_fs_types[@]}"
Expand All @@ -933,11 +949,13 @@ check_mount() {
[[ " $mods_to_load " == *\ $_mod\ * ]] && return 0
[[ " $mods_checked_as_dep " == *\ $_mod\ * ]] && return 1

# This should never happen, but...
[[ -d $_moddir ]] || return 1

[[ $2 ]] || mods_checked_as_dep+=" $_mod "

[[ -d $_moddir ]] || {
dwarn "Module '$_mod' cannot be installed without a source directory, '$_moddir'."
return 1
}

# shellcheck disable=SC2154
if [[ " $omit_dracutmodules " == *\ $_mod\ * ]]; then
return 1
Expand Down Expand Up @@ -999,11 +1017,13 @@ check_module() {
[[ " $mods_to_load " == *\ $_mod\ * ]] && return 0
[[ " $mods_checked_as_dep " == *\ $_mod\ * ]] && return 1

# This should never happen, but...
[[ -d $_moddir ]] || return 1

[[ $2 ]] || mods_checked_as_dep+=" $_mod "

[[ -d $_moddir ]] || {
dwarn "Module '$_mod' cannot be installed without a source directory, '$_moddir'."
return 1
}

if [[ " $omit_dracutmodules " == *\ $_mod\ * ]]; then
ddebug "Module '$_mod' will not be installed, because it's in the list to be omitted!"
return 1
Expand Down Expand Up @@ -1067,6 +1087,7 @@ for_each_module_dir() {
local _moddir
local _func
local _reason
LC_COLLATE=C
_func=$1
for _moddir in "$dracutbasedir/modules.d"/[0-9][0-9]*; do
[[ -d $_moddir ]] || continue
Expand Down
51 changes: 17 additions & 34 deletions dracut.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1783,7 +1783,7 @@ done

export initdir dracutbasedir \
dracutmodules force_add_dracutmodules add_dracutmodules omit_dracutmodules \
mods_to_load \
mods_to_load mods_to_postprocess \
fw_dir drivers_dir debug no_kernel kernel_only \
omit_drivers mdadmconf lvmconf root_devs \
use_fstab fstab_lines libdirs fscks nofscks ro_mnt \
Expand All @@ -1794,10 +1794,13 @@ export initdir dracutbasedir \
hostonly_cmdline loginstall

mods_to_load=""
mods_to_postprocess=""
# check all our modules to see if they should be sourced.
# This builds a list of modules that we will install next.
for_each_module_dir check_module
for_each_module_dir check_mount
# Assure an explicit command shell or ~no~sh.
[[ $dracutmodules == all ]] || module_depends ~sh "$(dracut_module_path ~sh)"

dracut_module_included "fips" && export DRACUT_FIPS_MODE=1

Expand Down Expand Up @@ -1896,6 +1899,7 @@ fi
_isize=0 #initramfs size
modules_loaded=" "
# source our modules.
dinfo "*** Including modules ***"
for moddir in "$dracutbasedir/modules.d"/[0-9][0-9]*; do
_d_mod=${moddir##*/}
_d_mod=${_d_mod#[0-9][0-9]}
Expand Down Expand Up @@ -2230,13 +2234,14 @@ if [[ $kernel_only != yes ]]; then
build_ld_cache
fi

if dracut_module_included "squash"; then
readonly squash_dir="$initdir/squash/root"
readonly squash_img="$initdir/squash-root.img"
mkdir -p "$squash_dir"
dinfo "*** Install squash loader ***"
DRACUT_SQUASH_POST_INST=1 module_install "squash"
fi
for _d_mod in $mods_to_postprocess; do
# $_d_mod here includes structured info as module:moddir[@action@...]
strstr "$_d_mod" "@installpost@" && {
_d_mod=${_d_mod/@installpost/}
_moddir=${_d_mod%@*}
action=installpost module_postprocess "${_d_mod%:*}" "${_moddir#*:}"
}
done

if [[ $do_strip == yes ]] && ! [[ $DRACUT_FIPS_MODE ]]; then
# stripping files negates (dedup) benefits of using reflink
Expand All @@ -2255,32 +2260,10 @@ if [[ $do_strip == yes ]] && ! [[ $DRACUT_FIPS_MODE ]]; then
dinfo "*** Stripping files done ***"
fi

if dracut_module_included "squash"; then
dinfo "*** Squashing the files inside the initramfs ***"
declare squash_compress_arg
# shellcheck disable=SC2086
if [[ $squash_compress ]]; then
if ! mksquashfs /dev/null "$DRACUT_TMPDIR"/.squash-test.img -no-progress -comp $squash_compress &> /dev/null; then
dwarn "mksquashfs doesn't support compressor '$squash_compress', failing back to default compressor."
else
squash_compress_arg="$squash_compress"
fi
fi

# shellcheck disable=SC2086
if ! mksquashfs "$squash_dir" "$squash_img" \
-no-xattrs -no-exports -noappend -no-recovery -always-use-fragments \
-no-progress ${squash_compress_arg:+-comp $squash_compress_arg} 1> /dev/null; then
dfatal "Failed making squash image"
exit 1
fi

rm -rf "$squash_dir"
dinfo "*** Squashing the files inside the initramfs done ***"

# Skip initramfs compress
compress="cat"
fi
for _d_mod in $mods_to_postprocess; do
_d_mod=${_d_mod%%@*}
squash_compress=$squash_compress module_postprocess "${_d_mod%:*}" "${_d_mod#*:}"
done

dinfo "*** Creating image file '$outfile' ***"

Expand Down
36 changes: 28 additions & 8 deletions man/dracut.modules.7.asc
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,41 @@ point of time in which they are executed, are described in <<stages>>.

The main script, which creates the initramfs is dracut itself. It parses all
arguments and sets up the directory, in which everything is installed. It then
executes all check, install, installkernel scripts found in the modules, which
are to be processed. After everything is installed, the install directory is
archived and compressed to the final initramfs image. All helper functions used
by check, install and installkernel are found in in the file _dracut-functions_.
These shell functions are available to all module installer (install,
installkernel) scripts, without the need to source _dracut-functions_.
executes all check, install, installkernel, or module_setup scripts found in
the modules, which are to be processed. After everything is installed and
before the install directory is archived and compressed to the final initramfs
image, a postprocessing loop will run any postprocess() functions in a module's
module_setup.sh that has been enrolled in the variable $mods_to_postprocess.
This enables adjustments to be made to the installed image once all other
module scripts have completed. All helper functions used by check, install,
installkernel, and module_setup are found in in the file _dracut-functions_.
These shell functions are available to all module installer (check, install,
installkernel, module_setup) scripts, without the need to source
_dracut-functions_.

A module can check the preconditions for install and installkernel with the
check script. Also dependencies can be expressed with check. If a module passed
check, install and installkernel will be called to install all of the necessary
check or module_setup script. Also dependencies can be expressed with check or
within module_setup with the depends() function. If a module passed check or
module_setup:check(), then install and installkernel or module_setup:install()
and module_setup:installkernel() will be called to install all of the necessary
files for the module. To split between kernel and non-kernel parts of the
installation, all kernel module related parts have to be in installkernel. All
other files found in a module directory are module specific and mostly are hook
scripts and udev rules.

=== Command Shells ===
A dracut build runs in Bash. Command scripts installed in the initramfs should
be POSIX compliant. DASH, Bash, mksh, and BusyBox are supported as command
shells and have dracut modules, _00dash_, _00bash_, _00mksh_, and _05busybox_.
The meta module _99&#126;sh_ is provided to assure that a command shell is properly
installed during a build. An alternative command shell may be specified by
adding a module for it into the `../dracut/modules.d/` directory and linking
its executable to `/bin/sh` of the host. After dracut installs all specified
and dependent modules, it will loop through all installed executables and
install any dynamic dependencies found by _ldd_ and any command interpreters
found in shebang directives. The meta module _99&#126;no&#126;sh_ may be used to
opt out of automatic shell installation should that be desired during an
incremental or `-m`, `--modules` exclusive build.

[[stages]]
== Boot Process Stages
Expand Down
27 changes: 27 additions & 0 deletions modules.d/00bash/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
# Prerequisite check(s) for module.
check() {

# Enroll module for postprocessing.
strstr " $mods_to_postprocess " " bash:$moddir@installpost@ " || {
mods_to_postprocess+=" bash:$moddir@installpost@ "
}

# If the binary(s) requirements are not fulfilled the module can't be installed.
require_binaries bash || return 1

Expand All @@ -30,3 +35,25 @@ install() {
[[ -L $initdir/bin/sh ]] || ln -sf bash "${initdir}/bin/sh"

}

# Execute any postprocessing requirements.
postprocess() {

[[ $action == installpost ]] && {
[[ $(readlink "${initdir}"/bin/sh) == bash ]] && {
local version ver0 ver1

# local - (available since bash-4.4 2016-09-15) automates the
# restoration of local xtrace & other set options.
IFS=' ' read -r -a version <<< "$(command /bin/bash --version)"
IFS=. read -r ver0 ver1 _ <<< "${version[3]}"
if ((${ver0}${ver1} < 44)); then
dfatal "Installed Bash ${version[3]} ${version[4]}.
At least Bash 4.4 is required for proper xtrace logging
when Bash is the initramfs command shell."
exit 1
fi
}
}
return 0
}
5 changes: 0 additions & 5 deletions modules.d/99base/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@ install() {
ln -s dracut-util "${initdir}/usr/bin/dracut-getarg"
ln -s dracut-util "${initdir}/usr/bin/dracut-getargs"

if [ ! -e "${initdir}/bin/sh" ]; then
inst_multiple bash
(ln -s bash "${initdir}/bin/sh" || :)
fi

# add common users in /etc/passwd, it will be used by nfs/ssh currently
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad we can remove this now, but you should also make the base module dependent on the new "sh" module, like the following

depends() {
    echo "sh udev-rules"
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not needed with the refactored approach.

# use password for hostonly images to facilitate secure sulogin in emergency console
[[ $hostonly ]] && pwshadow='x'
Expand Down
61 changes: 53 additions & 8 deletions modules.d/99squash/module-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,29 @@ check() {
}

depends() {

if find_binary busybox &> /dev/null \
&& ! strstr " $omit_dracutmodules " " busybox "; then
echo "busybox"
fi
echo "systemd-initrd"
return 0
}

install() {

# Enroll module for postprocessing.
# shellcheck disable=SC2154
mods_to_postprocess+=" squash:$moddir@installpost@ "

}

installpost() {
local _busybox
_busybox=$(find_binary busybox)
# shellcheck disable=SC2154
readonly squash_dir="$initdir/squash/root"
readonly squash_img="$initdir/squash-root.img"
mkdir -p "$squash_dir"
dinfo "*** Install squash loader ***"

# Move everything under $initdir except $squash_dir
# itself into squash image
Expand All @@ -26,16 +42,16 @@ installpost() {
mkdir -p "$initdir"/squash/
mkdir -p "$squash_dir"/squash/

# Copy dracut spec files out side of the squash image
# so dracut rebuild and lsinitrd can work
# Copy /dracut/ directory files out of the squash image directory
# so dracut rebuild and lsinitrd can work.
for file in "$squash_dir"/usr/lib/dracut/*; do
[[ -f $file ]] || continue
DRACUT_RESOLVE_DEPS=1 dracutsysrootdir="$squash_dir" inst "${file#"$squash_dir"}"
done

# Install required modules and binaries for the squash image init script.
if [[ $_busybox ]]; then
inst "$_busybox" /usr/bin/busybox
if find_binary busybox; then
inst busybox /usr/bin/busybox
for _i in sh echo mount modprobe mkdir switch_root grep umount; do
ln_r /usr/bin/busybox /usr/bin/$_i
done
Expand All @@ -61,8 +77,37 @@ installpost() {
build_ld_cache
}

install() {
if [[ $DRACUT_SQUASH_POST_INST ]]; then
postprocess() {

# shellcheck disable=SC2154
[[ $action == installpost ]] && {
installpost
return 0
}

dinfo "*** Squashing the files inside the initramfs ***"
declare squash_compress_arg
if [[ $squash_compress ]]; then
# shellcheck disable=SC2086
if ! mksquashfs /dev/null "$DRACUT_TMPDIR"/.squash-test.img -no-progress -comp $squash_compress &> /dev/null; then
dwarn "mksquashfs doesn't support compressor '$squash_compress', falling back to default compressor."
else
squash_compress_arg="$squash_compress"
fi
fi

# shellcheck disable=SC2086
if ! mksquashfs "$squash_dir" "$squash_img" \
-no-xattrs -no-exports -noappend -no-recovery -always-use-fragments \
-no-progress ${squash_compress_arg:+-comp $squash_compress_arg} 1> /dev/null; then
dfatal "Failed making squash image"
exit 1
fi

rm -rf "$squash_dir"
dinfo "*** Squashing the files inside the initramfs done ***"

# Skip initramfs compress
export compress="cat"

}
32 changes: 32 additions & 0 deletions modules.d/99~no~sh/module-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/bin/bash
# This file is part of dracut.
# SPDX-License-Identifier: GPL-2.0-or-later

# This meta module is included when no command shell is desired.

check() {

# We only want to return 255 since this is a meta module.
return 255

}

install() {

# Enroll module for postprocessing.
mods_to_postprocess+=" ~no~sh:$moddir@installpost@ "

# Installing a null link satisfies the executable dependency check.
ln -sf ../../dev/null "${initdir}"/bin/sh

}

postprocess() {

[[ $action == installpost ]] && {
# Remove the faked installation link.
rm "${initdir}"/usr/bin/sh
return $?
}
return 0
}
Loading
Loading