#!/system/bin/sh

set -e

echo "System Image Upgrader for Ubuntu Touch"
echo "Preparing command file"
if [ ! -e "$1" ]; then
    echo "Command file doesn't exist: $1"
    exit 1
fi
mv $1 $1.applying
COMMAND_FILE=$1.applying

REMOVE_LIST="$COMMAND_FILE"

# Used as a security check to see if we would change the password
DATA_FORMAT=0

# System Mountpoint
SYSTEM_MOUNTPOINT=/cache/system

echo "Starting image upgrader"

# Functions
check_filesystem() {
    # $1 => image to check (partition or device img)
    if [ ! -e $1 ]; then
        echo "Partition/image not found: $1"
        return 1
    fi

    # It's fine for e2fsck to return something different than 0
    set +e
    e2fsck -yf $1
    ret=$?
    # From e2fsck man page:
    # 0 - No errors
    # 1 - File system errors corrected
    if [ $ret -ne 0 ] && [ $ret -ne 1 ]; then
        echo "e2fsck is unable to fix partition/image $1, aborting (return code $ret)"
        exit 1
    fi
    set -e

    # Workaround for resize2fs in case of clock inconsistency
    # tune2fs -l formats using ctime, e. g. "Thu Jan  1 07:17:03 1970"
    # which cannot be parsed by toybox's date, so below is the ugly conversion
    last_mount_time_fmt=$(tune2fs -l "$1" 2>/dev/null | awk -F': +' '
        /Last mount time:/ {
            if ($2 != "n/a" && $2 != "") {
                split($2, parts, " ")
                # parts: 1:DayOfWeek, 2:Month, 3:Day, 4:Time, 5:Year
                m["Jan"]="01"; m["Feb"]="02"; m["Mar"]="03"; m["Apr"]="04"; m["May"]="05"; m["Jun"]="06"
                m["Jul"]="07"; m["Aug"]="08"; m["Sep"]="09"; m["Oct"]="10"; m["Nov"]="11"; m["Dec"]="12"
                gsub(/:/, "", parts[4])
                printf "%04d%s%02d%s", parts[5], m[parts[2]], parts[3], parts[4]
            }
        }')
    if [ -n "$last_mount_time_fmt" ]; then
        tune2fs -T "$last_mount_time_fmt" "$1" >/dev/null 2>&1 || true
    fi
}

verify_signature() {
    # $1 => validation keyring name
    # $2 => path to validate

    if [ ! -e $2 ]; then
        echo "File doesn't exist: $2"
        return 1
    fi

    # Check against the blacklist
    if [ -e /tmp/system-image/blacklist/pubring.gpg ]; then
        export GNUPGHOME=/tmp/system-image/blacklist/
        if gpg --ignore-time-conflict --verify $2 >/dev/null 2>&1; then
            echo "File signed by a blacklisted key: $2"
            return 1
        fi
    fi

    # Check against the keyring
    export GNUPGHOME=/tmp/system-image/$1/
    if [ ! -e "$GNUPGHOME" ]; then
        echo "Keyring doesn't exist: $1"
        return 1
    fi

    if gpg --ignore-time-conflict --verify $2 >/dev/null 2>&1; then
        return 0
    fi

    return 1
}

install_keyring() {
    # $1 => full path to tarball
    # $2 => full path to signature

    # Some basic checks
    if [ ! -e "$1" ] || [ ! -e "$2" ]; then
        echo "Missing keyring files: $1 => $2"
        return 1
    fi

    # Unpacking
    TMPDIR=$(mktemp -d -p /tmp/system-image/ tmp.XXXXXXXXXX)
    cd $TMPDIR
    busybox xzcat $1 | bsdtar --numeric-owner -Ppxf -
    if [ ! -e keyring.json ] || [ ! -e keyring.gpg ]; then
        rm -Rf $TMPDIR
        echo "Invalid keyring: $1"
        return 1
    fi

    # Extract the expiry
    keyring_expiry=$(grep "^    \"expiry\": " keyring.json | cut -d: -f2 | sed -e "s/[ \",]//g")
    if [ -n "$keyring_expiry" ] && [ "$keyring_expiry" -lt "$(date +%s)" ]; then
        rm -Rf $TMPDIR
        echo "Keyring expired: $1"
        return 1
    fi

    # Extract the keyring type
    keyring_type=$(grep "^    \"type\": " keyring.json | cut -d: -f2 | sed -e "s/[, \"]//g")
    if [ -z "$keyring_type" ]; then
        rm -Rf $TMPDIR
        echo "Missing keyring type: $1"
        return 1
    fi

    if [ -e /tmp/system-image/$keyring_type ]; then
        rm -Rf $TMPDIR
        echo "Keyring already loaded: $1"
        return 1
    fi

    signer="unknown"
    case "$keyring_type" in
        archive-master)
            signer=""
        ;;

        image-master)
            signer="archive-master"
        ;;

        image-signing|blacklist)
            signer="image-master"
        ;;

        device-signing)
            signer="image-signing"
        ;;
    esac

    if [ -n "$signer" ] && ! verify_signature $signer $2; then
        rm -Rf $TMPDIR
        echo "Invalid signature: $1"
        return 1
    fi

    mkdir /tmp/system-image/$keyring_type
    chmod 700 /tmp/system-image/$keyring_type
    mv $TMPDIR/keyring.gpg /tmp/system-image/$keyring_type/pubring.gpg
    chmod 600 /tmp/system-image/$keyring_type/pubring.gpg
    chown 0:0 /tmp/system-image/$keyring_type/pubring.gpg
    rm -Rf $TMPDIR
    return 0
}

