$blogname

random things and thoughts … and bad ideas

my very own Gentoo UEFI live stick

After buying the MS Surface Pro 2 my first thought was: “Let’s get some real OS on this thing and actually start working”. The SP2 is not as locked down as your generic Android or iOS tablet because it is technically a normal PC just as any other notebook. Still the limited connectivity and very minimalistic UEFI make booting other operating systems not as trivial as usual. To test Linux running on the SP2 and later handle the permanent installation I looked for a Linux live distribution that can be EFI booted from a USB stick. Surprisingly many live distributions I found did not support both EFI boot and USB sticks back when I did my search. Having played around with aufs to make my router run from RAM some time before I thought I should just try to create my own UEFI Live Stick.

Preparing the operating system

The basic structure of the live stick I thought about was rather simple: A minimalistic Linux system including a few system and network administration tools installed to a USB stick and made “live” by using the union filesystem aufs to run it completely from RAM. I am a long time Gentoo user and run both my desktop and router with it because Gentoo can be adjusted to nearly any situation. So the choice which specific flavor of Linux I should use as the base for the stick was a pretty obvious one for me. Because this was not supposed to be a very sophisticated solution I just based the stick on a normal Gentoo installation starting similar to the Gentoo Handbook.

Choosing to make a pure 64bit system I got the current amd64-nomultilib stage3 file and extracted it to a working directory. The next few steps more or less just followed the handbook. After mounting /dev, /proc and /sys into the stage3 directory and preparing the mirror and resolver configuration I chrooted into the stage3. Freshly installed stage3s don’t include any data for the package system so at first the portage tree has to be updated. The handbook recommends using emerge-webrsync for the initial update. Having an up-to-date portage tree I configured portage’s make.conf next. Obviously for a live system the compiler optimization and tuning should be something generic so in CFLAGS I used -mtune=generic. For the USE-flags I entered the most common filesystems to enable support for them in hard disk management tools. I also added the flags minimal and -systemd to reduce dependencies a little. To broaden support for devices running the live stick I added intel, nouveau and radeon to the VIDEO_CARDS option to build with support for the open source drivers at least.

Because some software needs a kernel configuration to check for enabled kernel functions the kernel sources should be installed and configured first. I started with installing aufs-sources, the Gentoo kernel sources with aufs already patched in. Because I am lazy and inexperienced with configuring generic kernels for a wide range of devices I chose to extract the kernel configuration from the Arch Linux Live medium and adjust it slightly for my stick. The most important change was activating the aufs under Miscellaneous filesystems. Additional options for aufs are not necessary to just make the stick work. Other things that needed checking are initramfs support under General Setup, because initramfs is needed to set up the aufs at boot time, as well as EFI runtime service support and EFI stub under Processor type and features, to make the kernel image boot directly from the UEFI. Finally I checked the device configuration for drivers and options specific to the SP2 like touchscreen, WLAN/BT controller, the Type Cover and so on. With kernel 3.16 and the Arch Linux configuration no changes were necessary regarding essential devices on the SP2.

After the kernel was set up I continued with installing all the software needed on the stick. Essential to getting the kernel booted are just the kernel itself and maybe busybox but to actually do something with the stick I decided to install some tools like (g)parted, ethtool, tcpdump, wireshark and xfce4 to test the touchscreen of the SP2. With all the software installed on the new system I set up the basic configuration mentioned in the handbook. I generated a fitting locale, set the hostname of the system, set the default keymap, changed the root password and changed the clock setting to local instead of UTC because the stick is supposed to run on a device shipped with Windows. The /etc/fstab  and /etc/conf.d/net files stayed empty because mounting the rootfs on the stick is done in the initramfs and other filesystems as well as network interfaces that are present on the device running the stick cannot be known at build time. To finish up the system I configured the default services to run which was only syslog-ng in the current version of the live system.

The kernel and initramfs

The system in the working directory was ready and just had to be placed on the stick and started by a kernel. So I went on to build the kernel and prepare the booting process. Apparently to create working modules the whole kernel build process has to run at least once to create a proper Module.symvers file. Starting with a simple make I got this done. The kernel build process can create and populate the initramfs needed for the boot process on the fly and build it into the generated kernel image. To do that support for initramfs must be enabled and additionally a path to a file listing the contents of the initramfs has to be specified in the kernel configuration. Details about the list file and alternatives to configuring the initramfs are explained in the kernel documentation in filesystems/ramfs-rootfs-initramfs.txt. My list file contains the basic directory layout of a Linux system, the directories needed for mounting the aufs, a /dev/console node so the shell of the initramfs works, the busybox binary providing the shell and basic system tools, the init script getting run by the kernel that does the actual work of the initramfs as well as the directory structure and the kernel modules needed to mount the root filesystem and the aufs. The final list looked like this.

