#!/bin/bash
# cos_root_perm, cos_mounts and cos_overlay variables already processsed

#======================================
# Functions
#--------------------------------------

function getOverlayMountpoints {
    local mountpoints
    local path

    for path in "${rw_paths[@]}"; do
        if ! hasMountpoint "${path}" "${cos_mounts[@]}"; then
            mountpoints+="${path}:overlay "
        fi
    done
    echo "${mountpoints}"
}

function getStateMountpoints {
    local mountpoints=$1
    local state_mounts
    local path

    for path in "${state_paths[@]}"; do
        if ! hasMountpoint "${path}" "${mountpoints}"; then
            state_mounts+="${path} "
        fi
    done
    echo "${state_mounts}"
}

function hasMountpoint {
    local path=$1
    shift
    local mounts=("$@")
    local mount

    for mount in "${mounts[@]}"; do
        if [ "${path}" = "${mount#*:}" ]; then
            return 0
        fi
    done
    return 1
}

function parseOverlay {
    local overlay=$1

    case "${overlay}" in
        UUID=*) \
            overlay="block:/dev/disk/by-uuid/${overlay#UUID=}"
        ;;
        LABEL=*) \
            overlay="block:/dev/disk/by-label/${overlay#LABEL=}"
        ;;
    esac
    echo "${overlay}"
}

function parseMount {
    local mount=$1

    case "${mount}" in
        UUID=*) \
            mount="/dev/disk/by-uuid/${mount#UUID=}"
        ;;
        LABEL=*) \
            mount="/dev/disk/by-label/${mount#LABEL=}"
        ;;
    esac
    echo "${mount}"
}