property_write() {
    prop="$1"
    prop_value="$2"
    prop_dir="/data/android-data/property"
    # everything is wiped after a format, let's get a skeleton going
    mkdir -p "$prop_dir"
    chown 0:0 "$prop_dir"
    chmod 700 "$prop_dir"
    echo -n "$prop_value" > "$prop_dir/$prop"
    # properties won't be read if they aren't ro root
    chown 0:0 "$prop_dir/$prop"
    chmod 600 "$prop_dir/$prop"
}

adb_onlock() {
    if [ "$DATA_FORMAT" -eq 0 ]; then
        return 1
    fi

    flag="/data/.adb_onlock"
    # if the param != "true" we just delete the flag
    case $1 in
        true)
            touch "$flag"
        ;;

        false)
            rm -f "$flag"
        ;;

        *)
            echo "Unkown parameter $1, disabling"
            rm -f "$flag"
        ;;
    esac
}

set_password() {
    if [ "$DATA_FORMAT" -eq 0 ]; then
        return 1
    fi
    user="phablet"
    password="$1"
    if [ -z "$password" ]; then
        return 1
    fi
    path=/bin:/usr/bin:/sbin:/usr/sbin
    PATH=$path chroot "$SYSTEM_MOUNTPOINT" /bin/sh -c "echo -n "$user:$password" | chpasswd"
    return 0
}

unset_password() {
    if [ "$DATA_FORMAT" -eq 0 ]; then
        return 1
    fi
    # Needs implementation
}

usb_enable() {
    prop_dir="/data/android-data/property"
    prop="persist.sys.usb.config"
    prop_val="$1"
    # Property value ordering is important here
    grep -q -s mtp "$prop_dir/$prop" && [ "$prop_val" == "adb" ] && prop_val="mtp,adb"
    grep -q -s adb "$prop_dir/$prop" && [ "$prop_val" == "mtp" ] && prop_val="mtp,adb"
    property_write "$prop" "$prop_val"
}

usb_disable() {
    prop_dir="/data/android-data/property"
    prop="persist.sys.usb.config"
    prop_val="$1"
    remain_prop=""
    # Property value ordering is important here
    grep -q -s mtp "$prop_dir/$prop" && [ "$prop_val" == "adb" ] && remain_prop="mtp"
    grep -q -s adb "$prop_dir/$prop" && [ "$prop_val" == "mtp" ] && remain_prop="adb"
    # we should not allow empty properties for the usb config
    [ "$remain_prop" == "" ] && remain_prop="adb"
    property_write "$prop" "$remain_prop"
}

factory_wipe() {
    # only set this flag if coming from a data wipe
    if [ "$DATA_FORMAT" -eq 0 ]; then
        return 1
    fi

    flag="/data/.factory_wipe"
    # if the param != "true" we just delete the flag
    case $1 in
        true)
            touch "$flag"
        ;;

        false)
            rm -f "$flag"
        ;;

        *)
            echo "Unkown parameter $1, disabling"
            rm -f "$flag"
        ;;
    esac
}