# directories / dev nodes
dir /bin 755 0 0
dir /dev 755 0 0
dir /etc 755 0 0
dir /lib 755 0 0
dir /proc 755 0 0
dir /root 700 0 0
dir /sbin 755 0 0
dir /sys 755 0 0
dir /usr 755 0 0
dir /var 755 0 0

dir /aufs 755 0 0
dir /ro 755 0 0
dir /rw 755 0 0

nod /dev/console 622 0 0 c 5 1

# busybox
file /bin/busybox /bin/busybox 755 0 0

# initscript
file /init /usr/src/initramfs/init 755 0 0

# modules
dir /lib/modules								755 0 0
dir /lib/modules/3.16.1-aufs							755 0 0
dir /lib/modules/3.16.1-aufs/kernel						755 0 0
dir /lib/modules/3.16.1-aufs/kernel/crypto					755 0 0
dir /lib/modules/3.16.1-aufs/kernel/drivers					755 0 0
dir /lib/modules/3.16.1-aufs/kernel/drivers/usb					755 0 0
dir /lib/modules/3.16.1-aufs/kernel/drivers/usb/storage				755 0 0
dir /lib/modules/3.16.1-aufs/kernel/drivers/scsi				755 0 0
dir /lib/modules/3.16.1-aufs/kernel/lib						755 0 0

file /lib/modules/3.16.1-aufs/kernel/crypto/crct10dif_common.ko			/lib/modules/3.16.1-aufs/kernel/crypto/crct10dif_common.ko		755 0 0
file /lib/modules/3.16.1-aufs/kernel/drivers/usb/storage/usb-storage.ko		/lib/modules/3.16.1-aufs/kernel/drivers/usb/storage/usb-storage.ko	755 0 0
file /lib/modules/3.16.1-aufs/kernel/drivers/scsi/scsi_mod.ko			/lib/modules/3.16.1-aufs/kernel/drivers/scsi/scsi_mod.ko		755 0 0
file /lib/modules/3.16.1-aufs/kernel/drivers/scsi/sd_mod.ko			/lib/modules/3.16.1-aufs/kernel/drivers/scsi/sd_mod.ko			755 0 0
file /lib/modules/3.16.1-aufs/kernel/lib/crc-t10dif.ko				/lib/modules/3.16.1-aufs/kernel/lib/crc-t10dif.ko			755 0 0

As a note: I got the list of necessary modules by trial and error. I built the complete stick, tried to boot it and noted what modules couldn’t be loaded and repeated until it booted without error. Another and probably more professional approach would be to check the module requirements inside the kernel configuration.

The contents of the initramfs had been set up but there was still no script running in the initramfs. For a simple setup like this live system the init script does not have to be very complex either. All the script has to do is load the necessary kernel modules, mount the filesystem on the stick to create an aufs combined with a tmpfs and finally change the rootfs and execute the right init. Also in case anything goes wrong the initramfs should provide a shell to look for the reason. So using /bin/busybox sh as the interpreter I created my init script. At the beginning I declared a function to start a shell that could be called later to provide the fallback shell. The first real action in the script is mounting /proc, /sys and /dev and loading the modules scsi_mod, usb-storage and sd_mod in that order. I gave each of the modules a few seconds of waiting time so the devices got time to load properly. Mounting the filesystem on the stick by device name is no option for a live system and busybox mount does not support UUIDs but it does support mounting by filesystem label. So to create the aufs I first let the script mount the filesystem on the stick read-only by its label to the mountpoint /ro in the initramfs. The tmpfs that stores all the changes gets mounted to /rw. The last line in that section actually combines both of these branches to the aufs and mounts it at /aufs. With the aufs working I could exchange the rootfs with it and start the real init which is done with the switch_root command. Before actually switching the rootfs I unmounted /proc, /sys and /dev to clean up the initramfs leading to this finished version of the script.

#!/bin/busybox sh
rescue_shell() {
	echo "Starting Rescue Shell."
	/bin/busybox --install -s
	exec /bin/sh
}

echo "Running initramfs"

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev

modprobe scsi_mod
sleep 1s
modprobe usb-storage
sleep 2s
modprobe sd_mod
sleep 5s

