Linux – How to boot into root btrfs file system with minimal initramfs without udev hook

arch linuxinitramfsmkinitcpio

Arch Linux is UEFI booted from USB flash drive with GPT and three partitions:

  1. EFI (vfat file system)
  2. root (btrfs file system, was converted from ext4)
  3. home (btrfs file system, was converted from ext4)

The btrfs partitions have no sub-volumes and are on a single disk (the USB flash memory drive). LVM is not in use here.

Task

Trying to create a minimal initramfs by removing udev and a lot of other hooks. Optimizing Bootup With mkinitcpio was used as inspiration too.
The effective mkinitcpio hooks are: base, autodetect and modconf.

Btrfs hook

The btrfs hook is not enabled because mkinitcpio hooks documentation lists for btrfs hook:

This hook is not required for using Btrfs on a single device.

Regression

  1. I have tried to remove udev -> boot error
  2. I have tried to add the btrfs module -> boot error
  3. I have added btrfs hook -> boot error
  4. Changed root=PARTUUID= to root=UUID= notation -> boot error
  5. adding paramters rootfstype=btrfs -> boot error
  6. rootdelay=0 -> boot error
  7. rootdelay=10 -> boot error
  8. Mounting using /dev/sda2 from emergency shell -> ok

Error

Only after inserting udev or systemd hooks the system will root into the btrfs root partition, otherwise this error appears:

ERROR: device 'PARTUUID=c2...c13' not found. Skipping fsck.
:: mounting 'PARTUUID=c2...c13' on real root 
mount: can't find 'PARTUUID=c2...c13'
You are now being dropped into an emergency shell.

Runtime init debug/log output

Enabling boot parameters rd.debug and rd.log shows that "premount" calls the resolve_device function and that returns an empty lookup.

resolve_device PARTUUID=c2...c13
local major minor dev tag device=PARTUUID=c2...c13
blkid -lt PARTUUID=c2...c13 -o device
dev=

The last empty dev is causing the device not found error.

initramfs mount command

mount_handler=default_mount_handler
...
# Mount root at /new_root
"$mount_handler" /new_root

source: https://git.archlinux.org/mkinitcpio.git/tree/init

default_mount_handler() {
    msg ":: mounting '$root' on real root"
    mount ${rootfstype:+-t $rootfstype} -o ${rwopt:-ro}${rootflags:+,$rootflags} "$root" "$1"

source: https://git.archlinux.org/mkinitcpio.git/tree/init_functions#n375

initramfs mount version

[rootfs ]# mount -V
mount from util-linux 2.29.2 (libmount 2.29.2: btrfs, assert, debug)

initramfs contents

$ lsinitcpio -a /boot/initramfs-linux-tiny.img
==> Image: /boot/initramfs-linux-tiny.img
==> Created with mkinitcpio 23
==> Kernel: 4.10.3-1-ARCH
==> Size: 3.53 MiB
==> Compressed with: lz4 -l
  -> Uncompressed size: 8.32 MiB (.424 ratio)
  -> Estimated extraction time: 0.028s

==> Included modules:
  ahci [explicit]         hid-generic [explicit]      raid6_pq            usbcore
  atkbd [explicit]        i8042 [explicit]        scsi_mod            usbhid [explicit]
  btrfs [explicit]        libahci             sd_mod [explicit]       xhci-hcd
  crc32c-intel [explicit]     libata              serio               xhci-pci [explicit]
  crc32c_generic          libcrc32c           serio_raw [explicit]        xor
  ehci-hcd            libps2              uas [explicit]
  ehci-pci [explicit]         ohci-hcd            usb-common
  hid                 ohci-pci [explicit]         usb-storage

==> Included binaries:
  blkid       busybox     dosfsck     fsck        fsck.vfat   kmod        mount       switch_root

The emergency shell its blkid command lists the correct (PART)UUID value. Could the mounting using using (PART)UUID fail because there is no /dev/disk/?

Question

What is necessary to boot into a non-raid non-subvolume single-drive root btrfs partition located on a USB flash drive without udev?


PS This error might be caused by a RACE condition, UUID/PARTUUID not yet available when initramfs/init executes the mount ... UUID=... command.

Best Answer

Cause

In version 23, the mkinitcpio resolve_device() function is called only once. When at execution time the drive labels are not yet read, blkid can't lookup the kernel drive (/dev/...) name for the requested label.

Solution

By adding the "without-udev" hook, as listed below, the resolve_device function is left untouched. Though standard available mkinitcpio functionality to override the mount_handler add a run_hook is used to poll until blkid returns a value, or (a timeout of) 10 seconds has passed. Thus the "udev" hook can be removed from mkinitcpio config.

Notes

  • This solution was created with the help of falconindy.
  • There was an error message in the early boot phase involving fsck. To remove that message the without-udev hook has been rewritten to use a run_hook instead of a mount_handler. The newer code is even shorter.

$ cat /usr/lib/initcpio/hooks/without-udev
#!/bin/ash
# Minimal initramfs files are created without udev.
# This hooks provides a polling disk mount replacement for udev.
# Udev hook can be removed, resulting in smaller initramfs files.

run_hook () {
    local dev timeout sleepval device=$root
    # if udev is running then exit
    [ "$udevd_running" -eq 1 ] && return
    # try for (timeout * sleepval =) 10 seconds to handle slow (USB) devices
    timeout=1000
    sleepval=0.01

    case $device in
        # label to resolve, when resolved the kernel block device also exists
        UUID=*|LABEL=*|PARTUUID=*|PARTLABEL=*)
            while [ $timeout -gt 0 ]; do
                timeout=$((timeout - 1))
                dev=$(blkid -lt "$device" -o device)
                [ -n "$dev" ] && timeout=0 || sleep $sleepval
            done
            ;;
        # kernel named block device, poll for existence
        /dev/*)
            while [ $timeout -gt 0 ]; do
                timeout=$((timeout -1))
                if [ -b "$device" ]; then
                    dev=$device
                    timeout=0
                else
                    sleep $sleepval
                fi
            done
            ;;
    esac
}

# vim:set syntax=sh:

$ cat /usr/lib/initcpio/install/without-udev
#!/bin/bash

build() {
    add_runscript
}

help() {
    cat <<HELPEOF
This hook provides support for booting without the "udev" hook,
including support for UUID, LABEL, PARTUUID, PARTLABEL.
HELPEOF
}

# vim: set ft=sh ts=4 sw=4 et:
Related Question