mount_data() {
    # Helper function to mount /data, handling LVM if present
    # Check if data is on LVM
    vgchange -ay ubports 2>/dev/null || true
    if [ -e /dev/ubports/userdata ]; then
        echo "Mounting /data from LVM (ubports/userdata)"
        mount /dev/ubports/userdata /data
    else
        echo "Mounting /data from fstab"
        mount /data
    fi
}

migrate_device_to_lvm() {
    # $1 => "format" or "mount" - indicates if this is a fresh install or upgrade
    # $2 => system_device - the raw partition device path
    # $3 => required_size_mb - required size in MB
    # $4 => current_size_mb - current raw partition size in MB
    operation="$1"
    system_device="$2"
    required_size_mb="$3"
    current_size_mb="$4"

    echo "Migrating device to LVM..."

    # Migrate userdata to LVM if not already done
    if [ ! -e /dev/ubports/userdata ]; then
        echo "Migrating userdata partition to LVM"
        userdata_partition=$(awk '$1 !~ /^#/ && $2=="/data" {print $1; exit}' /etc/fstab)
        if [ -z "$userdata_partition" ]; then
            echo "Error: Could not find userdata partition in fstab"
            exit 1
        fi
        # Calculate reserve: space we need for system + some buffer
        userdata_reserve=$(getprop ro.systemimage.lvm_on_userdata_reserve_mb)
        if [ -z "$userdata_reserve" ]; then
            # Reserve at least the difference between required and current system size + 1MB for safety
            space_needed=$((required_size_mb - current_size_mb + 1))
            [ "$space_needed" -lt 0 ] && space_needed=0
            userdata_reserve=$space_needed
            echo "Calculated userdata reserve: ${userdata_reserve}MB (${required_size_mb}MB needed - ${current_size_mb}MB current + 1MB buffer)"
        fi
        /system/bin/lvm-fs-migrator --device="$userdata_partition" --vg-name=ubports --lv-name=userdata --reserve="$userdata_reserve"
    else
        echo "Userdata already on LVM"
    fi

    # Now setup system with LVM
    # Check if we're using super partition
    if [ -e /dev/block/by-name/super ]; then
        echo "Setting up super partition with LVM"
        super_partition="/dev/block/by-name/super"

        # Use temporary VG name to avoid conflict with existing 'ubports' VG from userdata
        temp_vg_name="ubports_super"

        # Convert Android dynamic partitions to LVM structure
        /system/bin/lvm-super-migrator --device="$super_partition" --vg-name="$temp_vg_name"

        # Merge with existing ubports VG if it exists
        if vgs ubports >/dev/null 2>&1; then
            echo "Merging super VG into ubports VG"
            vgchange -an "$temp_vg_name"
            vgmerge ubports "$temp_vg_name"
        else
            echo "Renaming VG from $temp_vg_name to ubports"
            vgrename "$temp_vg_name" ubports
        fi

        # Rename system or system_a to rootfs
        [ -e /dev/ubports/system ] && lvrename ubports system rootfs
        [ -e /dev/ubports/system_a ] && lvrename ubports system_a rootfs
    else
        # Legacy system partition
        if [ "$operation" == "format" ]; then
            echo "Setting up system partition with LVM (fresh install)"
            # Fresh install - just create LVM structure, no need to migrate data
            # Create physical volume
            pvcreate "$system_device"
            # Create volume group (may already exist from userdata)
            vgcreate -s 1M ubports "$system_device" 2>/dev/null || vgextend ubports "$system_device"
            # Create logical volume with required size
            lvcreate -L "${required_size_mb}M" -n rootfs ubports
        else
            echo "Migrating system partition to LVM (delta upgrade)"
            # Delta upgrade - preserve existing data

            # Use temporary VG name to avoid conflict with existing 'ubports' VG from userdata
            temp_vg_name="ubports_system"

            /system/bin/lvm-fs-migrator --device="$system_device" --vg-name="$temp_vg_name" --lv-name=rootfs

            # Merge with existing ubports VG if it exists
            if vgs ubports >/dev/null 2>&1; then
                echo "Merging system VG into ubports VG"
                vgchange -an "$temp_vg_name"
                vgmerge ubports "$temp_vg_name"
            else
                echo "Renaming VG from $temp_vg_name to ubports"
                vgrename "$temp_vg_name" ubports
            fi
        fi
    fi

    # Resize the rootfs logical volume to the required size
    if [ -e /dev/ubports/rootfs ]; then
        echo "Resizing filesystem on rootfs to ${required_size_mb}MB"
        lvextend -L "${required_size_mb}M" /dev/ubports/rootfs
        check_filesystem /dev/ubports/rootfs
        resize2fs /dev/ubports/rootfs
    else
        echo "Error: LVM rootfs logical volume not found after migration"
        exit 1
    fi

    echo "LVM migration complete"
    return 0
}

