This introduces support for the rd.log and rd.debug kernel command line
options, which log early userspace activity to /run/initramfs/init.log.
Code is largely inspired by Dracut's implementation of early userspace
logging, but without needless complexity and redundancies.
Signed-off-by: Dave Reisner <dreisner(a)archlinux.org>
---
init | 5 ++
init_functions | 147 +++++++++++++++++++++++++++++++++++++++++++++------
install/base | 2 +-
man/mkinitcpio.8.txt | 25 +++++++++
4 files changed, 162 insertions(+), 17 deletions(-)
diff --git a/init b/init
index dfebd53..7859aa3 100644
--- a/init
+++ b/init
@@ -19,6 +19,9 @@ mkdir -m755 /run/initramfs
# parse the kernel command line
parse_cmdline </proc/cmdline
+# setup logging as early as possible
+rdlogger_start
+
for d in ${disablehooks//,/ }; do
[ -e "/hooks/$d" ] && chmod 644 "/hooks/$d"
done
@@ -72,6 +75,8 @@ if [ "${break}" = "postmount" ]; then
launch_interactive_shell
fi
+rdlogger_stop
+
exec env -i \
"TERM=$TERM" \
"RD_TIMESTAMP=$RD_TIMESTAMP" \
diff --git a/init_functions b/init_functions
index dc4361a..707f37c 100644
--- a/init_functions
+++ b/init_functions
@@ -1,5 +1,11 @@
# This file contains common functions used in init and in hooks
+# logging targets
+_rdlog_file=$(( 1 << 0 ))
+_rdlog_kmsg=$(( 1 << 1 ))
+_rdlog_cons=$(( 1 << 2 ))
+_rdlog_all=$(( (1 << 3) - 1 ))
+
msg () {
[ "${quiet}" != "y" ] && echo $@
}
@@ -41,6 +47,10 @@ launch_interactive_shell() {
sh -i
}
+bitfield_has_bit() {
+ [ $(( $1 & $2 )) -gt 0 ]
+}
+
major_minor_to_device() {
local dev
@@ -70,6 +80,30 @@ run_hookfunctions() {
done
}
+set_log_option() {
+ local opt words
+
+ for opt in ${1//|/ }; do
+ case $opt in
+ all)
+ rd_log=$_rdlog_all
+ ;;
+ kmsg)
+ rd_log=$(( rd_log | _rdlog_kmsg ))
+ ;;
+ file)
+ rd_log=$(( rd_log | _rdlog_file ))
+ ;;
+ console)
+ rd_log=$(( rd_log | _rdlog_cons ))
+ ;;
+ *)
+ err "unknown rd.log parameter: '$opt'"
+ ;;
+ esac
+ done
+}
+
parse_cmdline() {
local _w _quoted _lhs _rhs _cmdline
read -r _cmdline
@@ -82,8 +116,25 @@ parse_cmdline() {
rw|ro) rwopt=$_w ;;
fsck.mode=*)
case ${_w#*=} in
- force) forcefsck=y ;;
- skip) fastboot=y ;;
+ force)
+ forcefsck=y
+ ;;
+ skip)
+ fastboot=y
+ ;;
+ esac
+ ;;
+ rd.*)
+ case ${_w#rd.} in
+ debug)
+ rd_debug=1
+ ;;
+ log)
+ rd_log=$_rdlog_all
+ ;;
+ log=*)
+ set_log_option "${_w#rd.log=}"
+ ;;
esac
;;
# abide by shell variable naming rules
@@ -140,16 +191,10 @@ fsck_root() {
fsck_device "$root"
fsckret=$?
- fsck_ret() {
- [ -z "$fsckret" ] && return 1
- [ "$fsckret" -eq "$1" ] && return 0
- [ "$(( fsckret & $1 ))" -eq "$1" ]
- }
-
- if [ "$fsckret" -ne 255 ]; then
- if [ "$fsckret" = '0' ] || fsck_ret 1; then
+ if [ -n "$fsckret" ] && [ "$fsckret" -ne 255 ]; then
+ if [ "$fsckret" = '0' ] || bitfield_has_bit $fsckret 1; then
echo "$fsckret" > /run/initramfs/root-fsck
- elif fsck_ret 4; then
+ elif bitfield_has_bit $fsckret 4; then
err "Bailing out. Run 'fsck $root' manually"
printf '%s\n' \
"********** FILESYSTEM CHECK FAILED **********" \
@@ -163,7 +208,7 @@ fsck_root() {
echo ":: Automatic reboot in progress"
sleep 2
reboot -f
- elif fsck_ret 2; then
+ elif bitfield_has_bit $fsckret 2; then
printf '%s\n' \
"************** REBOOT REQUIRED **************" \
"* *" \
@@ -172,13 +217,13 @@ fsck_root() {
"*********************************************"
sleep 10
reboot -f
- elif fsck_ret 8; then
+ elif bitfield_has_bit $fsckret 8; then
err "fsck failed on '$root'"
- elif fsck_ret 16; then
+ elif bitfield_has_bit $fsckret 16; then
err "Failed to invoke fsck: usage or syntax error"
- elif fsck_ret 32; then
+ elif bitfield_has_bit $fsckret 32; then
echo ":: fsck cancelled on user request"
- elif fsck_ret 128; then
+ elif bitfield_has_bit $fsckret 128; then
err "fatal error invoking fsck"
fi
fi
@@ -254,4 +299,74 @@ default_mount_handler() {
fi
}
+rdlogger_start() {
+ [ -n "$rd_log" ] || return
+ mkfifo /run/initramfs/rdlogger.pipe
+ rdlogger </run/initramfs/rdlogger.pipe >/dev/console 2>&1 &
+ exec >/run/initramfs/rdlogger.pipe 2>&1
+ [ -n "$rd_debug" ] && set -x
+}
+
+rdlogger_stop() {
+ local i=0
+
+ [ -e /run/initramfs/rdlogger.pipe ] || return
+
+ [ -n "$rd_debug" ] && { set +x; } 2>/dev/null
+
+ # signal logger to exit by redirecting FDs back to /dev/console
+ exec 0<>/dev/console 1<>/dev/console 2<>/dev/console
+
+ # wait up to 1 second for rdlogger to exit gracefully
+ while [ -e /run/initramfs/rdlogger.pipe ] && [ $i -lt 10 ]; do
+ sleep 0.1
+ i=$(( i + 1 ))
+ done
+
+ [ $i -eq 10 ] && kill %1 2>/dev/null
+}
+
+rdlogger() {
+ local line
+
+ # always cleanup on exit
+ trap 'rm -f /run/initramfs/rdlogger.pipe' EXIT
+
+ # establish log targets. Either redirect to an appropriate file descriptor,
+ # or to /dev/null. This way, logging can Just Happen and the attached FD
+ # will Do The Right Thing™.
+
+ # rd.log=kmsg
+ if [ -c /dev/kmsg ] && bitfield_has_bit "$rd_log" "$_rdlog_kmsg"; then
+ exec 5>/dev/kmsg
+ else
+ exec 5>/dev/null
+ fi
+
+ # rd.log=file
+ if bitfield_has_bit "$rd_log" "$_rdlog_file"; then
+ exec 6>/run/initramfs/init.log
+ else
+ exec 6>/dev/null
+ fi
+
+ # rd.log=console
+ if ! bitfield_has_bit "$rd_log" "$_rdlog_cons" || [ -n "$quiet" ]; then
+ exec >/dev/null
+ fi
+
+ while read -r line; do
+ # rd.log=kmsg
+ printf '<31>initramfs: %s\n' "$line" >&5
+
+ # rd.log=file
+ printf '%s\n' "$line" >&6
+
+ # rd.log=console
+ printf '%s\n' "$line"
+ done
+
+ # EOF, shutting down...
+}
+
# vim: set ft=sh ts=4 sw=4 et:
diff --git a/install/base b/install/base
index ad0e5f2..397168f 100644
--- a/install/base
+++ b/install/base
@@ -3,7 +3,7 @@
build() {
local applet
- add_binary /usr/lib/initcpio/busybox /bin/busybox
+ add_binary /usr/lib/initcpio/busybox /usr/bin/busybox
for applet in $(/usr/lib/initcpio/busybox --list); do
add_symlink "/usr/bin/$applet" busybox
diff --git a/man/mkinitcpio.8.txt b/man/mkinitcpio.8.txt
index 56ac571..4f2db59 100644
--- a/man/mkinitcpio.8.txt
+++ b/man/mkinitcpio.8.txt
@@ -248,6 +248,31 @@ the kernel command line:
device to show up, if it is not available immediately. This defaults to 5
seconds. If an invalid integer is passed, this variable will have no effect.
+*rd.debug*::
+ Enables shell debug (xtrace). This option is only useful in combination with
+ the 'rd.log' option.
+
+*rd.log*['=<console|file|kmsg|all>']::
+ Enables logging of early userspace messages. If specified, the optional
+ parameter describes where this information is logged. Multiple options can be
+ OR'd together using the pipe (|) character. Messages are always logged
+ to the console unless the 'quiet' parameter is passed.
+
+ *console*;;
+ Writes log output to '/dev/console'.
+
+ *file*;;
+ Writes log output to '/run/initramfs/init.log'
+
+ *kmsg*;;
+ Writes output to the kernel ring buffer using the '/dev/kmsg' device
+ (introduced in Linux 3.5). This option is a no-op if your kernel does
+ not support this device.
+
+ *all*;;
+ Writes output to all known log targets. This is the default if no option
+ is specified.
+
These are only the variables that the core of mkinitcpio honor. Additional
hooks may look for other environment variables and should be documented by the
help output for the hook.
--
1.8.3.1