#!/bin/sh

TMP_SELF_COPY=/var/run/cloned-overlay-backup

action=${1}
shift

has_overlay() {
    [ -d "/overlay/upper" ] || return 1
    [ "overlay" = "$(/bin/mount | awk '($3 ~ /^\/$/) && ($5 !~ /rootfs/) { print $5 }')" ] || return 1

    return 0
}

has_ext_overlay() {
    [ -d "/ext_overlay/upper" ] || return 1
    grep '^overlayfs:/overlay / ' /proc/mounts | grep -Fq 'upperdir=/ext_overlay/upper' || return 1

    return 0
}

backup() {
    if ! has_overlay; then
        echo "only supports squashfs firmware"
        exit 1
    fi
    if [ -z "$1" ]; then
        echo "input backup path is null"
        exit 1
    fi
    local BACKUP_PATH="$1"

    if echo "$BACKUP_PATH" | grep -q -e '^/overlay/upper' -e '^/ext_overlay/upper' ; then
        echo "can not backup to /overlay/upper, /ext_overlay/upper"
        exit 1
    fi

    if [ ! -d "${BACKUP_PATH}" ] && ! mkdir -p "${BACKUP_PATH}" ; then
        echo "backup path does not exist and can not be create"
        exit 1
    fi

    local realpath="$(cd "${BACKUP_PATH}"; pwd -P)"
    if [ -z "$realpath" ]; then
        echo "cannot get absolute path of ${BACKUP_PATH}"
        exit 1
    fi

    local mountpoint=$(findmnt -T $realpath -o TARGET | sed -n 2p)
    # while read -r; do
    #     if [[ "x$realpath" == "x$REPLY" || "x${realpath#$REPLY/}" != "x$realpath" ]]; then
    #         mountpoint="$REPLY"
    #         break
    #     fi
    # done < <(
    #     cat /proc/mounts | grep -v '^overlay ' | awk 'NR>1 {print $2}' | grep -v '^/$' | \
    #         sort -u | \
    #         while read -r; do printf "%b\n" "$REPLY" ; done | \
    #         awk '{print length, $0}' | sort -nr | cut -d' ' -f2-
    # )

    if [ "/" = "$mountpoint" ]; then
        echo "can not backup to /"
        exit 1
    else
        echo "found mount point $mountpoint"
    fi

    local filename_suffix=
    local tar_extra_args=
    if has_ext_overlay; then
        tar_extra_args="$tar_extra_args ext_overlay/upper"
        filename_suffix="${filename_suffix}.s"
    fi

    local hostname=$(cat /proc/sys/kernel/hostname)
    local fwver=$(. /etc/openwrt_release; echo $DISTRIB_ID-$DISTRIB_RELEASE)

    local date=$(date +%Y-%m%d-%H%M)
    local backup_name="backup_overlay_${hostname}_${fwver}_${date}${filename_suffix}.overlay.tar.gz"
    local backup_full_path="$BACKUP_PATH/$backup_name"
    echo "writing backup to $backup_full_path"
    if tar -C / -cz overlay/upper $tar_extra_args > "$backup_full_path" ; then
        sync "$BACKUP_PATH"
        echo "backup success"
        return 0
    else
        rm -f "$backup_full_path"
        echo "backup failed"
        exit 1
    fi
}

patch_sandbox_device() {
    local overlay="$1"
    local ucidir="$overlay/upper/etc/config"
    local uci_section=$(uci -c "$ucidir" -q show fstab | grep '^fstab\.@mount\[[0-9]*\]\.target='"'/overlay'\$" | head -1 | grep -o '^fstab\.@mount\[[0-9]*\]')
    if [ -n "$uci_section" ]; then
        if [ "x1" = "x$(uci -c "$ucidir" get "$uci_section.enabled")" ]; then
            uci -c "$ucidir" -q delete "$uci_section.uuid"
            uci -c "$ucidir" -q delete "$uci_section.label"
            uci -c "$ucidir" -q delete "$uci_section.device"
            if [ -n "$SANDBOX_UUID" ]; then
                uci -c "$ucidir" set "$uci_section.uuid=$SANDBOX_UUID"
            elif [ -n "$SANDBOX_LABEL" ]; then
                uci -c "$ucidir" set "$uci_section.label=$SANDBOX_LABEL"
            else
                uci -c "$ucidir" set "$uci_section.device=$SANDBOX_DEVICE"
            fi
            uci -c "$ucidir" commit fstab
            echo "found and patched $overlay"
            return 0
        fi
    fi
    return 1
}