resize_system_partition_in_super() {
    # $1 => system device path (e.g. /dev/block/mapper/system_a)
    # $2 => required size in MB
    # $3 => operation (format|mount)
    system_device="$1"
    required_size_mb="$2"
    operation="$3"
    super_device="/dev/block/by-name/super"

    if [ ! -e "$super_device" ]; then
        echo "Error: super partition $super_device not found"
        return 1
    fi

    slot_suffix=$(getprop ro.boot.slot_suffix) # May be empty.
    partition_name="system${slot_suffix}"

    echo "Resizing logical partition $partition_name to ${required_size_mb}MB via lpresize"
    lpresize "$super_device" "$partition_name" "${required_size_mb}M" || return 1

    if [ "$operation" == "mount" ]; then
        echo "Resizing filesystem on $system_device"
        # check_filesystem has already run as part of mount operation handling
        resize2fs "$system_device" || return 1
    fi

    return 0
}

remove_logical_partitions_in_super() {
    # $1 => comma-separated partition names (e.g. product,system_ext)
    # $2 => slot suffix (e.g. _a)
    partition_names="$1"
    slot_suffix="$2"
    super_device="/dev/block/by-name/super"

    if [ ! -e "$super_device" ]; then
        echo "Error: super partition $super_device not found"
        return 1
    fi

    (
        IFS=","
        set -f
        for partition_name in $partition_names; do
            echo "Removing logical partition $partition_name via lpremove"
            lpremove "$super_device" "$partition_name" --slot="$slot_suffix" || return 1
        done
    )

    return $?
}

check_and_enlarge_filesystem() {
    # $1 => system_device
    system_device="$1"

    fs_block_count=$(tune2fs -l "$system_device" 2>/dev/null | awk '/Block count:/ {print $3}')
    fs_block_size=$(tune2fs -l "$system_device" 2>/dev/null | awk '/Block size:/ {print $3}')

    device_size_bytes=$(blockdev --getsize64 "$system_device" 2>/dev/null || echo "0")

    if [ -n "$fs_block_count" ] && [ -n "$fs_block_size" ] && [ "$device_size_bytes" -gt 0 ]; then
        fs_size_bytes=$(awk "BEGIN {printf \"%.0f\", $fs_block_count * $fs_block_size}")

        # If filesystem size is smaller than device size by more than 1MB
        diff_mb=$(awk "BEGIN {print int(($device_size_bytes - $fs_size_bytes) / 1048576)}")
        if [ "$diff_mb" -gt 0 ]; then
            echo "Filesystem size is smaller than device size, running resize2fs"
            # check_filesystem has already run as part of mount operation handling
            resize2fs "$system_device"
        else
            echo "Filesystem size matches device size"
        fi
    fi
}

