]> git.ipfire.org Git - thirdparty/dracut-ng.git/commitdiff
feat(overlayfs): add persistent device overlay support
authorNadzeya Hutsko <nadzeya.hutsko@canonical.com>
Wed, 28 Jan 2026 22:19:34 +0000 (09:19 +1100)
committerNeal Gompa (ニール・ゴンパ) <ngompa13@gmail.com>
Thu, 29 Jan 2026 22:45:12 +0000 (23:45 +0100)
Extends the overlayfs module to support persistent overlay storage on
block devices using rd.overlay=. Supported formats: LABEL=, UUID=,
PARTUUID=, PARTLABEL=, and /dev/ paths.

Changes written to the overlay device persist across reboots. Falls
back to tmpfs if the device cannot be resolved or mounted.

.github/workflows/daily-basic.yml
.github/workflows/daily-hostonlystrict.yml
.github/workflows/daily-omitsystemd.yml
.github/workflows/integration.yml
man/dracut.cmdline.7.adoc
modules.d/70overlayfs/module-setup.sh
modules.d/70overlayfs/mount-overlayfs.sh
modules.d/70overlayfs/prepare-overlayfs.sh
test/TEST-21-OVERLAYFS/Makefile [new file with mode: 0644]
test/TEST-21-OVERLAYFS/assertion.sh [new file with mode: 0755]
test/TEST-21-OVERLAYFS/test.sh [new file with mode: 0755]

index 590928e587fd7419d8c05a70eeda0396e78460bd..902440a1b4740dff4a09a995335c99a09c26ec69 100644 (file)
@@ -41,6 +41,7 @@ jobs:
                     - "11"
                     - "13"
                     - "20"
+                    - "21"
                     - "26"
                     - "30"
                     - "50"
@@ -91,6 +92,7 @@ jobs:
                     - "11"
                     - "13"
                     - "20"
+                    - "21"
                     - "26"
                     - "30"
                     - "50"
index 2dbd9d3ec355a6eabff0dff7702f99ef889da12c..187c0022541e8ff204c0fc1574fdcd5270db0100 100644 (file)
@@ -35,6 +35,7 @@ jobs:
                     - "11"
                     - "13"
                     - "20"
+                    - "21"
                     - "26"
                     - "30"
                     - "40"
index 577a0e0f27504bef687bbba8da93abcb0b67f1fa..79a53eb8c4fda2230bed2d048ba8de1133fd0139 100644 (file)
@@ -34,6 +34,7 @@ jobs:
                     - "10"
                     - "11"
                     - "20"
+                    - "21"
                     - "26"
                     - "30"
                     - "31"
index 1ec62259bec1aa939dcfbf3b5e3514a8b68d6335..1ab2b98ba204e5ee901e9bc4116042f2de5fd64c 100644 (file)
@@ -91,6 +91,7 @@ jobs:
                     - "12"
                     - "13"
                     - "20"
+                    - "21"
                     - "26"
                     - "30"
                     - "50"
index c4b2a0482ab3f410c927787b316f2301919759b0..eff195fc7daf736099ec18ba9c6f80d45a0ea8cc 100644 (file)
@@ -1109,6 +1109,31 @@ The **rd.overlay.readonly** option, which allows a persistent overlayfs to
 be mounted read-only through a higher level transient overlay directory, has
 been implemented through the multiple lower layers feature of OverlayFS.
 
+**rd.overlay=**__{LABEL=<label>|UUID=<uuid>|PARTUUID=<uuid>|PARTLABEL=<label>|/dev/<device>}__::
+Specifies the storage device for persistent overlay (for both live images and
+regular OverlayFS).  When a block device is specified, it will be mounted and
+used for persistent overlay storage.  For live images with LABEL= specification,
+if the labeled partition does not exist, it will be automatically created using
+available free space on the device.
++
+Supported device specifications:
++
+--
+* _LABEL=<label>_ - Use the device with the specified filesystem label.
+* _UUID=<uuid>_ - Use the device with the specified UUID.
+* _PARTUUID=<uuid>_ - Use the device with the specified partition UUID.
+* _PARTLABEL=<label>_ - Use the device with the specified partition label.
+* _/dev/<device>_ - Use the specified device path directly.
+--
++
+[listing]
+.Examples
+----
+rd.overlay=LABEL=persist
+rd.overlay=UUID=12345678-1234-1234-1234-123456789abc
+rd.overlay=/dev/sdb1
+----
+
 Booting live images
 ~~~~~~~~~~~~~~~~~~~
 Requires the dracut 'dmsquash-live' module.
