#!/bin/bash
# Mount overlays over any directory that might be written to

# Create a loopback mount corresponding to persistent.img on the given device
map_persistent_img_file() {
	local root_partition=$1

	mkdir -p /run/media/eosboot
	if ! mount "${root_partition}" /run/media/eosboot; then
		echo "Could not mount ${root_partition} for persistent.img" >&2
		return 1
	fi

	local pfpath="/run/media/eosboot/endless/persistent.img"
	local persistent_loop
	persistent_loop=$(losetup -f --show "${pfpath}")
	if [ $? != 0 ]; then
		echo "${pfpath} not found in ${root_partition}" >&2
		umount /run/media/eosboot
		return 1
	fi

	echo "Found persistent.img in ${root_partition}, using it for persistent storage" >&2
	echo ${persistent_loop}
	return 0
}

# Attempt to find a target to act as backing storage for the overlayfses
find_storage_partition() {
	local bootdev
	bootdev=$(grep -oP "endless\.image\.device=UUID=[^ ]+" /proc/cmdline)
	bootdev=${bootdev:21}
	if [ -z "${bootdev}" ] ; then
		echo "Failed to extract endless.image.device from kernel command line" >&2
		return 1
	fi

	local root_partition
	root_partition=$(blkid --list-one --output device --match-token "${bootdev}")
	if [ ! -b "${root_partition}" ] ; then
		echo "String passed on endless.image.device does not represent a block device" >&2
		return 1
	fi

	local storage_partition
	storage_partition=$(map_persistent_img_file "${root_partition}")
	if [ $? == 0 ]; then
		echo "${storage_partition}"
		return 0
	fi

	# Check for ISO layout - root partition is partition 1, storage partition
	# exists at partition 3.
	[ "${root_partition: -1}" == "1" ] || return 1

	storage_partition=${root_partition:0:-1}3
	[ -b "${storage_partition}" ] || return 1

	echo "${storage_partition}"
	return 0
}

setup_and_mount_storage() {
	local device=$1
	local marker
	[ -b "${device}" ] || return 1

	# Check for the marker installed at partition creation time.
	# If found, we know we've found the right partition and it's ready
	# for formatting.
	echo "Looking for endless_live_storage_marker in ${device}" >&2
	read -r -d '' -N 27 marker < "${device}"
	if [ "${marker}" = "endless_live_storage_marker" ]; then
		echo "Marker found, formatting ${device}" >&2
		mke2fs -t ext4 -O dir_index,^huge_file -m 1 -L endless-live \
			"${device}"
		# let udev become aware of new device
		udevadm settle
	fi

	# Check the partition label
	echo "Checking the endless-live label in ${device}" >&2
	label=$(e2label "${device}" 2>/dev/null) || return 1
	[ "${label}" = "endless-live" ] || return 1

	echo "endless-live label matched, mounting ${device}" >&2
	mkdir -p /run/eos-live
	mount "${device}" /run/eos-live
}

setup_ro_mount() {
    local path=$1

    # If the directory isn't a mount point, make a read only bind mount
    # on top of the directory and be done.
    if ! findmnt "$path" >/dev/null; then
        mount -o bind,ro "$path" "$path"
        return 0
    fi

    # In case of an automount, trigger the real mount so its options can
    # be inspected.
    ls "$path" >/dev/null

    # If it's already mounted read only, assume we're done.
    local mounted_options
    mounted_options=$(findmnt -n -f -d backward -o OPTIONS "$path")
    [[ $mounted_options =~ (,|^)ro(,|$) ]] && return 0

    # If there's an automount, adjust the mount options in the mount
    # unit so the read only option persists across mountings.
    local escaped_path automount_unit
    escaped_path=$(systemd-escape -p "$path")
    automount_unit="${escaped_path}.automount"
    if systemctl -q is-active "$automount_unit"; then
        # Add a drop-in to append ro to the mount unit's Options.
        # mount(8) will use the last rw or ro found in the options.
        local mount_unit mount_options new_mount_options
        mount_unit="${escaped_path}.mount"
        mount_options=$(systemctl show -P Options "$mount_unit")
        new_mount_options="${mount_options:+${mount_options},}ro"

        local dropin_dir="/run/systemd/system/${mount_unit}.d"
        local dropin_conf="${dropin_dir}/readonly.conf"
        mkdir -p "$dropin_dir"
        echo "Changing ${mount_unit} Options from \"${mount_options}\"" \
            "to \"${new_mount_options}\"" >&2
        cat > "$dropin_conf" <<EOF
[Mount]
Options=${new_mount_options}
EOF
        systemctl daemon-reload

        # Make sure the mount unit is started so the subsequent remount
        # isn't applied to the automount.
        systemctl start "$mount_unit"
    fi

    # Remount the current mount read only.
    mount -o remount,ro "$path"
}

setup_overlay() {
    local dir=$1
    [ -d "/$dir" ] || return
    # If the directory is a symlink, assume it's pointing to a location
    # covered by another top level overlay
    [ -L "/$dir" ] && return
    # Make sure there's not already an overlay mounted here to allow the
    # same path to be passed multiple times or the script to run
    # multiple times.
    findmnt -t overlay "/$dir" >/dev/null && return 0
    mkdir -p "/run/eos-live/$dir" "/run/eos-live/$dir-workdir"
    if ! mount -t overlay -o \
        "rw,upperdir=/run/eos-live/$dir,lowerdir=/$dir,workdir=/run/eos-live/$dir-workdir" \
        "eos-live-$dir" "/$dir"
    then
        # Overlayfs doesn't support using case insensitive filesystems
        # such as vfat and exfat as a lower directory. Change the mount
        # to read only so changes can't be made there.
        echo "Could not mount overlayfs on /$dir, trying to mount read only" >&2
        setup_ro_mount "/$dir"
    fi
}

# Use persistent storage to back overlayfses, if available
storage_partition=$(find_storage_partition)
if [ -b "${storage_partition}" ]; then
	echo "Found a device to back persistent storage: ${storage_partition}" >&2
	setup_and_mount_storage "${storage_partition}"
else
	echo "Could not find a device to back persistent storage" >&2
fi

# If we booted and systemd was unable to find or create a machine-id,
# it will create one at /run/machine-id and bind mount it at /etc/machine-id.
# But when we create the /etc overlay, we will lose that bind mount.
#
# Here we create the overlay upperdir in advance, pre-seeding systemd's
# boot-generated machine-id before we create the overlay. This way, we avoid
# any points during the transition where /etc/machine-id is missing.
mkdir -p /run/eos-live/etc
if [[ -s /run/machine-id ]]; then
  cp /run/machine-id /run/eos-live/etc/machine-id
fi

# For a live boot, we cannot overlay /sysroot directly: we need to read
# an xattr from the real (lower) directory, but if the upper dir exists
# (which it always does at the root of the mount!) then overlayfs
# returns xattrs from that.
overlay_dirs="boot etc srv sysroot/home sysroot/ostree var"
for dir in $overlay_dirs; do
    setup_overlay "$dir"
done