ensure_system_partition_size() {
    # $1 => "format" or "mount" - indicates if this is a fresh install or upgrade
    operation="$1"

    # Get required system partition size from property (in MB)
    required_size_mb=$(getprop ro.systemimage.system_partition_size)
    if [ -z "$required_size_mb" ]; then
        # No size requirement, skip checks
        return 0
    fi

    echo "Required system partition size: ${required_size_mb}MB"

    # First, check if LVM is already set up
    vgchange -ay ubports 2>/dev/null || true
    if [ -e /dev/ubports/rootfs ]; then
        echo "System already on LVM: /dev/ubports/rootfs"
        system_device="/dev/ubports/rootfs"
        current_size_bytes=$(blockdev --getsize64 "$system_device" 2>/dev/null || echo "0")
        current_size_mb=$(awk "BEGIN {printf \"%.0f\", $current_size_bytes / 1048576}")
        echo "Current LVM system partition size: ${current_size_mb}MB"

        # Check if current size is sufficient
        if [ "$current_size_mb" -ge "$required_size_mb" ]; then
            echo "System partition size is sufficient"
            if [ "$operation" == "mount" ]; then
                check_and_enlarge_filesystem "/dev/ubports/rootfs"
            fi
            return 0
        fi

        # Need to expand LVM logical volume
        echo "Expanding rootfs logical volume from ${current_size_mb}MB to ${required_size_mb}MB"
        lvextend -L "${required_size_mb}M" /dev/ubports/rootfs

        # Resize filesystem if this is a delta upgrade (mount operation)
        if [ "$operation" == "mount" ]; then
            echo "Resizing filesystem on rootfs"
            check_filesystem /dev/ubports/rootfs
            resize2fs /dev/ubports/rootfs
        fi

        echo "System partition size check complete"
        return 0
    fi

    # LVM not set up yet - check raw partition size
    system_device=$(awk '$1 !~ /^#/ && $2 ~ /^((\/mnt)*)?\/(system|system_root)$/{print $1; exit}' /etc/fstab)
    if [ -z "$system_device" ]; then
        system_device="$SYSTEM_PARTITION"
    fi

    if [ ! -e "$system_device" ]; then
        echo "Error: system partition does not exist: $system_device"
        exit 1
    fi

    current_size_bytes=$(blockdev --getsize64 "$system_device" 2>/dev/null || echo "0")
    # Can't use shell arithmetic due to 32-bit integer overflow in toybox shell
    current_size_mb=$(awk "BEGIN {print int($current_size_bytes / 1048576)}")
    echo "Current raw system partition size: ${current_size_mb}MB"

    # Check if current size is sufficient - no LVM needed
    if [ "$current_size_mb" -ge "$required_size_mb" ]; then
        echo "System partition size is sufficient, no LVM needed"
        if [ "$operation" == "mount" ]; then
            check_and_enlarge_filesystem "$system_device"
        fi
        return 0
    fi

    # Partition is too small - need to expand it
    echo "System partition is too small (${current_size_mb}MB < ${required_size_mb}MB)"

    if abm is-multiboot; then
        echo "Not expanding system partition due to detecting ABM multi-boot"
        return 0
    fi

    # Check if LVM expansion is enabled
    use_lvm=$(getprop ro.systemimage.use_lvm)
    if [ "$use_lvm" = "true" ] || [ "$use_lvm" = "1" ]; then
        echo "Using LVM to expand system partition"
        if ! migrate_device_to_lvm "$operation" "$system_device" "$required_size_mb" "$current_size_mb"; then
            echo "Error: failed to migrate device partitions to LVM"
            exit 1
        fi
    else
        remove_logical_partitions_in_super "$(getprop ro.systemimage.remove_dynparts)"
        remove_logical_partitions_in_super "$(getprop ro.systemimage.remove_dynparts_slot_a)" "_a"
        remove_logical_partitions_in_super "$(getprop ro.systemimage.remove_dynparts_slot_b)" "_b"

        if ! resize_system_partition_in_super "$system_device" "$required_size_mb" "$operation"; then
            echo "Error: failed to resize logical system partition in super"
            exit 1
        fi
    fi

    echo "System partition expansion complete"
    return 0
}

# Initialize GPG
rm -Rf /tmp/system-image
mkdir -p /tmp/system-image
if [ -e /etc/system-image/archive-master.tar.xz ]; then
    echo "Loading keyring: archive-master.tar.xz"
    install_keyring /etc/system-image/archive-master.tar.xz /etc/system-image/archive-master.tar.xz.asc
fi

# Check the kernel command line to see whether Ubuntu should be installed to a partition
# or in a file that is loop mounted.
#if grep -q systempart= /proc/cmdline; then
#    USE_SYSTEM_PARTITION=1
#else
#    USE_SYSTEM_PARTITION=0
#fi

# Force usage of system partition for now, the existance of Android 9.0 devices
# with a too small system partition is questionable at this point.
USE_SYSTEM_PARTITION=1