index dae2009352018b39bffbe5c948e43b5a471c2139..50e35aa5c25bc787cfcd4d60fd9d03cef0a393d6 100755 (executable)
@@ -15,6 +15,7 @@ installkernel() {
 
 install() {
     inst_hook pre-mount 01 "$moddir/prepare-overlayfs.sh"
-    inst_hook mount 01 "$moddir/mount-overlayfs.sh"     # overlay on top of block device
-    inst_hook pre-pivot 10 "$moddir/mount-overlayfs.sh" # overlay on top of network device (e.g. nfs)
+    inst_hook mount 01 "$moddir/mount-overlayfs.sh"       # overlay on top of block device
+    inst_hook pre-pivot 00 "$moddir/prepare-overlayfs.sh" # prepare for network root (e.g. nfs)
+    inst_hook pre-pivot 10 "$moddir/mount-overlayfs.sh"   # overlay on top of network device
 }
index aa71b1b7bc16113b9c89a80bd2d4209253ea6764..379d03f645d2cbf1a7a7d046f7a52ad5407597c6 100755 (executable)
@@ -4,8 +4,14 @@ command -v getarg > /dev/null || . /lib/dracut-lib.sh
 
 getargbool 0 rd.overlayfs -d rd.live.overlay.overlayfs && overlayfs="yes"
 getargbool 0 rd.overlay.readonly -d rd.live.overlayfs.readonly && readonly_overlay="--readonly" || readonly_overlay=""
+overlay=$(getarg rd.overlay -d rd.live.overlay)
 
-[ -n "$overlayfs" ] || return 0
+[ -n "$overlayfs" ] || [ -n "$overlay" ] || return 0
+
+# Only proceed if prepare-overlayfs.sh has run and set up rootfsbase.
+# This handles the case where root isn't available yet (e.g., network root like NFS).
+# The script will be called again at pre-pivot when the root is mounted.
+[ -e /run/rootfsbase ] || return 0
 
 if [ -n "$readonly_overlay" ] && [ -h /run/overlayfs-r ]; then
     ovlfs=lowerdir=/run/overlayfs-r:/run/rootfsbase
index 4380334ad766be0527168a60c0204aec08fb781a..c9439ea00bddcac533ab87274e8da11656fd697d 100755 (executable)
@@ -4,16 +4,70 @@ command -v getarg > /dev/null || . /lib/dracut-lib.sh
 
 getargbool 0 rd.overlayfs -d rd.live.overlay.overlayfs && overlayfs="yes"
 getargbool 0 rd.overlay.reset -d rd.live.overlay.reset && reset_overlay="yes"
+overlay=$(getarg rd.overlay -d rd.live.overlay)
 
-[ -n "$overlayfs" ] || return 0
+[ -n "$overlayfs" ] || [ -n "$overlay" ] || return 0
+
+overlay_mode="tmpfs"
+overlay_device=""
+
+case "$overlay" in
+    LABEL=* | UUID=* | PARTLABEL=* | PARTUUID=* | /dev/*)
+        overlay_mode="device"
+        overlay_device=$(label_uuid_to_dev "$overlay")
+        if [ ! -b "$overlay_device" ]; then
+            warn "Failed to resolve device from '$overlay', falling back to tmpfs"
+            overlay_mode="tmpfs"
+        fi
+        ;;
+    *)
+        # For dmsquash-live compatibility, any other format uses tmpfs
+        overlay_mode="tmpfs"
+        ;;
+esac
+
+# Skip if root not mounted and rootfsbase not set up by another module (e.g. dmsquash-live)
+if ! ismounted "$NEWROOT" && ! [ -e /run/rootfsbase ]; then
+    return 0
+fi
 
 if ! [ -e /run/rootfsbase ]; then
     mkdir -m 0755 -p /run/rootfsbase
     mount --bind "$NEWROOT" /run/rootfsbase
 fi
 
-mkdir -m 0755 -p /run/overlayfs
-mkdir -m 0755 -p /run/ovlwork
+if [ "$overlay_mode" = "device" ] && [ -n "$overlay_device" ]; then
+    info "Attempting to use persistent overlay on $overlay_device"
+
+    wait_for_dev -n "$overlay_device"
+
+    mkdir -m 0755 -p /run/overlayfs-backing
+
+    if mount "$overlay_device" /run/overlayfs-backing; then
+        info "Successfully mounted persistent overlay on $overlay_device"
+
+        mkdir -m 0755 -p /run/overlayfs-backing/overlay
+        mkdir -m 0755 -p /run/overlayfs-backing/ovlwork
+
+        ln -sf /run/overlayfs-backing/overlay /run/overlayfs
+        ln -sf /run/overlayfs-backing/ovlwork /run/ovlwork
+    else
+        warn "Failed to mount $overlay_device, falling back to tmpfs"
+        overlay_mode="tmpfs"
+    fi
+fi
+
+if [ "$overlay_mode" = "tmpfs" ]; then
+    info "Using tmpfs overlay (changes will not persist across reboots)"
+
+    [ -d /run/overlayfs ] || {
+        mkdir -m 0755 -p /run/initramfs/overlay/overlayfs
+        mkdir -m 0755 -p /run/initramfs/overlay/ovlwork
+        ln -sf /run/initramfs/overlay/overlayfs /run/overlayfs
+        ln -sf /run/initramfs/overlay/ovlwork /run/ovlwork
+    }
+fi
+
 if [ -n "$reset_overlay" ] && [ -h /run/overlayfs ]; then
     ovlfsdir=$(readlink /run/overlayfs)
     info "Resetting the OverlayFS overlay directory."
diff --git a/test/TEST-21-OVERLAYFS/Makefile b/test/TEST-21-OVERLAYFS/Makefile
new file mode 100644 (file)
index 0000000..7999b73
--- /dev/null
@@ -0,0 +1 @@
+include ../Makefile.testdir
diff --git a/test/TEST-21-OVERLAYFS/assertion.sh b/test/TEST-21-OVERLAYFS/assertion.sh
new file mode 100755 (executable)
index 0000000..dedce7d
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+if ! grep -q " overlay " /proc/mounts; then
+    echo "overlay filesystem not found in /proc/mounts" >> /run/failed
+fi
+
+if ! echo > /test-overlay-write; then
+    echo "overlay is not writable" >> /run/failed
+fi
+
+if grep -qE 'rd\.overlay=(LABEL|UUID|PARTUUID|PARTLABEL|/dev/)' /proc/cmdline; then
+    if grep -q "rd.overlay=LABEL=NONEXISTENT" /proc/cmdline; then
+        if grep -q "/run/overlayfs-backing" /proc/mounts; then
+            echo "non-existent device should have fallen back to tmpfs but backing is mounted" >> /run/failed
+        fi
+    else
+        if ! grep -q "/run/overlayfs-backing" /proc/mounts; then
+            echo "persistent overlay device not mounted at /run/overlayfs-backing" >> /run/failed
+        fi
+    fi
+else
+    # tmpfs mode - verify persistent backing is NOT mounted
+    if grep -q "/run/overlayfs-backing" /proc/mounts; then
+        echo "tmpfs mode but persistent backing is mounted at /run/overlayfs-backing" >> /run/failed
+    fi
+fi
+
+# Dump /proc/mounts at the end if there were any failures for easier debugging
+if [ -s /run/failed ]; then
+    {
+        echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /proc/mounts >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
+        cat /proc/mounts
+        echo "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< /proc/mounts <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<"
+    } >> /run/failed
+fi
diff --git a/test/TEST-21-OVERLAYFS/test.sh b/test/TEST-21-OVERLAYFS/test.sh
new file mode 100755 (executable)
index 0000000..6272551
--- /dev/null
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+set -eu
+# shellcheck disable=SC2034
+TEST_DESCRIPTION="Test overlayfs module with persistent device overlay"
+
+client_run() {
+    local test_name="$1"
+    shift
+    local client_opts="$*"
+
+    client_test_start "$test_name"
+
+    declare -a disk_args=()
+    qemu_add_drive disk_args "$TESTDIR"/root.img root
+    qemu_add_drive disk_args "$TESTDIR"/overlay.img overlay
+
+    "$testdir"/run-qemu -nic none \
+        "${disk_args[@]}" \
+        -append "$TEST_KERNEL_CMDLINE root=LABEL=dracut $client_opts" \
+        -initrd "$TESTDIR"/initramfs.testing
+    check_qemu_log
+
+    client_test_end
+}
+
+test_run() {
+    local overlay_uuid
+    overlay_uuid=$(blkid -s UUID -o value "$TESTDIR"/overlay.img)
+
+    client_run "tmpfs overlay" "rd.overlayfs=1"
+    client_run "persistent device overlay (LABEL)" "rd.overlay=LABEL=OVERLAY"
+    client_run "persistent device overlay (UUID)" "rd.overlay=UUID=$overlay_uuid"
+    client_run "persistent device overlay (device path)" \
+        "rd.overlay=/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_overlay"
+    client_run "fallback to tmpfs (non-existent LABEL)" "rd.overlay=LABEL=NONEXISTENT"
+}
+
+test_setup() {
+    call_dracut --tmpdir "$TESTDIR" \
+        --add-confdir test-root \
+        -i ./assertion.sh /assertion.sh \
+        -f "$TESTDIR"/initramfs.root
+
+    build_ext4_image "$TESTDIR"/dracut.*/initramfs/ "$TESTDIR"/root.img dracut
+
+    rm -f "$TESTDIR"/overlay.img
+    truncate -s 32M "$TESTDIR"/overlay.img
+    mkfs.ext4 -q -L OVERLAY "$TESTDIR"/overlay.img
+
+    test_dracut --add overlayfs
+}
+
+# shellcheck disable=SC1090
+. "$testdir"/test-functions