function readLayoutConfig {
    local mount
    local mounts=()
    : "${MERGE:=true}"

    if [ "${DEBUG_RW}" = "true" ]; then
        cos_root_perm="rw"
    fi

    if [ -n "${VOLUMES}" ]; then
        for volume in ${VOLUMES}; do
            mounts+=("$(parseMount ${volume})")
        done
    fi

    if [ "${MERGE}" = "true" ]; then
        if [ -n "${cos_mounts}" ]; then
            for mount in ${cos_mounts}; do
                if ! hasMountpoint "${mount#*:}" "${mounts[@]}"; then
                    mounts+=("${mount}")
                fi
            done
        fi
    fi

    if [ -n "${OVERLAY}" ]; then
        cos_overlay=$(parseOverlay "${OVERLAY}")
    fi
    if [ ${#mounts[@]} -gt 0 ]; then
        cos_mounts=("${mounts[@]}")
    else
        cos_mounts=()
    fi

    state_paths=()
    state_bind="${PERSISTENT_STATE_BIND:-false}"
    state_target="${PERSISTENT_STATE_TARGET:-/usr/local/.state}"

    # An empty RW_PATHS is a valid value, default rw_paths are only
    # applied when RW_PATHS is unset.
    if [ -n "${RW_PATHS+x}" ]; then
        rw_paths=(${RW_PATHS})
    fi
    if [ -n "${PERSISTENT_STATE_PATHS}" ]; then
        state_paths=(${PERSISTENT_STATE_PATHS})
    fi
}

function getCOSMounts {
    local mount
    local mounts

    for mount in "${cos_mounts[@]}"; do
        mounts+="${mount#*:}:${mount%%:*} "
    done
    mounts+="$(getOverlayMountpoints)"
    echo -e "${mounts// /\\n}" | sort -
}

function mountOverlayBase {
    local fstab_line

    mkdir -p "${overlay_base}"
    if [ "${cos_overlay%%:*}" = "tmpfs" ]; then
        overlay_size="${cos_overlay#*:}"
        mount -t tmpfs -o "defaults,size=${overlay_size}" tmpfs "${overlay_base}"
        fstab_line="tmpfs ${overlay_base} tmpfs defaults,size=${overlay_size} 0 0\n"
    elif [ "${cos_overlay%%:*}" = "block" ]; then
        overlay_block="${cos_overlay#*:}"
        mount -t auto "${overlay_block}" "${overlay_base}"
        fstab_line="${overlay_block} ${overlay_base} auto defaults 0 0\n"
    fi
    echo "${fstab_line}"
}

function mountOverlay {
    local mount=$1
    local base=${2:-$overlay_base}
    local merged
    local upperdir
    local workdir
    local fstab_line

    mount="${mount#/}"
    merged="/sysroot/${mount}"
    if [ "${base##/run}" == "${base}"  ]; then
        base="/sysroot${base}"
    fi
    if ! mountpoint -q "${merged}"; then
        upperdir="${base}/${mount//\//-}.overlay/upper"
        workdir="${base}/${mount//\//-}.overlay/work"
        mkdir -p "${merged}" "${upperdir}" "${workdir}"
        if [ $? -ne 0 ]; then
            >&2 echo "failed creating one of '${merged}', '${upperdir}' or '${workdir}'"
            exit 1
        fi
        mount -t overlay overlay -o "defaults,lowerdir=${merged},upperdir=${upperdir},workdir=${workdir}" "${merged}"
        fstab_line="overlay /${mount} overlay defaults,lowerdir=/${mount},upperdir=${upperdir##/sysroot},workdir=${workdir##/sysroot}"
        required_mount=$(findmnt -fno TARGET --target "${base}")
        if [ -n "${required_mount}" ] && [ "${required_mount}" != "/" ]; then
            fstab_line+=",x-systemd.requires-mounts-for=${required_mount##/sysroot}"
        fi
        fstab_line+="\n"
    fi
    echo "${fstab_line}"
}

function mountState {
    local mount=$1
    local base
    local fstab_line
    local state_dir

    if [ "${state_bind}" = "true" ]; then
        mount="${mount#/}"
        base="/sysroot/${mount}"
        state_dir="/sysroot${state_target}/${mount//\//-}.bind"
        if ! mountpoint -q "${base}"; then
            mkdir -p "${base}" "${state_dir}"
            if [ $? -ne 0 ]; then
                >&2 echo "failed creating '${base}' or '${state_dir}'"
                exit 1
            fi
            rsync -aqAX "${base}/" "${state_dir}/"
            mount -o defaults,bind "${state_dir}" "${base}"
            fstab_line="${state_dir##/sysroot} /${mount} none defaults,bind 0 0\n"
        fi
    else
        fstab_line=$(mountOverlay "${mount}" "${state_target}")
    fi
    echo "${fstab_line}"
}

function mountPersistent {
    local mount=$1

    if [ -e "${mount#*:}" ] && ! findmnt -rno SOURCE "${mount#*:}" > /dev/null; then
        mount -t auto "${mount#*:}" "/sysroot${mount%%:*}"
    else
        echo "Warning: ${mount#*:} already mounted or device not found" >&2
    fi
    echo "${mount#*:} ${mount%%:*} auto defaults 0 0\n"
}

function waitForPath {
    local path=$1
    local count=0

    while [ ! -e "${path}" ]; do
        sleep 1
        udevadm settle
        (( count++ ))
        if [ "${count}" -ge "${timeout}" ]; then
            echo "Error: ${path} not found" >&2
            return 1
        fi
    done
    return 0
}

#======================================
# Mount the rootfs layout
#--------------------------------------

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

declare timeout=60
declare cos_mounts=${cos_mounts}
declare cos_overlay=${cos_overlay}
declare cos_root_perm=${cos_root_perm}
declare overlay_base="/run/overlay"
declare rw_paths=("/etc" "/root" "/home" "/opt" "/srv" "/usr/local" "/var")
declare etc_conf="/sysroot/etc/systemd/system/etc.mount.d"
declare cos_layout="/run/cos/cos-layout.env"
declare root_fstype=$(findmnt -rno FSTYPE /sysroot)
declare root=$(findmnt -rno SOURCE /sysroot)
declare base_part=$(findmnt -rno SOURCE /run/cos/root)
declare partname
declare fstab
declare state_paths
declare state_bind
declare state_target
declare mount

readLayoutConfig

[ -z "${cos_overlay}" ] && exit 0

# If sysroot is already an overlay do not prepare the rw overlay
if [ "${root_fstype}" != "overlay" ]; then
    if [ -f "${base_part}" ]; then
        fstab="${base_part} /run/initramfs/cos-state auto ${cos_root_perm} 0 0\n"
    fi
    fstab+="${root} / auto ${cos_root_perm} 0 0\n"
    fstab+=$(mountOverlayBase)
fi

mountpoints=($(getCOSMounts))

for mount in "${mountpoints[@]}"; do
    if [ "${mount#*:}" = "overlay" ]; then
        if [ "${root_fstype}" != "overlay" ]; then
            fstab+=$(mountOverlay "${mount%%:*}")
        fi
    else
        # wait for the device and exit on failure
        waitForPath "${mount#*:}" || exit 1
        # ensure filesystem check is not done twice for this device
        part_name=$(basename $(realpath "${mount#*:}"))
        [ -e "/tmp/elemental-fsck-${part_name}" ] || systemd-fsck "${mount#*:}"
        fstab+=$(mountPersistent "${mount}")
    fi
done

for mount in $(getStateMountpoints "${mountpoints[@]}"); do
    fstab+=$(mountState "${mount}")
done

echo -e "${fstab}" > /sysroot/etc/fstab

if [ ! -f "${etc_conf}/override.conf" ]; then
    mkdir -p "${etc_conf}"
    {
        echo "[Mount]"
        echo "LazyUnmount=true"
    } > "${etc_conf}/override.conf"
fi

exit 0
