#!/bin/sh

# SPDX-License-Identifier: MIT
#
# Copyright 2022 (C), Microsoft Corporation

# Simple initramfs module intended to mount a read-write (RW)
# overlayfs on top of /, keeping the original root filesystem
# as read-only (RO), free from modifications by the user.
#
# NOTE: The read-only IMAGE_FEATURE is not required for this to work
#
# This script is based on the overlay-etc.bbclass, which sets up
# an overlay on top of the /etc directory, but in this case allows
# accessing the original, unmodified rootfs at /rofs after boot.
#
# It relies on the initramfs-module-rootfs to mount the original
# root filesystem, and requires 'overlayrootrwdev=<foo>' to be passed as a
# kernel parameter, specifying the device/partition intended to
# use as RW.
# Mount options of the RW device can be tweaked with 'overlayrootfstype='
# (defaults to 'ext4') and 'overlayrootfsflags=' ('defaults').
#
# This module needs to be executed after the initramfs-module-rootfs
# since it relies on it to mount the filesystem at initramfs startup
# but before the finish module which normally switches root.
# After overlayroot is executed the usual boot flow continues from
# the real init process.
#
# If something goes wrong while running this module, the rootfs
# is still mounted RO (with no overlay) and the finish module is
# executed to continue booting normally.
#
# It also has a dependency on overlayfs being enabled in the
# running kernel via KERNEL_FEATURES (kmeta) or any other means.


PATH=/sbin:/bin:/usr/sbin:/usr/bin

# We get OLDROOT from the rootfs module
OLDROOT="/rootfs"

NEWROOT="${RWMOUNT}/root"
RWMOUNT="/overlay"
ROMOUNT="${RWMOUNT}/rofs"
UPPER_DIR="${RWMOUNT}/upper"
WORK_DIR="${RWMOUNT}/work"

MODULES_DIR=/init.d

# Something went wrong, make sure / is mounted as read only anyway.
exit_gracefully() {
    echo $1 >/dev/console
    echo >/dev/console
    echo "OverlayRoot mounting failed, starting system as read-only" >/dev/console
    echo >/dev/console

    # The following is borrowed from rootfs-postcommands.bbclass
    # This basically looks at the real rootfs mounting options and
    # replaces them with "ro"

    # Tweak the mount option and fs_passno for rootfs in fstab
    if [ -f ${OLDROOT}/etc/fstab ]; then
        sed -i -e '/^[#[:space:]]*\/dev\/root/{s/defaults/ro/;s/\([[:space:]]*[[:digit:]]\)\([[:space:]]*\)[[:digit:]]$/\1\20/}' ${OLDROOT}/etc/fstab
    fi

    # Tweak the "mount -o remount,rw /" command in busybox-inittab inittab
    if [ -f ${OLDROOT}/etc/inittab ]; then
        sed -i 's|/bin/mount -o remount,rw /|/bin/mount -o remount,ro /|' ${OLDROOT}/etc/inittab
    fi

    # Continue as if the overlayroot module didn't exist to continue booting
    . $MODULES_DIR/99-finish
    eval "finish_run"
}

# migrate legacy parameter
if [ ! -z "$bootparam_rootrw" ]; then
    bootparam_overlayrootrwdev="$bootparam_rootrw"
fi

if [ -z "$bootparam_overlayrootrwdev" ]; then
    exit_gracefully "overlayrootrwdev= kernel parameter doesn't exist and its required to mount the overlayfs"
fi

mkdir -p ${RWMOUNT}

# Mount RW device
if mount -n -t ${bootparam_overlayrootfstype:-ext4} -o ${bootparam_overlayrootfsflags:-defaults} ${bootparam_overlayrootrwdev} ${RWMOUNT}
then
    # Set up overlay directories
    mkdir -p ${UPPER_DIR}
    mkdir -p ${WORK_DIR}
    mkdir -p ${NEWROOT}
    mkdir -p ${ROMOUNT}

    # Remount OLDROOT as read-only
    mount -o bind ${OLDROOT} ${ROMOUNT}
    mount -o remount,ro ${ROMOUNT}

    # Mount RW overlay
    mount -t overlay overlay -o lowerdir=${ROMOUNT},upperdir=${UPPER_DIR},workdir=${WORK_DIR} ${NEWROOT} || exit_gracefully "initramfs-overlayroot: Mounting overlay failed"
else
    exit_gracefully "initramfs-overlayroot: Mounting RW device failed"
fi

# Set up filesystems on overlay
mkdir -p ${NEWROOT}/proc
mkdir -p ${NEWROOT}/dev
mkdir -p ${NEWROOT}/sys
mkdir -p ${NEWROOT}/rofs

mount -n --move ${ROMOUNT} ${NEWROOT}/rofs
mount -n --move /proc ${NEWROOT}/proc
mount -n --move /sys ${NEWROOT}/sys
mount -n --move /dev ${NEWROOT}/dev

exec chroot ${NEWROOT}/ ${bootparam_init:-/sbin/init} || exit_gracefully "Couldn't chroot into overlay"