# However, do not use the block device name in the kernel command line, as that is not consistently
# named in booting normal and recovery modes. Expect fstab to have a system mountpoint or use a fallback.
if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then
    SYSTEM_PARTITION=$(grep "^[^#].*\(/mnt\)*/system\(_root\)* " /etc/fstab | cut -f 1 -d\ )
    #Fall back to emmc@android if there's no system in fstab
    if [ "$SYSTEM_PARTITION" == "" ]; then
        SYSTEM_PARTITION="emmc@android"
    fi
fi

# If we are not using the system partition, ensure that /data is mounted
if [ "$USE_SYSTEM_PARTITION" -eq 0 ]; then
    mount_data
fi

# Process the command file
FULL_IMAGE=0
echo "Processing command file"
while read line
do
    set -- $line
    case "$1" in
        format)
            echo "Formatting: $2"
            case "$2" in
                system)
                    FULL_IMAGE=1
                    # Cleanup files left from halium-install
                    rm -f /data/system.img
                    rm -f /data/android-rootfs.img
                    rm -f /data/rootfs.img
                    # Delete ubuntu.img if present. Either its a leftover, or we recreate it
                    rm -f /data/ubuntu.img
                    if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then
                        echo "system partition: $SYSTEM_PARTITION"
                        umount /system_root || true
                        umount /mnt/system || true
                        # Ensure system partition size before formatting (full upgrade path)
                        ensure_system_partition_size "format"
                        # If LVM migration occurred, switch to the logical volume before formatting
                        if [ -e /dev/ubports/rootfs ]; then
                            SYSTEM_PARTITION=/dev/ubports/rootfs
                        fi
                        mkfs.ext4 $SYSTEM_PARTITION
                    else
                        # We need to use legacy image name here, halium-boot needs this to decide file layout
                        dd if=/dev/zero of=/data/ubuntu.img seek=3M bs=1024 count=0
                        mkfs.ext2 -F /data/ubuntu.img
                    fi
                ;;

                data)
                    if [ "$USE_SYSTEM_PARTITION" -eq 1 ]; then
                        mount_data || true
                    fi

                    for entry in /data/* /data/.writable_image /data/.factory_wipe /data/.fscrypt; do
                        if [ "$USE_SYSTEM_PARTITION" -eq 0 ]; then
                            if [[ ( "$entry" == "/data/ubuntu.img" ) || ( "$entry" == "/data/rootfs.img" ) || ( "$entry" == "/data/system.img" ) || ( "$entry" == "/data/android-rootfs.img" ) ]]; then
                                    continue
                            fi
                        fi

                        # Some devices use /data as /cache, so avoid removing
                        # files that are essential to flashing and upgrading
                        if [ "$entry" == "/data/cache" ]; then
                            continue
                        fi

                        # If this is android-data, inspect closer as we need the
                        # cache "partition" to stay functioning during use of the
                        # system-image-upgrader script. It is bind mounted
                        # and leads to premature cancellation otherwise.
                        if [ "$entry" == "/data/android-data" ]; then
                            for android_data_entry in /data/android-data/*; do
                                if [ "$android_data_entry" != "/data/android-data/cache" ]; then
                                    rm -Rf $android_data_entry
                                fi
                            done
                        else
                            rm -Rf $entry
                        fi
                    done
                    # mtp is always enabled by default
                    usb_enable mtp
                    DATA_FORMAT=1

                    if [ "$USE_SYSTEM_PARTITION" -eq 1 ]; then
                        umount /data
                    fi
                ;;

                *)
                    echo "Unknown format target: $2"
                ;;
            esac
        ;;

        enable)
            echo "Enabling: $2"
            case "$2" in
                developer_mode)
                    usb_enable adb
                ;;

                mtp)
                    usb_enable mtp
                ;;

                default_password)
                    set_password $3
                ;;

                adb_onlock)
                    adb_onlock true
                ;;

                factory_wipe)
                    factory_wipe true
                ;;

                *)
                    echo "Unknown enable target: $2"
                ;;
            esac
        ;;

        disable)
            echo "Disabling: $2"
            case "$2" in
                developer_mode)
                    usb_disable adb
                ;;

                mtp)
                    usb_disable mtp
                ;;

                default_password)
                    unset_password
                ;;

                adb_onlock)
                    adb_onlock false
                ;;

                factory_wipe)
                    factory_wipe false
                ;;

                *)
                    echo "Unknown disable target: $2"
                ;;
            esac
        ;;

        load_keyring)
            if [ ! -e "/cache/recovery/$2" ] || [ ! -e "/cache/recovery/$3" ]; then
                echo "Skipping missing file: $2"
                continue
            fi
            REMOVE_LIST="$REMOVE_LIST /cache/recovery/$2 /cache/recovery/$3"

            echo "Loading keyring: $2"
            install_keyring /cache/recovery/$2 /cache/recovery/$3

            if [ -e /tmp/system-image/image-master/pubring.gpg ] && \
               [ ! -e /tmp/system-image/blacklist/pubring.gpg ] && \
               [ -e /data/system-data/var/lib/system-image/blacklist.tar.xz ] && \
               [ -e /data/system-data/var/lib/system-image/blacklist.tar.xz.asc ]; then
                echo "Loading blacklist keyring"
                install_keyring /data/system-data/var/lib/system-image/blacklist.tar.xz /data/system-data/var/lib/system-image/blacklist.tar.xz.asc
            fi
        ;;

        mount)
            case "$2" in
                system)
                    mkdir -p "$SYSTEM_MOUNTPOINT"
                    if [ "$USE_SYSTEM_PARTITION" -eq 1 ];then
                        umount $SYSTEM_PARTITION || true
                        umount $SYSTEM_MOUNTPOINT || true
                        umount /system_root || true
                        umount /mnt/system || true
                        check_filesystem $SYSTEM_PARTITION
                        # Ensure system partition size before mounting (delta upgrade path)
                        ensure_system_partition_size "mount"
                        # If LVM migration occurred, switch to the logical volume before mounting
                        if [ -e /dev/ubports/rootfs ]; then
                            SYSTEM_PARTITION=/dev/ubports/rootfs
                        fi
                        mount $SYSTEM_PARTITION "$SYSTEM_MOUNTPOINT"
                    else
                        check_filesystem /data/ubuntu.img
                        mount -o loop /data/ubuntu.img "$SYSTEM_MOUNTPOINT/"
                    fi
                ;;

                *)
                    echo "Unknown mount target: $2"
                ;;
            esac
        ;;

        unmount)
            case "$2" in
                system)
                    umount "$SYSTEM_MOUNTPOINT"
                    rmdir "$SYSTEM_MOUNTPOINT"
                ;;

                *)
                    echo "Unknown mount target: $2"
                ;;
            esac
        ;;

        update)
            if [ ! -e "/cache/recovery/$2" ] || [ ! -e "/cache/recovery/$3" ]; then
                echo "Skipping missing file: $2"
                continue
            fi

            REMOVE_LIST="$REMOVE_LIST /cache/recovery/$3"

            if ! verify_signature device-signing /cache/recovery/$3 && \
               ! verify_signature image-signing /cache/recovery/$3; then
                echo "Invalid signature"
                exit 1
            fi

            echo "Applying update: $2"
            cd /cache
            rm -Rf partitions
            rm -Rf data  || true

            # Start by removing any file listed in "removed"
            if [ "$FULL_IMAGE" != "1" ]; then
                busybox xzcat recovery/$2 | bsdtar --numeric-owner -pxf - removed >/dev/null 2>&1 || true
                if [ -e removed ]; then
                    while read file; do
                        rm -Rf $file
                    done < removed
                fi
                rm -f removed
            fi

            # Unpack everything else on top of the system partition
            busybox xzcat recovery/$2 | bsdtar --numeric-owner -Ppxf -
            rm -f removed

            # WORKAROUND: fix erroneous vendor symlink
            if [ -h "$SYSTEM_MOUNTPOINT/vendor" ]; then
                VENDOR_SYMLINK_PATH=$(readlink $SYSTEM_MOUNTPOINT/vendor)
                if [ "$VENDOR_SYMLINK_PATH" == "/android/system/vendor" ]; then
                    rm $SYSTEM_MOUNTPOINT/vendor
                    ln -s /android/vendor $SYSTEM_MOUNTPOINT/vendor
                fi
            fi

            # Move things to data
            if [ -d data ]; then
                mv data/* /data/ || true
                rm -Rf data || true
            fi

            # Process partition images
            grep "^/dev" /etc/fstab | while read line; do
                set -- $line

                part=${2##/}
                path=$1

                if abm is-multiboot && [ -e "partitions/${part}.img" ] && [ -e "$path" ]; then
                    case "$part" in
                        *boot) # e.g. boot (kernel) + vendor_boot (kmods/recovery) & init_boot (halium) ramdisk fragments
                            echo "Unpacking ${part}.img for multiboot..."
                            unpackbootimg -i "partitions/${part}.img" >/dev/null
                            rm "partitions/${part}.img"
                            ;;
                        vendor|system_dlkm|vendor_dlkm) # known partition images wanted in multiboot rootfs, follows usual logic
                            path="$SYSTEM_MOUNTPOINT/var/lib/lxc/android/$part.img"
                            [ -e "$path" ] || install -m664 /dev/null "$path"
                            ;;
                        *)
                            echo "Ignoring $path update under ABM"
                            rm "partitions/${part}.img"
                            ;;
                    esac
                fi

                if [ -e partitions/${part}.img ] && [ -e $path ]; then
                    if simgtest partitions/${part}.img 2>/dev/null; then
                        echo "Flashing sparse ${part} at ${path}"
                        simg2img partitions/${part}.img ${path}
                    else
                        echo "Checking if ${part} at ${path} needs updating..."
                        new_size=$(wc -c < partitions/${part}.img)
                        if ! head -c ${new_size} ${path} | cmp -s partitions/${part}.img; then
                            echo "  Flashing (binary content differs)..."
                            cat partitions/${part}.img > ${path}
                        else
                            echo "  Already up-to-date"
                        fi
                    fi
                    sync
                    rm partitions/${part}.img
                fi
            done

            # Handle kernel-related "partition" updates for multiboot when all unpacked
            if [ -f boot.img-kernel ]; then
                ut_conf="$(abm print-conf)" # e.g. "/bootset/db/entries/rom0.conf"
                rom="/bootset/${ut_conf##*/}"; rom=${rom%.conf} # e.g. "/bootset/rom0"
                echo "Updating multiboot kernel files for $rom..."
                cp boot.img-kernel "$rom/zImage"
                for ramdisk in "vendor_boot.img-vendor_ramdisk" "init_boot.img-ramdisk" "boot.img-ramdisk"; do
                    [ -f $ramdisk ] && cat $ramdisk >> boot.img-final-ramdisk
                done
                cp boot.img-final-ramdisk "$rom/initrd.cpio.gz"
                # shellcheck disable=SC3034
                cmdline="$(<boot.img-cmdline) $(cat vendor_boot.img-vendor_cmdline 2>/dev/null) $(getprop ro.abm.extra_cmdline)"
                sed -i "s:options .*:options ${cmdline# *}:" "$ut_conf"
                sync
                rm *boot.img-*
            fi

            # Remove tarball to free up space, since device tarballs
            # extract partitions/blobs that might fill up cache,
            # this way we ensure we got space for the partitions/blobs
            rm -f recovery/$2
        ;;

        "#")
            # We are processing a command in the script, ignore it
        ;;

        *)
            echo "Unknown command: $1"
        ;;
    esac