mount LABEL=efitoo-live-root /ro -o ro
if	[ $? -ne 0 ]; then
	echo	"Error mounting root filesystem"
	rescue_shell
fi
mount -t tmpfs tmpfs /rw -o defaults,noatime,nodiratime
mount -t aufs none /aufs -o defaults,noatime,nodiratime,br:/rw:/ro=rr
if	[ $? -ne 0 ]; then
	echo	"Error mounting aufs"
	rescue_shell
fi

mount -t devtmpfs none /aufs/dev
umount /proc
umount /sys
umount /dev
exec switch_root -c /dev/console /aufs /sbin/init
echo	"Could not switch root"
rescue_shell

With all the contents for the initramfs built or configured I could finish the kernel by entering the path to my list file in the kernel configuration and do another make. The resulting kernel image is a bootable UEFI image that already includes the initramfs and will run it automatically when booted. At this point it was finally time to leave the chroot environment with exit. To finish the stick I just had to create the needed partitions on the stick and move the kernel image and the system in the appropriate places.

Make it boot

The easiest way to boot with UEFI is using a GUID Partition Table with a EFI System Partition. Using gparted I created a GPT with two partitions on my USB stick. The first partition was formatted with FAT16, because of the size of only 50 MB, and got the boot flag enabled to make gparted set its GUID properly. The other partition got the rest of the stick’s capacity and was formatted with ext4. Because I used a label to mount the filesystem in the initramfs the label for this filesystem had to be the exact same value as was used in the init script of the initramfs. So I had the ESP to boot from and a partition to store the operating system. Be advised that once you create a GPT on a USB stick Windows seems to be unable to do anything with the stick. Neither Windows 7 nor 8 were able to mount the stick. Not even creating a new partition table Windows could work with was possible.

At the end only copying everything onto the stick was left. After unmounting /dev, /proc and /sys in the working directory, so they don’t get copied, I used rsync -a to copy all the files from my working directory into the ext4 filesystem on the stick retaining all meta data like date stamps and permissions. To save some space it is a good idea to clean up the working directory before copying it. For example the portage tree and kernel sources could be deleted. By default most UEFIs on 64bit systems load the image found in EFI/Boot/BOOTX64.EFI on the ESP. Placing the kernel image, which was built as a loadable UEFI image, into that location will make most computers boot the kernel if the USB stick is selected as boot device. Because the initramfs is built into the kernel image no further configuration, boot options or boot loaders are required. Et voilà, a USB stick that uses UEFI to boot Gentoo Linux running in RAM was created.

Following up

Obviously after just these steps the stick won’t be a fully fledged live system with network autoconfig, sensible default settings for all programs or all the fancy features other live systems offer. But setting up these things or just adding new software only need work inside the working directory of the live system which then has to be copied to the stick. The basic setup of the bootable live stick does not need any changes. Adding things like persistent storage inside the live system will probably take some more severe changes in the init script and partitioning of the stick.

What I learned from this little experiment was how simple it can be to make Linux boot using UEFI and a built-in initramfs. Just having a single image that can boot itself and includes all necessary scripts and modules reduces complexity a lot. It should even be possible to put all of the userland into the initramfs using busybox with some other minimalistic tools dropping the aufs completely and never switching rootfs. That way the whole system would be contained in a single kernel image that can be booted independently from everything else on the drive. This would make for a nice rescue OS that just sits on your ESP occupying a few MB of drive space.

Again if you feel you have to add something to this please comment below.

TL;DR

  • extract current stage3 file into working directory
  • chroot intostage3 just like with a new Gentoo installation
    • configure portage and update tree (make.conf with generic optimization/tuning options)
    • install aufs-sources and get kernel configuration suited for wide range of devices
    • adjust kernel configuration for the live stick: activate aufs, EFI stub, built-in initramfs, etc.
    • emerge all necessary / wanted software and configure settings, default services, users, passwords etc.
    • create list file for the initramfs including init script, necessary modules and busybox
    • create init script for initramfs to load modules, mount file system on stick by label, create aufs and switch rootfs into the aufs
    • build kernel including built-in initramfs
  • prepare the stick: GPT, one EFI System Partition formatted as FAT, another partition for the root filesystem formatted and labeled properly
  • copy configured stage3 from working directory into the root filesystem on the USB stick retaining all permissions and ownership
  • copy kernel into the EFI System Partition as EFI/Boot/BOOTX64.EFI
Advertisements

Comments

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s