Ferdinand Keil's

electronic notes

Jan 15, 2017

Read-only Filesystem for Raspberry Pi

I recently set up a Raspberry Pi as a print- and scan-server. I want to be able to just turn off the Raspberry Pi without a proper shutdown, so to prevent SD-card corruption a read-only filesystem was needed. The easy way to implement this is to just mount the root filesystem as read-only. An unwanted side-effect of this is that all writes to the filesystem fail. The superior solution uses a read-only root with an overlay stored in RAM. Luckily ejolson on the Raspberry Pi forum has written a nice tutorial to implement this.

I successfully configured Raspbian Jessie Lite (2016-11-25) running on a Raspberry Pi 1. I have not tested it with any other Pi, but they should work just as well. If you try this tutorial with a more recent Raspbian version it might not work!

Update: Still works with a Raspberry Pi 1 and Raspbian Stretch Lite (2018-04-18).

Step 1

Change into a root shell and change to /usr/share/initramfs-tools by typing:

sudo bash
cd /usr/share/initramfs-tools # type into root shell

You have to change the file hook-functions. Just add overlay to the modules in line 528.

515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# The modules "most" classes added per default to the initramfs
auto_add_modules()
{
    local arg
    local modules=

    if [ "$#" -eq 0 ] ; then
        set -- base net ide scsi block ata i2o dasd ieee1394 firewire mmc usb_storage
    fi

    for arg in "$@" ; do
        case "$arg" in
        base)
            modules="$modules ehci-pci ehci-orion ehci-hcd ohci-hcd ohci-pci uhci-hcd usbhid overlay"
            modules="$modules xhci xhci-pci xhci-hcd"
            modules="$modules btrfs ext2 ext3 ext4 ext4dev "
            modules="$modules isofs jfs reiserfs udf xfs"
            modules="$modules nfs nfsv2 nfsv3 nfsv4"
            modules="$modules af_packet atkbd i8042 psmouse"
            modules="$modules virtio_pci virtio_mmio"

You can find my version on Gist: /usr/share/initramfs-tools/hook-functions.

This takes care of loading the overlay module at boot time.

Step 2

Next, you have to create a new boot script. Change to /usr/share/initramfs-tools/scripts and copy the existing boot script. Don't worry if the last command fails, it will still work.

cd /usr/share/initramfs-tools/scripts
cp local overlay
cp -rp local-premount overlay-premount
cp -rp local-bottom overlay-bottom # don't mind if this fails

Now you'll have to change the file overlay to mount the root read-only. The changes start in line 138.

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    ROOT=$(resolve_device "$ROOT")

#   if [ "${readonly}" = "y" ]; then
#       roflag=-r
#   else
#       roflag=-w
#   fi

    # FIXME This has no error checking
    modprobe ${FSTYPE}

    checkfs ${ROOT} root

    # FIXME This has no error checking
    # Mount root
#   if [ "${FSTYPE}" != "unknown" ]; then
#       mount ${roflag} -t ${FSTYPE} ${ROOTFLAGS} ${ROOT} ${rootmnt}
#   else
#       mount ${roflag} ${ROOTFLAGS} ${ROOT} ${rootmnt}
#   fi
    mkdir /upper /lower
    if [ "${FSTYPE}" != "unknown" ]; then
        mount ${roflag} -t ${FSTYPE} ${ROOTFLAGS} ${ROOT} /lower
    else
        mount ${roflag} ${ROOTFLAGS} ${ROOT} /lower
    fi
    modprobe overlay
    mount -t tmpfs tmpfs /upper
    mkdir /upper/data /upper/work
    mount -t overlay \
        -olowerdir=/lower,upperdir=/upper/data,workdir=/upper/work \
        overlay ${rootmnt}
}

Gist: /usr/share/initramfs-tools/scripts/overlay

Step 3 (Raspberry Pi 1, Zero, B+)

You can now generate a suitable initramfs. However, you'll have to determine the kernel version first. In this example it is 4.4.34+. For a Raspberry Pi 2 or later it would be 4.4.34-v7+ (notice the v7). Now you can run update-initramfs using the kernel version. Then you just rename the resulting file.

Update: the command below takes care of determining the correct Kernel version.

update-initramfs -c -k "$(uname -r)"
mv "/boot/initrd.img-$(uname -r)" /boot/initrd.img

Now you have to tell the bootloader to load the initramfs. This can be done by adding the following lines to /boot/config.txt:

55
56
57
58
59
# Enable audio (loads snd_bcm2835)
dtparam=audio=on

kernel=kernel.img
initramfs initrd.img

Gist: /boot/config.txt

Step 3 (Raspberry Pi 2 and later)

As the later Raspberry Pi models use a different CPU, the output of uname will have an additional v7 in the kernel version (e.g. 4.4.34-v7+). The commands change to:

uname -a
# Linux raspberrypi 4.4.34-v7+ #930 Wed Nov 23 15:12:30 GMT 2016
armv6l GNU/Linux
update-initramfs -c -k 4.4.34-v7+
cd /boot
mv initrd.img-4.4.34-v7+ initrd7.img

Notice the 7 in initrd7.img. Now change /boot/config.txt:

55
56
57
58
59
# Enable audio (loads snd_bcm2835)
dtparam=audio=on

kernel=kernel7.img
initramfs initrd7.img

Step 4

The last step is to change the boot command. Just add boot=overlay to the command in /boot/cmdline.txt:

boot=overlay dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait fastboot noswap

To disable the overlay this string has to be removed. As this file is found in the FAT-formatted boot partition this can be done by inserting the SD-card into a Windows PC as well. Notice that I have also added fastboot and noswap to the command. These options disable filesystem checking at boot and the swap.

Finish

If you have followed all the steps you can now reboot. After reboot run df to see if it worked.

df
# Filesystem 1K-blocks Used Available Use% Mounted on
# udev 10240 0 10240 0% /dev
# tmpfs 37092 4596 32496 13% /run
# overlay 92720 1180 91540 2% /
# tmpfs 92720 0 92720 0% /dev/shm
# tmpfs 5120 4 5116 1% /run/lock
# tmpfs 92720 0 92720 0% /sys/fs/cgroup
# /dev/mmcblk0p1 64456 26528 37928 42% /boot
# tmpfs 18548 0 18548 0% /run/user/1000

If the filesystem for root (/) shows up as overlay it worked.