The initramfs concept is a bug. It is the kludge that allows the kernel to punt to userspace on all kinds of problems. Basically it is a small filesystem that the kernel loads before it loads the real file system. Something sounds fishy there doesn’t it? This is helpful for things like encrypted file systems, drives with weird (proprietary driver?) hardware or NFS mounts, and, my problem, RAID boot drives. Read more about what it is at the pretty good Wikipedia page.

Note
I also just found out that LVM partitions which contain a Linux root file system need an initramfs too. This is not just to boot and find the kernel since GRUB2 can penetrate LVM. But the kernel can not mount a file system on LVM without userspace tools.

I like to have my kernel do what is needed because it is set up exactly the way it should be. And in 2005, this worked for something like mounting a simple Linux system that just happened to be protected by a kernel managed software RAID1 double drive setup. But as time wore on, the metadata for the RAID volumes changed and although the new bootloader understood it, the kernel, weirdly, did not. No one seemed to care that these formerly robust systems were now broken. This was because *every*body used an initramfs so that the kernel wouldn’t have to do its job. I fought this and tried to do it the Right Way, but eventually I just ran into too many brick walls. So a stupid initramfs it is.

Homemade initramfs

Because I like to understand what’s going on, and minimize cruft (maintenance and security headaches), I wanted to create my own initramfs and not rely on any automagical tools that did who-knows-what. Here is the system that worked for me.

Build Automation

Start in a directory which contains a subdirectory called initramfs-src which contains the file system you want to be in the initramfs. Then run this and you will get

  • initramfs.cpio - suitable for including in the kernel itself

  • initramfs.igz - the gzipped version suitable for loading by the bootloader.

fs2initramfs
#/bin/sh
cd /boot/initramfs-src/
find . | cpio -H newc -o > ../initramfs.cpio
cd ..
cat initramfs.cpio | gzip > initramfs.igz

Minimal Directory Structure

The initram filesystem must contain the following directories:

  • bin

    • busybox (static)

    • sh i.e. ln busybox sh

  • etc

    • mdev.conf (can be empty)

  • newroot

  • proc

  • sbin

    • mdadm (static) The whole point of my initramfs - see below

  • sys

/init

Also in the top level there needs to be a file called init. This is what control is handed over to by the kernel right after the file system is loaded into ram. Here is a pretty competent version of that that I got from jootamam’s notes.

init
#!/bin/sh

#Mount things needed by this script
mount -t proc proc /proc
mount -t sysfs sysfs /sys

#Disable kernel messages from popping onto the screen
# echo 0 > /proc/sys/kernel/printk

#Clear the screen
# clear

#Create all the symlinks to /bin/busybox
busybox --install -s

#Create device nodes
mknod /dev/null c 1 3
mknod /dev/tty c 5 0
mdev -s

#Function for parsing command line options with "=" in them
# get_opt("init=/sbin/init") will return "/sbin/init"
get_opt() {
        echo "$@" | cut -d "=" -f 2
}

# XED-
/sbin/mdadm --verbose --assemble /dev/md0 /dev/sda /dev/sdb

#Defaults
init="/sbin/init"
#root="/dev/hda1"
root="/dev/md0"

#Process command line options
for i in $(cat /proc/cmdline); do
        case $i in
                root\=*)
                        root=$(get_opt $i)
                        ;;
                init\=*)
                        init=$(get_opt $i)
                        ;;
        esac
done

#Mount the root device
mount "${root}" /newroot

#Check if $init exists and is executable
if [[ -x "/newroot/${init}" ]] ; then
        #Unmount all other mounts so that the ram used by
        #the initramfs can be cleared after switch_root
        umount /sys /proc

        #Switch to the new root and execute init
        exec switch_root /newroot "${init}"
fi

#This will only be run if the exec above failed
echo "Failed to switch_root, dropping to a shell"
exec sh

Statically Compiled Tools

Note the /sbin/mdadm line that assembles that RAID1 array with modern >v1.0 volume metadata, the whole point of this annoying exercise.

Of course you’ll need a /sbin/mdadm and either 1. all of its library dependencies checkable (recursively) with ldd /sbin/mdadm or 2. a statically compiled version of that executable (i.e. no dependencies). I went with the latter in Gentoo which was quite easy with the following command.

USE="static" ebuild /usr/portage/sys-fs/mdadm/mdadm-3.2.6.r1.ebuild compile

GRUB2

When booting this with a linux kernel use initrd but when booting it as a paravirtualized Xen kernel, use module for both the kernel file and the initramfs.

Editing Existing initrd

Got an initrd with something stupid going on? For example, maybe the wrong names are used in the init file. Here’s how to get into the thing and make changes.

mkdir /tmp/initrd
cd /tmp/initrd
gzip -cd /boot/initrd-xxxxxxxxx.img | cpio -imd --quiet

Edit anything you need to edit and then put it back like this.

cd /tmp/initrd
find . | cpio -co | gzip -9 > /boot/initrd-xxxxxxxxx.img