Early Userspace Mounting
This article will detail how to build a custom minimal initramfs that checks the /usr filesystem and pre-mounts /usr. This has become necessary for affected configurations because of various changes in udev (see bug #364235).
In this article we'll be working with the following:
- Busybox
- An initramfs content list
- The gen_init_cpio and gen_initramfs.sh utilities, provided by the kernel itself.
The initramfs also contains the required libraries and binaries to run an ext4 fsck. Most of the code to run the fsck is coming from the /etc/init.d/fsck script.
When using any other filesystem than ext4, add the required binaries / libraries into the initramfs list.
Basically, the init script is doing following actions:
- Mounts the root partition on /mnt/root as read-only.
- Symlinks the /etc/fstab from the root partition to the initramfs environment.
- Checks the filesystem of our /usr device using the embedded /sbin/fsck binary.
- Mounts /usr, then moves it to /mnt/root/usr using the
--move
mount parameter. - Switches to real root and executes init.
The article also assumes we are working in /usr/src/initramfs, so for the sake of ease, begin with creating this directory.
Requirements
The most important package here is sys-apps/busybox as it provides utilities suitable for an initramfs. It is also critical that to emerge it with static
USE flag enabled:
root #
USE="static" emerge --ask sys-apps/busybox
Enabling the
static
USE flag will disable PAM support, regardless if its enabled or not.Make sure that the running kernel is built with the devtmpfs option enabled. It is required by the init script below and udev:
Device Drivers --->
Generic Driver Options --->
[*] Maintain a devtmpfs filesystem to mount at /dev
Next up is the initramfs_list file which will tell gen_initramfs.sh how to construct the initramfs:
# directory structure
dir /proc 755 0 0
dir /usr 755 0 0
dir /bin 755 0 0
dir /sys 755 0 0
dir /var 755 0 0
dir /lib 755 0 0
dir /sbin 755 0 0
#dir /lib64 755 0 0
#dir /lib32 755 0 0
dir /mnt 755 0 0
dir /mnt/root 755 0 0
dir /etc 755 0 0
dir /root 700 0 0
dir /dev 755 0 0
# busybox
file /bin/busybox /bin/busybox 755 0 0
# libraries required by /sbin/fsck.ext4 and /sbin/fsck
file /lib/ld-linux.so.2 /lib/ld-linux.so.2 755 0 0
file /lib/libext2fs.so.2 /lib/libext2fs.so.2 755 0 0
file /lib/libcom_err.so.2 /lib/libcom_err.so.2 755 0 0
file /lib/libpthread.so.0 /lib/libpthread.so.0 755 0 0
file /lib/libblkid.so.1 /lib/libblkid.so.1 755 0 0
file /lib/libmount.so.1 /lib/libmount.so.1 755 0 0
file /lib/libuuid.so.1 /lib/libuuid.so.1 755 0 0
file /lib/libe2p.so.2 /lib/libe2p.so.2 755 0 0
file /lib/libc.so.6 /lib/libc.so.6 755 0 0
file /lib/librt.so.1 /lib/librt.so.1 755 0 0
file /lib/libdl.so.2 /lib/libdl.so.2 755 0 0
file /sbin/fsck /sbin/fsck 755 0 0
file /sbin/fsck.ext4 /sbin/fsck.ext4 755 0 0
# our init script
file /init /usr/src/initramfs/init 755 0 0
Please note that if the computer under maintenance is running the amd64 version, it is necessary to do a bit of editing. Reference lib64 rather than lib for the libraries required by /sbin/fsck.ext4 and /sbin/fsck. Additionally, the ld-linux library needs to explicitly point to the x86-64 version. For example, the first three lines of that section would now be:
# libraries required by /sbin/fsck.ext4 and /sbin/fsck
file /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 755 0 0
file /lib64/libext2fs.so.2 /lib64/libext2fs.so.2 755 0 0
Copy and save the contents of the above to /usr/src/initramfs/initramfs_list after adjusting for the current architecutre.
Last up is the actual init file which will execute the initramfs:
#!/bin/busybox sh
rescue_shell() {
echo "$@"
echo "Something went wrong. Dropping you to a shell."
busybox --install -s
exec /bin/sh
}
uuidlabel_root() {
for cmd in $(cat /proc/cmdline) ; do
case $cmd in
root=*)
type=$(echo $cmd | cut -d= -f2)
echo "Mounting rootfs"
if [ $type == "LABEL" ] || [ $type == "UUID" ] ; then
uuid=$(echo $cmd | cut -d= -f3)
mount -o ro $(findfs "$type"="$uuid") /mnt/root
else
mount -o ro $(echo $cmd | cut -d= -f2) /mnt/root
fi
;;
esac
done
}
check_filesystem() {
# most of code coming from /etc/init.d/fsck
local fsck_opts= check_extra= RC_UNAME=$(uname -s)
# FIXME : get_bootparam forcefsck
if [ -e /forcefsck ]; then
fsck_opts="$fsck_opts -f"
check_extra="(check forced)"
fi
echo "Checking local filesystem $check_extra : $1"
if [ "$RC_UNAME" = Linux ]; then
fsck_opts="$fsck_opts -C0 -T"
fi
trap : INT QUIT
# using our own fsck, not the builtin one from busybox
/sbin/fsck -p $fsck_opts $1
case $? in
0) return 0;;
1) echo "Filesystem repaired"; return 0;;
2|3) if [ "$RC_UNAME" = Linux ]; then
echo "Filesystem repaired, but reboot needed"
reboot -f
else
rescue_shell "Filesystem still have errors; manual fsck required"
fi;;
4) if [ "$RC_UNAME" = Linux ]; then
rescue_shell "Fileystem errors left uncorrected, aborting"
else
echo "Filesystem repaired, but reboot needed"
reboot
fi;;
8) echo "Operational error"; return 0;;
12) echo "fsck interrupted";;
*) echo "Filesystem couldn't be fixed";;
esac
rescue_shell
}
# temporarily mount proc and sys
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
# disable kernel messages from popping onto the screen
echo 0 > /proc/sys/kernel/printk
# clear the screen
clear
# mounting rootfs on /mnt/root
uuidlabel_root || rescue_shell "Error with uuidlabel_root"
# space separated list of mountpoints that ...
mountpoints="/usr" #note: you can add more than just usr, but make sure they are declared in /usr/src/initramfs/initramfs_list
# ... we want to find in /etc/fstab ...
ln -s /mnt/root/etc/fstab /etc/fstab
# ... to check filesystems and mount our devices.
for m in $mountpoints ; do
check_filesystem $m
echo "Mounting $m"
# mount the device and ...
mount $m || rescue_shell "Error while mounting $m"
# ... move the tree to its final location
mount --move $m "/mnt/root"$m || rescue_shell "Error while moving $m"
done
echo "All done. Switching to real root."
# clean up. The init process will remount proc sys and dev later
umount /proc
umount /sys
umount /dev
# switch to the real root and execute init
exec switch_root /mnt/root /sbin/init
Copy and save the contents of the above to /usr/src/initramfs/init.
System preparation
In fstab, we must set the sixth field for the /usr
entry to 0
, this will prevent the OpenRC fsck init script to try to check the filesystem for the already mounted /usr:
/dev/sdb3 /usr ext4 noatime 0 0
Generating the Initramfs
Building as an embedded Initramfs
It is not necessary to compile gen_init_cpio or make it executable because these steps will be handled when building the kernel with make. Both files, initramfs_list and init must be copied into /usr/src/initramfs/. For an embedded initramfs one line is missing and must be added to initramfs_list
nod /dev/console 0600 0 0 c 5 1
This kernel configuration will do all steps to include all needed files in an embedded initramfs.
For embedding the initramfs directly into the kernel image, the initramfs_list must be coded in Initramfs source file(s) (CONFIG_INITRAMFS_SOURCE) in the kernel (directly under the Initial RAM filesystem and RAM disk (initramfs/initrd) support (CONFIG_BLK_DEV_INITRD) option):
General setup --->
[*] Initial RAM filesystem and RAM disk (initramfs/initrd) support
(/usr/src/initramfs/initramfs_list) Initramfs source file(s)
[*] Support initial ramdisk/ramfs compressed using gzip
Built-in initramfs compression mode (Gzip) --->
Building as an external CPIO archive
The kernel sources provide the gen_init_cpio and gen_initramfs.sh utilities. The gen_init_cpio utility does not come prepackaged and needs to be built:
root #
make -C /usr/src/linux/usr/ gen_init_cpio
Make sure that these two are executable:
root #
cd /usr/src/linux
root #
chmod +x usr/gen_init_cpio usr/gen_initramfs.sh
For older Kernels exchange usr/ with scripts/
Run the gen_initramfs.sh script with the -o
argument pointing to where we want the initramfs image to be placed followed by the path to our initramfs_list file:
root #
cd /usr/src/linux
root #
usr/gen_initramfs.sh -o /boot/initrd.cpio /usr/src/initramfs/initramfs_list
After that compress the file /boot/initrd.cpio via gzip:
root #
gzip --best /boot/initrd.cpio
This will create the archive /boot/initrd.cpio.gz.
Bootloader configuration
To use the external initramfs, the bootloader needs to be configured as shown below for GRUB and LILO as examples. For an embedded Initramfs this is not necessary !
Configuring GRUB
Add the initrd
line to /boot/grub/grub.conf:
title Gentoo Linux <version>
root (hd0,0)
kernel /boot/kernel-<version>-gentoo
initrd /boot/initrd.cpio.gz
Configuring LILO
Add the initrd
and append
line to /etc/lilo.conf:
image = /boot/vmlinuz-<version>-gentoo
#root = /dev/sda4
label = gentoo
read-only
append = "real_root=/dev/sda4"
initrd = boot/initrd.cpio.gz
Using a Stub Kernel
If no bootmanager is used (UEFI boots a stub kernel directly) the UUID of the root partition must be configured into the built-in kernel command line or as parameter in the UEFI boot entry (see next paragraph):
Processor type and features --->
[*] Built-in kernel command line
(root=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Built-in kernel command string
When using an external Initramfs initrd.cpio.gz must be copied to the EFI System Partition and initrd= must use the correct path. Set the parameter initrd= only in an UEFI boot entry. It does not work when setting it in the built-in kernel command line. In this case it is recommended to set both parameter in this UEFI boot entry. Example:
root #
efibootmgr -c -d /dev/sda -p 1 -L "Gentoo" -l '\EFI\gentoo\bzImage.efi' -u 'root=UUID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx initrd=\EFI\gentoo\initrd.cpio.gz'
See more here: Efibootmgr#Creating_a_boot_entry
Result
When booting, the output looks like this:
Mounting rootfs
Checking local filesystem : /usr
/dev/sdb3: clean, 285754/1640160 files, 1663220/6556528 blocks
Mounting /usr
(Potentially other fs checks on other partitions here...)
All done. Switching to real root.
INIT: version 2.88 booting
OpenRC 0.9.8.4 is starting up Gentoo Linux (i686)
Press I to enter interactive boot mode
[...]
See also
- Custom Initramfs — the successor of initrd. It provides early userspace which can do things the kernel can't easily do by itself during the boot process.
- cpio — a file archiving utility