done < $COMMAND_FILE

# Remove the update files
for file in $REMOVE_LIST; do
    rm -f $file
done

# If a previous SWAP of 512MB is available, remove
if [ -e /data/SWAP.img ]; then
    echo "Removing obsolete SWAP.img"
    rm -f /data/SWAP.img
fi

# Ensure we have sane permissions
if [ "$USE_SYSTEM_PARTITION" -eq 0 ];then
    chmod 600 /data/ubuntu.img
    chown 0:0 /data/ubuntu.img
fi

touch /data/.last_update || true
sync

echo "Done upgrading..."

# Make sure there are always 5% reserved in /data for root usage
# else filling $HOME can make the system unusable since writable
# files and dirs share the space with $HOME and android does not
# reserve root space in /data for ext4.
# Devices with f2fs can reserve space with setting the
# "reserve_root" mount option.
if [ "$USE_SYSTEM_PARTITION" -eq 1 ]; then
    mount_data
fi

DATA_FS_TYPE=$(grep "/data " /proc/mounts| cut -d ' ' -f3)
if [ "$DATA_FS_TYPE" == "ext4" ]; then
    tune2fs -m 5 $(grep "/data " /proc/mounts| sed -e 's/ .*$//')
fi

# Always unmount unconditionally
umount /data

sync