restore() {
    if ! has_overlay; then
        echo "only supports squashfs firmware"
        exit 1
    fi
    if [ -z "$1" ]; then
        echo "input backup path is null"
        exit 1
    fi
    local BACKUP_PATH_FILE="$1"

    if [ ! -f "${BACKUP_PATH_FILE}" ]; then
        echo "invalid backup file, can not restore"
        exit 1
    fi

    # prevent uci cache
    rm -rf /var/run/uci

    SANDBOX_UUID=
    SANDBOX_LABEL=
    SANDBOX_DEVICE=
    local tar_extra_args=overlay/upper
    if has_ext_overlay; then
        tar_extra_args=
        if echo "${BACKUP_PATH_FILE}" | grep -q '\.s\.overlay\.tar\.gz$' ; then
            local uuid label device line
            local uci_section=$(uci -c /overlay/upper/etc/config show fstab | grep '^fstab\.@mount\[[0-9]*\]\.target='"'/overlay'\$" | head -1 | grep -o '^fstab\.@mount\[[0-9]*\]')
            if [ -z "$uci_section" ]; then
                echo "failed to find sandbox config, there is debug info:"
                uci -c /overlay/upper/etc/config show fstab | grep '^fstab\.[^.]*\.target='"'/overlay'\$"
                exit 1
            fi
            while read line; do
                export -n "$line"
            done <<EOF
                $(uci -c /overlay/upper/etc/config show "$uci_section" | sed -e '/^fstab\.[^\.]*=/d' -e 's/^fstab\.[^\.]*\.//g' | grep -e '^uuid=' -e '^label=' -e '^device=' | sed "s/'//g")
EOF
            SANDBOX_UUID=$uuid
            SANDBOX_LABEL=$label
            SANDBOX_DEVICE=$device
        fi
    fi

    sync /
    echo "restoring from ${BACKUP_PATH_FILE}"
    if tar -C / -xz $tar_extra_args < "${BACKUP_PATH_FILE}" ; then
        if [ -n "$SANDBOX_UUID" -o -n "$SANDBOX_LABEL" -o -n "$SANDBOX_DEVICE" ]; then
            echo "patch sandbox device ${SANDBOX_UUID}${SANDBOX_LABEL}${SANDBOX_DEVICE}"
            patch_sandbox_device /overlay && patch_sandbox_device /ext_overlay
        fi
        sync /overlay /ext_overlay
        echo "restore success"
        echo "schedule to restart after 5 seconds!"
        /etc/init.d/tasks task_add reboot 'reboot -d 5'
        return 0
    else
        echo "restore failed"
        exit 1
    fi
}

supports_overlay_backup() {
    has_overlay || return 1
    echo "overlay"
    has_ext_overlay && echo "ext_overlay"
    return 0
}

filter_backups() {
    if has_ext_overlay; then
        cat
    else
        # do not support restoring sandbox backup on non sandbox env
        grep -v '\.s\.overlay\.tar\.gz$'
    fi
}

get_backup_file_list() {
    if [ -n "$1" ]; then
        ( cd "$1" && ls backup_overlay_*.overlay.tar.gz | filter_backups )
        return 0
    else
        echo "input backup path is null"
        exit 1
    fi
}

usage() {
    echo "usage: overlay-backup sub-command [arguments...]"
    echo "where sub-command is one of:"
    echo "      backup [dir]                    Backup all installed package(s) to [directory]"
    echo "      restore [dir]                   Restore package(s) by [directory]"
    echo "      supports_overlay_backup         check system supports overlay backup"
    echo "      get_backup_file_list [dir]      get local available backup file list in [dir]"
}

case $action in
    "supports_overlay_backup")
        supports_overlay_backup
    ;;
    "backup")
        backup "$@"
    ;;
    "restore")
        if [ "$0" = "$TMP_SELF_COPY" ]; then
            restore "$@"
        else
            echo "copy self $0 to $TMP_SELF_COPY when restore"
            cp -af "$0" "$TMP_SELF_COPY"
            exec "$TMP_SELF_COPY" restore "$@"
        fi
    ;;
    "get_available_backup_file_list")
        get_backup_file_list "$@"
    ;;
    *)
        usage
    ;;
esac
