#!/bin/sh
# shellcheck disable=2154
# Shellcheck does not see the assignment of variables in the called functions
# and complains about using unassigned variables.

HELP="
# Yocto installation script for Flash-N-Go System - (c) 2021 SECO Northern Europe GmbH
#
# This script is designed to run from the Flash-N-Go System v15.1 or higher.
#
# It writes Linux Yocto images to the internal eMMC memory.
#
# The script can load the image files either from a local directory (e.g. USB
# drive, SD card or /tmp directory) or over the network.
#
# To run the script, configure your server so that it points to the
# folder containing this script and the yocto release files.
# Connect your device to the network, boot Flash-N-Go system and make sure it
# can access your server (try with 'ping', configure Ethernet if necessary).
# Then run the following command (replace the IP
# address by your server's IP address, and append required arguments):
#   export URL=http://192.168.1.100; curl \$URL/fng-install.sh | sh -s -- <args>
#
# Installation from local media is possible with:
#   unset URL;./fng-install.sh <args>
#
# Options:
#     -b|--BS                       Boot script
#     -d|--DTB                      Device tree
#     --DTBO                        Device tree overlay
#     -e|--EraseRPM                 RPMs/package to erase
#     -f|--FS                       Root file system archive
#     -i|--Image                    Boot logo
#     -k|--KeepPartitions           Keep existing partitions, do not call fdisk (disables -u)
#     -p|--ParamFile                Parameter file (each parameter on a separate line)
#     -r|--RPM                      Additional RPM packages
#     -s|--UserScript               Additional user script to be executed after installation
#     -t|--TFTP                     Additional file (Format: <filename>:<target>)
#     --BackgroundColor             Set background color for boot logo 'black', 'white' or RGB hex format '0xRRGGBB'
#     --PostInstallScript           Script executed in the Flash-N-Go System environment after any other install step
#     --PreInstallScript            Script executed in the Flash-N-Go System environment prior to any other install step
#     --retry                       Retry the update X times if an error occurs
#     --AB-target                   Select 'A' or 'B' as target for the installation, A is default.
#                                   Implies '--AB-partitioning' and is working if running in FNG System only.
"

BOOTLOADER_MIN_VERSION_imx6=13.0
BOOTLOADER_MIN_VERSION_imx8m=2019.4
FNGSYSTEM_MIN_VERSION=12.0

#=========================
# fnginstall common
# Start of include file 
#=========================

# shellcheck disable=2154,2153
# Shellcheck does not see the assignment of variables in the called functions
# and complains about using unassigned variables.
# Common functions to the SECO North install scripts
# shellcheck disable=2034

TARGETDEVICE="mmcblk0"
MACHINE=seco-mx8mp
ALLOW_INSTALLATION_IN_YOCTO=false

COMMON_PARAMETERS="\
#     -h|-\?|--help                 Call a 'usage' function to display a synopsis, then exit.
#     --debug                       Enable debug mode, doesn't delete tmp folder
#     --force                       Force execution, continue also when prechecks fail
#     --force-sfdisk                Call sfdisk with force parameter and overrule its checks
#     --force-reformat              Reformats all needed partitions
#     --fdisk|--force-repartition   Create new partition table, no matter what is found on disk
#     --auto-repartition            Create new partitions as needed, default is to display
#                                   a warning and exit if partitions do not match given
#                                   settings, except when an empty disk/unreadible partition
#                                   table is found.
#     --reuse-partitions            With this option set, the partition table found is reused
#                                   as far as possible, not stopping on warnings regarding the
#                                   partition layout.
#                                   This changes the meaning of --user-partition-size and
#                                   --AB/--no-AB to just defaults when a partition table is
#                                   created anyway, but they do not trigger a change anymore.
#     --files-on-targetdev          Set this option if the installation files are stored on
#                                   a partition of the target device. The partition
#                                   containing the installation files remains mounted. This
#                                   does not work if this partition is itself an installation
#                                   target partition.
#                                   No repartitioning is done (implies --reuse-partitions).
#     --no-AB|--no-AB-partitioning  Select A-only partition scheme, with one Boot
#                                   and one Root partition.
#                                   Will display a warning and exit, when another partition
#                                   scheme is found but --auto-repartition is not set,
#                                   otherwise the partition table is adapted if needed.
#     --AB|--AB-partitioning        Selects AB partition scheme with two boot and
#                                   two root partitions.
#                                   Will display a warning and exit, when another partition
#                                   scheme is found but --auto-repartition is not set,
#                                   otherwise the partition table is adapted if needed.
#     -u|--UserPartition            User partition (Format: <filesystem>:<megabytes>:<label>)
#                                   Supported filesystems: vfat, ext2, ext3, ext4
#                                   Remark: A partition size of 0 disables the creation of
#                                   the user partition completely.
#     --machine|--machine=*         Overwrite the machine type for the install for testing,
#                                   default is '$MACHINE'
#     -t=*|--target=*               Set the device to install to, default is '$TARGETDEVICE'.
#     --dry                         Dry run, don't actually write anything
#     -p=*|--ParamFile=*            Read further parameters from file
#     -v|--verbose                  Increase verbosity
#     --insecure                    Let curl ignore certificate problems
#     --curl-extra-arg=*            Additinal arguments passed to curl,
#     --url=*                       Set the curl prefix to given url, overwrites
#                                   environment variable URL/TFTP.
"

# Default start of the bootloader but may be changed after soc detection
BOOTLOADER_SEEK_DEFAULT=33
BOOTLOADER_SEEK_USER=$BOOTLOADER_SEEK_DEFAULT
BOOTLOADER_SEEK_BOOT=$BOOTLOADER_SEEK_DEFAULT

BOOTLOADER_SEEK_USER_MX8MP=32
BOOTLOADER_SEEK_BOOT_MX8MP=0

# Offset of the u-boot environment defconfig, from u-boot defconfig
BOOTLOADER_ENV_START=$((0x400000))

DEBUG=false
FORCE=false
FORCE_SFDISK=""

FORCE_REPARTITION=false
AUTO_REPARTITION=false
REUSE_PARTITIONS=false
FORMAT_PARTITIONS=false
SKIP_POSTINSTALL=false
SKIP_DOWNLOAD=false
REPARTITION_NEEDED=false
FILES_ON_TARGETDEV=false

DRYRUN=
AB_PARTITIONING=true
AB_PARTITIONING_PARAM=""
PART_USER_SIZE_PARAM=-1
PART_SCHEME_PARAM=""
PART_TABLE_TYPE_PARAM=""
MOUNTED_PARTITIONS=""

# Set to zero if not set outside as environment variable
VERBOSE="${VERBOSE:-0}"
# Optional curl args
CURL_ARGS=""
CURL_EXTRA_ARGS=""
# Fail the download if speed drops below this value for 30sec
SPEEDLIMIT="${SPEEDLIMIT-10000}"

COMPATIBLE_SOCID="Not set"
SCRIPTPATH=""

VERSIONS_SCRIPT_PREFIX=""

NEWLINE='
'

# Preserve handle to stdout if we later redirect &1
exec 5>&1
TMPLOGFILE=$(mktemp) || exit_error "Failed to create tmpfile"
LOGFILE="$TMPLOGFILE"

TMPDIR=$(mktemp -d) || exit_error "Failed to create temporary directory"

#=============================================
# Common defaults for all install scripts
# Partition sizes in MB
#=============================================
ALL_PARTITIONS="PART_BOOTLOADER PART_BOOTLOADER_EXTRA PART_CONFIG PART_FNGSYSTEM PART_A_BOOT PART_B_BOOT PART_A_ROOT PART_B_ROOT PART_USER "
PART_BOOTLOADER_SIZE_MIN=1

PART_BOOTLOADER_EXTRA_SIZE_DEFAULT=1
PART_BOOTLOADER_EXTRA_SIZE_MIN=1

PART_CONFIG_SIZE_DEFAULT=16
PART_CONFIG_SIZE_MIN=1
PART_CONFIG_FORMATED=false

PART_EXTENDED_SIZE_DEFAULT=1
PART_EXTENDED_SIZE_MIN=1

PART_FNGSYSTEM_SIZE_DEFAULT=128
PART_FNGSYSTEM_SIZE_MIN=96

PART_A_BOOT_SIZE_DEFAULT=128
PART_B_BOOT_SIZE_DEFAULT=128
PART_A_BOOT_SIZE_MIN=32
PART_B_BOOT_SIZE_MIN=32

PART_ROOT_SIZE_DEFAULT=""
PART_A_ROOT_SIZE_MIN=800
PART_B_ROOT_SIZE_MIN=800

# Different default user partition sizes
# for different storage media size _4G is
# for the 4G emmc and so on
USERPART_SIZE_16G=4096
USERPART_SIZE_8G=2048
USERPART_SIZE_4G=1024
USERPART_SIZE_2G=256
USERPART_SIZE_PARAM=-1

PART_USER_TYPE_DEFAULT="ext4"
PART_USER_SIZE_DEFAULT=""
PART_USER_LABEL_DEFAULT="USER"

# If nothing else is specified, all sizes are OK
PART_USER_SIZE_MIN=0

# Partition type GUIDs
GUID_BIOS=21686148-6449-6E6F-744E-656564454649
GUID_EFI_SYSTEM=C12A7328-F81F-11D2-BA4B-00A0C93EC93B
GUID_LINUX_FS=0FC63DAF-8483-4772-8E79-3D69D8477DE4
GUID_MS_RESERVED=E3C9E316-0B5C-4DB8-817D-F92DF00215AE
# These GUIDs are better suited for U-Boot and U-Boot environment,
# but are not yet supported by the 'fdisk' tool:
# GUID_BOOTLOADER=2568845D-2332-4675-BC39-8FA5A4748D15
# GUID_UBOOT_ENV=3DE21764-95BD-54BD-A5C3-4ABE786F38A8

#========================================
# Message functions
#========================================
set_verbosity()
{
    case "$1" in
        0|1|2|3) VERBOSE="$1";;
        +1) VERBOSE="$(( VERBOSE + 1 ))";;
        -1) VERBOSE="$(( VERBOSE - 1 ))";;
    esac
    if [ $VERBOSE -ge 2 ];
    then
        set -x
    fi
    if [ $VERBOSE -eq 0 ];then
        exec 1>>"$LOGFILE"
        exec 2>&1
    else
        # Restore output
        exec 1>&5
        exec 2>&1
    fi
}
set_verbosity

set_machine_defaults()
{
    # Map the yocto 'machine' to the 'soc_id' in sysfs
    # use | as separator if more then one id is OK.
    case $MACHINE in
        "seco-mx8mm"*)  COMPATIBLE_SOCID="i.MX8MM" ;;
        "seco-mx8mp"*)  COMPATIBLE_SOCID="i.MX8MP" ;;
        "seco-mx6"|"seco-mx6-"*)    COMPATIBLE_SOCID="i.MX6Q|i.MX6DL" ;;
        "seco-mx6ull"*) COMPATIBLE_SOCID="i.MX6ULL" ;;
    esac

    case ${MACHINE} in
        seco-mx6*)
            PART_SCHEME_DEFAULT=1
            [ -n "$BOOTLOADER_MIN_VERSION_imx6" ] && \
                BOOTLOADER_MIN_VERSION="$BOOTLOADER_MIN_VERSION_imx6"
            BOOTLOADER_NAME="Flash-N-Go Boot"
            ;;
        seco-mx8m*)
            PART_SCHEME_DEFAULT=2
            [ -n "$BOOTLOADER_MIN_VERSION_imx8m" ] && \
                BOOTLOADER_MIN_VERSION="$BOOTLOADER_MIN_VERSION_imx8m"
            BOOTLOADER_NAME="U-Boot"

            # Currently imx8mm and imx8mp are two machines in yocto
            # but we may handle them as one machine later, so distinguish
            # between those here

            if [ ! -r /sys/devices/soc0/soc_id ];then
                warn_msg "Failed to detect soc_id."
            else
                SOCID=$(cat /sys/devices/soc0/soc_id)
                info_msg "Detected soc $SOC_ID"
                case ${SOCID} in
                    i.MX8MP)
                        BOOTLOADER_SEEK_USER=$BOOTLOADER_SEEK_USER_MX8MP
                        BOOTLOADER_SEEK_BOOT=$BOOTLOADER_SEEK_BOOT_MX8MP
                        ;;
                    i.MX8MM)
                        # no overwrite needed
                        ;;
                esac
            fi
            ;;
        *)
            PART_SCHEME_DEFAULT=1
            [ -n "$BOOTLOADER_MIN_VERSION_imx6" ] && \
                BOOTLOADER_MIN_VERSION="$BOOTLOADER_MIN_VERSION_imx6"
            BOOTLOADER_NAME="Flash-N-Go Boot"
            ;;
    esac

    # These are more image specific then machine but set them here anyway.
    # On newer releases we use sys-versions which comes with the image
    # and should know to handle things on the image correctly
    # Older base images (fngsystem or yocto we are running on)
    # need other ways to read out the needed infos
    if command -v "sys-versions.sh" 1>/dev/null;then
        VERSIONS_SCRIPT_PREFIX="sys"
    else
        VERSIONS_SCRIPT_PREFIX="gf"
    fi
    if command -v "$VERSIONS_SCRIPT_PREFIX"-versions.sh 1>/dev/null;then
        check_fng_system_version=check_fng_system_version_gfversion
        is_fng_system=is_fng_system_gfversion
        check_bootloader_version=check_bootloader_version_gfversion
    else
        check_fng_system_version=check_fng_system_version_deprecated
        is_fng_system=is_fng_system_deprecated
        check_bootloader_version=check_bootloader_version_deprecated
    fi
}
set_machine_defaults

usage()
{
    # Extract help text from header above
    echo "$HELP" | cut -b 3- >&5
    echo "$COMMON_PARAMETERS" | cut -b 3- >&5
}

milestone()
{
    # Duplicate text to out and logfile
    printf "\n== %s ==\n\n" "$*" | tee -a "$LOGFILE" >&5
}

error_text()
{
    # shellcheck disable=2059
    printf "$*\n" | tee -a "$LOGFILE" >&5
}

error_msg()
{
    error_text "ERROR: $*"
}

warn_msg()
{
    error_text "WARNING: $*"
}


info_msg()
{
   # Duplicate text to out and logfile if needed
   if [ "$VERBOSE" -gt 0 ];then
       # shellcheck disable=2059
       printf "$*\n" | tee -a "$LOGFILE" >&5
   else
       # shellcheck disable=2059
       printf "$*\n" >> "$LOGFILE"
   fi
}
partitions_ready_message()
{
    {
    echo "The next message,traditionally used for synchronization during"
    echo "production tests, is printed here for compatibility reasons:"
    echo "Re-reading the partition table ..."
    }>&5
}

#========================================
# Cleanup on exit
#========================================
nop()
{
    echo
}

#========================================
# Called on any ungraceful exit
#========================================
param_error()
{
    error_text  "Failed to parse parameter: $*"
    exit 1
}

exit_error()
{
    trap nop EXIT
    trap nop INT
    set +e

    cleanup

    if [ "$VERBOSE" -le 0 ];then
        {
        echo
        echo "================================================="
        echo "  Error occurred during update."
        echo "================================================="
        echo
        if [ -n "${LOGFILE}" ];then
            echo "See latest log (complete log in $LOGFILE):"
            tail "$LOGFILE"
        fi
        }>&5
    fi

    if [ -n "$*" ];then
        error_msg "$*"
    fi
    error_msg "Update failed."
    exit 1
}

#========================================================
# helper functions
#========================================================
# Does the string $1 contain the substring $2
string_contains()
{
    case "$1" in
        *$2*) return 0
    esac
    return 1
}

string_ends_with()
{
    case "$1" in
        *$2) return 0
    esac
    return 1
}

string_begins_with()
{
    case "$1" in
        $2*) return 0
    esac
    return 1
}

is_number()
{
    if test "$1" -eq "$1" 2>/dev/null
    then
        return 0
    fi
    return 1
}

tar_compression()
{
    # The tar of fng-system is not yet able to detect compression method
    if string_ends_with "$1" ".tar"; then
        echo ""
    elif string_ends_with "$1" ".tgz"; then
        echo "z"
    elif string_ends_with "$1" ".tar.gz"; then
        echo "z"
    elif string_ends_with "$1" ".tar.bz2"; then
        echo "j"
    else
        if [ "$2" = "-q" ];then
            return 1;
        else
            error_msg "Unknown filetype of rootfs ${1} failed, giving up."
            exit 1
        fi
    fi
}

# The calculation is split up to stay in 32bit range
MB_to_sector() {
    factor=$(( 1024 * 1024 / SECTOR_SIZE ))
    eval "$1=$((  $2 * factor ))"
}
sector_to_MB() {
    factor=$(( 1024 * 1024 / SECTOR_SIZE ))
    eval "$1=$((  $2 / factor ))"
}
sector_to_B() {
    eval "$1=$((  $2 * SECTOR_SIZE ))"
}

automount()
{
    case $1 in
        0|disable)
            if [ -x /usr/sbin/automount.sh ];
            then
                chmod -x /usr/sbin/automount.sh
            fi
            ;;
        1|enable)
            if [ -e /usr/sbin/automount.sh ];
            then
                chmod +x /usr/sbin/automount.sh
            fi
            ;;
    esac
}

#================================================
# Checks if the partition passed as $2 is mounted
# somewhere and returns the mountpoint in the
# variable named by $1
#================================================
find_mountpoint()
{
    # Check if it is mounted and where
    # Use df to find out
    if ! df_out="$(df "$2" 2>/dev/null )"
    then
        return 1
    fi

    # df sometimes reports tmpfs as mount, sort this out
    # df -t is not supported by busybox, so do it manually
    # shellcheck disable=2016
    splitstring df_out_mountpoint " $newline" "$df_out" '$7' "Mounted"
    if [ "$df_out_mountpoint" = "/dev" ];then
        return 1
    fi
    if [ -z "$df_out_mountpoint" ];then
        return 1
    fi
    eval "$1=$df_out_mountpoint"
    return 0
}

#=========================================================
# Backup list of mounted devices
#=========================================================
store_mount()
{
    MOUNTED_PARTITIONS=$( cat /proc/mounts )
}

#=========================================================
# Mount back all devices
#=========================================================
restore_mount()
{
    info_msg "Restore mounted devices..."

    echo "$MOUNTED_PARTITIONS" |
        while read -r mnt
    do
       if ! grep -q "$mnt" /proc/mounts;then
           ARG_NUMBER=0
           for p in $mnt;do
               eval "val_${ARG_NUMBER}"=$p
               ARG_NUMBER="$(( ARG_NUMBER + 1 ))"
           done
           mount -t $val_2 $val_0 $val_1 -o $val_3
       fi
    done
}

#===================================================
# Splits a string by separators given, and returns
# specified elements given by 'format'
# Is also able to search for a key value in the splited
# results
# See variables comments.
# Called:
# splitstring r ":,|" "this-is:a|test" 'Result:$2--#3'
# r is set to "Result:a--test"
#===================================================
splitstring()
{
    # Prefix the variables, as there are no locals
    splitstring_ret="$1"     # Variable name to store the result in
    splitstring_sep="$2"     # String with chars used as separator, not part of the result
    splitstring_in="$3"      # The string to split
    splitstring_format="$4"  # Specifies the format for the result, use $x for the elements from the split
    splitstring_key="$5"     # Optional, when set the splited results are searched for this keyword, and $1 maps to the following element and so on

    OLDIFS="$IFS"
    IFS="$splitstring_sep"
    # shellcheck disable=2086
    set -- $splitstring_in
    IFS="$OLDIFS"

    # If 'splitstring_key is set, go through the split elements
    # and forward until the match is found
    # return error if no match was found
    if [ -n "$splitstring_key" ];then
        while [ $# -gt 0 ];do
            if [ "$1" = "$splitstring_key" ];then
                shift; break
            fi
            shift; continue;
        done
        # 'splitstring_key' was not found in the splits 
        # (no more elements left), return error code
        [ $# -lt 1 ] && return 1
    fi

    # This unpacks the splitstring_format "xx $1 xx" to the values of the split
    ret="$(eval echo "$splitstring_format")"

    # This sets the specified return variable
    eval "$splitstring_ret=\"$ret\""
}

# Wait for all processes using the MMC to complete
wait_for_mmc()
{
    if command -v fuser 1>/dev/null;then
        DEV=$1

        # Timeout after 10 seconds
        i=10
        while true
        do
            [ $i -gt 0 ] || exit_error "Timeout while waiting for MMC PIDs to finish"
            fuser "$DEV" >/dev/null || return
            sleep 1
            i=$(( i - 1))
        done
    else
        sleep 2 # we dont have fuser on fng system 15, just wait a little
    fi
}

#====================================================
# Check version using the gfversion tool
#  The idea is, that gfversion is installed in the image
#  and knows how to handle specific paths...
#====================================================

check_fng_system_version_gfversion()
{
    # If the minimal version is not set, just return
    [ -z "${FNGSYSTEM_MIN_VERSION}" ] && return 0

    # let sys-versions compare the version and return if OK
    "$VERSIONS_SCRIPT_PREFIX"-versions.sh -q -ge "$FNGSYSTEM_MIN_VERSION" fngsystem && return 0

    error_msg "Your Flash-N-Go version is outdated."
    error_text "Please update Flash-N-Go System to version" \
               "$FNGSYSTEM_MIN_VERSION or newer."
    $FORCE || exit 1
}

is_fng_system_gfversion()
{
    # gfversion return 0 if the version can be correctly found
    # and an error otherwise
    "$VERSIONS_SCRIPT_PREFIX"-versions.sh -q fngsystem 2>/dev/null && return 0
    return 1
}

check_bootloader_version_gfversion()
{
    # If the minimal version is not set, just return
    [ -z "${BOOTLOADER_MIN_VERSION}" ] && return 0

    # let sys-versions compare the version and return if OK
    "$VERSIONS_SCRIPT_PREFIX"-versions.sh -q -ge "$BOOTLOADER_MIN_VERSION" bootloader && return 0

    if [ "$BOOTLOADER_NAME" = "U-Boot" ]; # The bootloader name is set by the machine/soc type
    then
        # For u-boot the installed bootloader version is displayed
        # and may not be readable when booted from RAM, so print
        # a warning only
        warn_msg "Your Bootloader version ($BOOTLOADER_NAME) is outdated" \
                 "or could not be detected."
    else
        error_msg "Your Bootloader version is outdated."
        error_text "Please update $BOOTLOADER_NAME to version" \
                   "$BOOTLOADER_MIN_VERSION or newer."
        $FORCE || exit 1
    fi
}


#====================================================
# Check version using the deprecated direct access
#  This makes more assumptions about the image,
#  so this is deprecated but needed for older
#  base versions
#====================================================
#============================================================
# Deprecated version comparison, needed for FNGSystem < 16.1
#============================================================
decode_version()
{
    rmajor="$1"
    rminor="$2"
    rpatch="$3"
    refversion="$4"

    OLDIFS="$IFS"
    IFS='=": .()-+r'
    # shellcheck disable=2086
    set -- $refversion
    IFS="$OLDIFS"

    major="$1"
    minor="$2"
    patch="$3"
    if [ -z "$minor" ];then minor=0; fi
    if [ -z "$patch" ];then patch=0; fi

    eval "$rmajor=$major"
    eval "$rminor=$minor"
    eval "$rpatch=$patch"
}

compare_versions()
{
    decode_version refmajor refminor refpatch "$1"

    # Compare the versions
    # Set the environment to pairs of both's versions elements
    # and compare $1 to $2 and so on.
    # if the next element is empty in the reference version
    # just return the comparision of the current pair
    # so the comparision of 16.0.2 -ge 16  is true.
    #
    set -- "$2" "$refmajor" "$3" "$refminor" "$4" "$refpatch"
    while [ $# -ge 2 ];
    do
        if [ "$1" -ne "$2" ];then break;  fi
        if [ -z "$4" ];then break; fi
        shift 2
    done
    if [ "$1" -ge "$2" ];then
        return 0
    else
        return 1
    fi
}


check_fng_system_version_deprecated()
{
    # If the minimal version is not set, just return
    [ -z "${FNGSYSTEM_MIN_VERSION}" ] && return 0

    # Old interface, available in older fngsystem versions
    # If the script is not found just exit
    if [ ! -x /etc/init.d/showversion ];then
        error_msg "Your Flash-N-Go version cannot be determined.."
        error_text "Please update Flash-N-Go System to version" \
                   "$FNGSYSTEM_MIN_VERSION or newer."
        $FORCE || exit 1
    fi

    VERSION_SHOWVERSION=$(/etc/init.d/showversion start)

    # Split up the version string using the IFS
    OLDIFS="$IFS"
    IFS='=":.()-+vr '
    # shellcheck disable=2086
    set -- $VERSION_SHOWVERSION
    IFS="$OLDIFS"

    # compare the version, return 0 if OK
    compare_versions "$FNGSYSTEM_MIN_VERSION" "$5" "$6" "$8" && return 0

    error_msg "Your Flash-N-Go version is outdated."
    error_text "Please update Flash-N-Go System to version" \
               "$FNGSYSTEM_MIN_VERSION or newer."
    $FORCE || exit 1
}

is_fng_system_deprecated()
{
    # if the hostname contains 'FLASH-N-GO', return 0
    grep -q FLASH-N-GO /etc/hostname && return 0
    return 1
}

check_bootloader_version_deprecated()
{
    # If the minimal version is not set, just return
    [ -z "${BOOTLOADER_MIN_VERSION}" ] && return 0

    # Old interface, the FNGBoot version is tradition found in cpuinfo
    VERSION_CPUINFO=$(grep FNGBoot /proc/cpuinfo)

    # Split the elements
    OLDIFS="$IFS"
    IFS='=": .()-+r'
    # shellcheck disable=2086
    set -- $VERSION_CPUINFO
    IFS="$OLDIFS"

    # If the version is OK, just return 0
    compare_versions "$BOOTLOADER_MIN_VERSION" "$2" "$3" "$4" && return 0

    if [ "$BOOTLOADER_NAME" = "U-Boot" ];
    then
        # For u-boot the installed bootloader version is displayed
        # and may not be readable when booted from RAM, so print
        # a warning only
        warn_msg "Your Bootloader version ($BOOTLOADER_NAME) is outdated or could not be detected."
    else
        error_msg "Your Bootloader version is outdated."
        error_text "Please update $BOOTLOADER_NAME to version $BOOTLOADER_MIN_VERSION or newer."
        $FORCE || exit 1
    fi
}


#===========================================================
# Check if we are running on the machine the software package is meant for
# MACHINE contains the 'machine' used in yocto like seco-mx6, seco-mx8mp, ...
#===========================================================
check_machine_type()
{
    SOCID_PATH="/sys/devices/soc0/soc_id"
    if [ ! -r  "$SOCID_PATH" ];then
        error_msg "Failed to read the soc_id from $SOCID_PATH"
        $FORCE || exit 1
    fi
    CURRENT_SOCID=$(cat "$SOCID_PATH" )

    # COMPATIBLE_SOCID is set by the machine type in set_machine_defaults
    # Loop over all elements seperated by |, return if a match is found
    OLDIFS=$IFS;IFS='|'
    for c in $COMPATIBLE_SOCID;do
        if [ "$CURRENT_SOCID" = "$c" ];then
            IFS=$OLDIFS
            return 0
        fi
    done

    error_msg "The software package is not compatible for this machine."
    error_msg "The soc_id of this machine is $CURRENT_SOCID."
    error_msg "The soc_id compatible with this software is $COMPATIBLE_SOCID."
    $FORCE || exit 1
}

#===========================================================
# Are we in FNG System,
# does the version of FNGBoot and FNG System match
#===========================================================
check_startup_conditions()
{
    check_machine_type

    if $is_fng_system;then
        $check_fng_system_version
    else
        if ! $ALLOW_INSTALLATION_IN_YOCTO;then
            error_msg "This script can be run from Flash-N-Go System only."
            $FORCE || exit 1
        fi
    fi

    $check_bootloader_version

    if [ ! -b "/dev/$TARGETDEVICE" ];then
        error_msg "The target '/dev/$TARGETDEVICE' is not a blockdevice or can't be found at all."
        $FORCE || exit 1
    fi
}

#=========================================
# Setup the CURL_PREFIX used for download
# and copy operation. It allows tftp(default)
# file path if URL or TFTP was not set, or just use
# the URL variable if a protocol was given
# (allows http://...)
#=========================================
setup_download_prefix()
{
    # Fall back to deprecated TFTP variable
    if [ -z "$URL" ] && [ -n "$TFTP" ];then
        info_msg "Please use URL= instead if TFTP="
        URL="$TFTP"
    fi

    # Choose cURL prefix dependent on whether URL variable is set
    if [ -z "$URL" ]
    then
        SCRIPTPATH=$(cd "$(dirname "$0")" && pwd)
        CURL_PREFIX="file://$SCRIPTPATH"
    else
        if string_contains "$URL" "://"
        then
            CURL_PREFIX="$URL"
        else
            CURL_PREFIX="tftp://$URL"
        fi
    fi

    CURL_ARGS=""
    if [ -n "$GITLAB_TOKEN" ];then
        # Concat all needed args, but use '|' as delimiter, so space is OK in variable
        CURL_ARGS="$CURL_ARGS|--header|PRIVATE-TOKEN: $GITLAB_TOKEN"
    fi

    # Check if we are using http[s] and fail on 404 and
    # other errors and follow redirects 3XX ...
    if string_begins_with "$CURL_PREFIX" "http*://";then
        CURL_ARGS="$CURL_ARGS|--fail|--location"
    fi
    CURL_ARGS="$CURL_ARGS|--speed-limit|$SPEEDLIMIT"
    # The next is with | as CURL_EXTRA_ARGS already contains it if not empty
    CURL_ARGS="$CURL_ARGS$CURL_EXTRA_ARGS"
}
#======================================================
# Argument parsing
#======================================================

# The function checks, if an argument is of the form
# key=value or key value, splits the string in the
# first case, and assigns the value to the retval
# (the first function parameter).
parse_args_value()
{
    retval="$1"
    p="$2"
    if string_contains "$p" "=";then
        v="${p##-*=}"
        r=0
    else
        v="$3"
        r=1
    fi
    if [ -z "$v" ];then
        error_msg "Parameter '$p' requires a non-empty option argument."
        exit 1
    fi
    eval "$retval"="$v"
    return $r
}

push_subdir()
{
    OLDSUBDIR="$SUBDIR|$OLDSUBDIR"
    SUBDIR="$1"
}
pop_subdir()
{
    OLDIFS="$IFS"
    IFS='|'
    # shellcheck disable=2086
    set -- $OLDSUBDIR
    SUBDIR="$1"
    shift
    OLDSUBDIR="$*"
    IFS="$OLDIFS"
}

parse_args()
{
    push_subdir "$1"
    shift
    while [ $# -ge 1 ]; do
        # The function returns the number of consumed parameters,
        # if 0 then continue parsing below, otherwise go to beginning of loop
        parse_extra_args "$1" "$2"
        consumed=$?
        if [ $consumed -gt 0 ];then
            shift $consumed
            continue
        fi

        case $1 in
            -h|-\?|--help)   # Call a "usage" function to display a synopsis, then exit.
                usage
                exit 0
                ;;
             --debug)       # Enable test mode
                DEBUG=true
                info_msg "Test mode selected"
                ;;
            --force) # Enable force mode
                FORCE=true
                ;;
            --force-sfdisk) # Enable force mode
                FORCE_SFDISK="--force"
                ;;
            --fdisk|--force-repartition) # Create new partition table, no matter what
                FORCE_REPARTITION=true
                ;;
            --force-reformat) # Reformats all needed partitions
                FORMAT_PARTITIONS=true
                ;;
            --auto-repartition) # Create new partitions as needed
                AUTO_REPARTITION=true
                ;;
            --reuse-partitions) # Keep as much as possible, don't stop on warnings
                REUSE_PARTITIONS=true
                ;;
            --files-on-targetdev)
                REUSE_PARTITIONS=true
                FILES_ON_TARGETDEV=true
                ;;
            --machine|--machine=*) # Overwrite the machine type to install on
                parse_args_value MACHINE "$1" "$2" || shift
                # For imx6 and imx6ull scheme 1 is default
                set_machine_defaults
                ;;
            --target|--target=*) # Don't use -t as shortcut, because it collides with the TFTP parameter
                parse_args_value TARGETDEVICE "$1" "$2" || shift
                TARGETDEVICE="${TARGETDEVICE#/dev/}"
                ;;
            --no-AB|--no-AB-partitioning)
                AB_PARTITIONING_PARAM=false
                ;;
            --AB|--AB-partitioning)
                AB_PARTITIONING_PARAM=true
                ;;
            -u|--UserPartition|-u=*|--UserPartition=*)
                parse_args_value USERPARTITION "$1" "$2" || ret=2
                ;;
            --dry) # Dry run, don't actually write anything
                DRYRUN="error_text DRYRUN Skipping: "
                ;;
            --skip-postinstall)
                SKIP_POSTINSTALL=true
                ;;
            --skip-download)
                SKIP_DOWNLOAD=true
                ;;
            -p|--ParamFile|-p=*|--ParamFile=*)
                parse_args_value filename "$1" "$2" || shift

                setup_download_prefix

                info_msg "Loading ${SUBDIR}${filename} ..."
                OLDIFS=$IFS;IFS='|'
                # shellcheck disable=2086
                args=$(curl ${CURL_ARGS#|} "${CURL_PREFIX}/${SUBDIR}${filename}" 2>/dev/null | tr -d '\r' | tr '\n' ' ')
                IFS=$OLDIFS
                if [ -z "$args" ]; then
                    error_msg "Failed loading arguments from ${SUBDIR}${filename}"
                fi

                #$args has no quote as splitting is wanted here
                # shellcheck disable=SC2086
                parse_args "$(dirname "${SUBDIR}${filename}")/" $args
                ;;
            -n|--nonverbose) set_verbosity 0;;
            -v|--verbose)    set_verbosity +1;;
            --insecure)
                CURL_EXTRA_ARGS="$CURL_EXTRA_ARGS|--insecure"
                ;;
            --curl-extra-arg|--curl-extra-arg=*)
                parse_args_value TMP "$1" "$2" || shift
                CURL_EXTRA_ARGS="$CURL_EXTRA_ARGS|$TMP"
                ;;
            --url|--url=*)
                parse_args_value URL "$1" "$2" || shift
                ;;
            -l|--logfile|-l=*|--logfile=*)
                parse_args_value NEWLOGFILE "$1" "$2" || shift
                touch "$NEWLOGFILE"
                if [ ! -w "$NEWLOGFILE" ]; then
                    param_error "cannot access the given logfile: $NEWLOGFILE"
                fi
                cat "$LOGFILE" >> "$NEWLOGFILE"
                LOGFILE="$NEWLOGFILE"
                ;;
            --)            # End of all options.
                break
                ;;
            *)
                info_msg "Ignoring parameter $1"
                ;;
        esac
        shift
    done
    pop_subdir
}

#==============================================
# Parse user partition argument
#    -u|--UserPartition
#    (Format "<filesystem>:<megabytes>:<label>").
#    allows to create an additional partition
#    during installation
#==============================================
parse_user_partition()
{
    # Parse user partition argument (Format "<filesystem>:<megabytes>:<label>").
    USERFS_TYPE="$PART_USER_TYPE_DEFAULT"
    USERFS_SIZE=0
    USERFS_LABEL="$PART_USER_LABEL_DEFAULT"

    if [ -n "$USERPARTITION" ]; then
        OLDIFS="$IFS"; IFS=':'
        # shellcheck disable=2086
        set -- $USERPARTITION
        IFS="$OLDIFS"
        USERFS_TYPE=$1
        USERFS_SIZE=$2
        USERFS_LABEL=$3
        if [ -z "$USERFS_TYPE" ] || [ -z "$USERFS_SIZE" ] || [ -z "$USERFS_LABEL" ]; then
            error_msg '"--UserPartition" requires an argument of the form "<filesystem>:<megabytes>:<label>"\n' >&2
        fi
        PART_USER_SIZE_PARAM="$USERFS_SIZE"
    fi

    case $USERFS_TYPE in
        ext2|ext3|ext4)
            USERFS_MKFSPARAM="-L ${USERFS_LABEL}"
            ;;
        vfat)
            USERFS_MKFSPARAM="-F 32 -n ${USERFS_LABEL}"
            ;;
        *)
            error_msg '"--UserPartition" supported filesystems: vfat, ext2, ext3, ext4\n' >&2
            exit 1
    esac
}

#===========================================================
# Use curl to download file
#===========================================================
do_load()
{
    milestone "Loading file $1"
    resolving_link=false
    CURL_SOURCE="$1"
    if [ -z "$2" ];then
        CURL_TARGET_FILE="${1##*/}"
    else
        CURL_TARGET_FILE="$2"
    fi
    CURL_TARGET="-o|$CURL_TARGET_FILE"

    while true;do
        OLDIFS=$IFS;IFS='|'
        # shellcheck disable=2086
        curl ${CURL_ARGS#|} "$CURL_PREFIX/$CURL_SOURCE" $CURL_TARGET 2>&1
        ret=$?
        IFS=$OLDIFS
        if [ "$ret" -eq 0 ];then
            # Handle the 'links' returned by gitlab, actually textfiles containing the real name.
            # if we got a 'zeroline' text file (one line, no line ending), it is probably a 'link'
            if [ ! -r "$CURL_TARGET_FILE" ];then
                return 1
            fi
            if [ "$( head "$CURL_TARGET_FILE" | wc -l)" -eq 0 ];then 
                resolving_link=true
                HEADER=$( head -n 1 "$CURL_TARGET_FILE")
                CURL_SOURCE="$(dirname "$CURL_SOURCE")/$HEADER"
                # Keep the current download
                mv "$CURL_TARGET_FILE" "$CURL_TARGET_FILE.bak"
                continue # Try the download again with the new filename
            fi
            if $resolving_link;then
                rm "$CURL_TARGET_FILE.bak"
            fi
            milestone "Loading file $1 finished."
            return 0;
        else
            if ! $resolving_link;then
                error_msg "Could not load file $1."
                [ -z "$URL" ] && info_msg "Did you forget to export the 'URL' variable ?."
            else
                # We tried to resolve a 'link' but that failed, anyway the original
                # download succeeded
                mv "$CURL_TARGET_FILE.bak" "$CURL_TARGET_FILE"
                milestone "Loading file $1 finished."
                return 0;
            fi
            return "$ret";
        fi
    done
}


#===========================================================
# Partition functions
#===========================================================
partition_error()
{
    info_msg "ERROR:\t$1"
    if $AUTO_REPARTITION || $FORCE_REPARTITION;then
        REPARTITION_NEEDED=true
        info_msg "\tPartition table will be altered.\n"
    else
        info_msg "" \
                "\tTo change the partition layout use one of:\n" \
                "\t  '--auto-repartition' to override the partitions as needed.\n" \
                "\t  '--force-repartition to create a complete new partition table.\n"
        exit 1
    fi
}

partition_warning()
{
    if $REUSE_PARTITIONS && [ -n "$2" ];then
        info_msg "$1\n\tContinue without changes anyway."
    else
        info_msg "WARNING: $1"
        if $AUTO_REPARTITION || $FORCE_REPARTITION;then
            REPARTITION_NEEDED=true
            info_msg "\tPartition table will be altered.\n"
        else
            info_msg "" \
                "\t Use '--reuse-partitions to continue with the current layout.\n" \
                "\t To change the partition layout use one of:\n" \
                "\t   '--auto-repartition' to override the partitions as needed.\n" \
                "\t   '--force-repartition to create a complete new partition table.\n" \
                "WARNING: expect dataloss in these cases."
            exit 1
        fi
    fi
}

#============================================
# Get the size of a partition, in MB or Sector
# get_disk_or_partition_size returnvar [M|S] partition
#============================================
get_disk_or_partition_size()
{
    # Call parted but ignore errors, as parted also complains about minor stuff
    # like unrecognized disk label and so on. Errors are hopefully detected on
    # broken output, if any.
    PARTED_OUTPUT=$(parted -s "$3" unit s print 2>/dev/null ) || true
    SECTOR_SIZE="${PARTED_OUTPUT#*Sector size*: }"
    SECTOR_SIZE="${SECTOR_SIZE%B/*}"
    # Assume 512 byte sectors
    is_number "$SECTOR_SIZE" || SECTOR_SIZE=512

    TOTAL_SIZE="${PARTED_OUTPUT#*Disk $3: }"
    TOTAL_SIZE="${TOTAL_SIZE%%s*}"
    if ! is_number "$TOTAL_SIZE";then
        # Fallback to blockdev
        TOTAL_SIZE="$(blockdev --getsize "$3" )"
    fi

    is_number "$TOTAL_SIZE" || { error_msg "Failed to get total size of $3"; return 1; }

    if [ "$2" = 'M' ];then
        sector_to_MB TOTAL_SIZE "$TOTAL_SIZE"
    fi

    eval "$1=$TOTAL_SIZE"
}

get_part_table_type()
{
    PARTED_OUTPUT=$(parted -s "$DEV" print 2>/dev/null)
    echo "$PARTED_OUTPUT" | grep "Partition Table: " | grep -oE '(msdos|gpt)'
    # return 'none' in case if partition table is not exist
    # (e.g. after sgdisk --zap-all or wipefs -a)
    if echo "$PARTED_OUTPUT" | grep -q "unrecognised disk label"; then
        echo "none"
    fi
}

get_sfdisk_version()
{
    sfdisk --version | sed -e 's|[^0-9]*\([0-9]\+\).\([0-9]*\).*|\1\2|'
}

#============================================
# Analyze current partition scheme
# Read the partition table of the device given
# Sets PARTITION_COUNT to the number of partitions
# Sets PARTITIONS="name:start:size:type:bootable|name:size:type|..."
# Sets SECTORSIZE, used to convert sectors to MB
# for further analyses, bootable is just 1 or 0
#
#============================================
read_partition_table()
{
    DEV="$1"

    CURRENT_LAYOUT=$( sfdisk -d "$DEV" )

    SECTOR_SIZE="${CURRENT_LAYOUT#*sector-size: }"
    SECTOR_SIZE="${SECTOR_SIZE%%$NEWLINE*}"

    # FNGSystem 15 sfdisk does not report the sector size
    if ! is_number "$SECTOR_SIZE";then
        # Use parted/blockdev to get it
        get_disk_or_partition_size  tmp M "$DEV"
        is_number "$SECTOR_SIZE" || exit_error "Failed to get sector size of $DEV"
    fi

    CURRENT_LAYOUT=$( echo "$CURRENT_LAYOUT" | grep "^$DEV")
    OLDIFS=$IFS
    IFS=":= ,$NEWLINE"
    # shellcheck disable=2086
    set -- $CURRENT_LAYOUT
    IFS=$OLDIFS

    PARTITION_COUNT=0
    PARTITIONS=""

    while [ $# -ge 7 ]; do # 7 Parameters per partition
        P="${1#$DEV}"
        ST="$3"
        SI="$5"
        T="$7"

        shift 7

        if [ "$1" = "bootable" ];then
            B=1;
            shift;
        else
            B=0
        fi

        PARTITIONS="$PARTITIONS$P:$ST:$SI:$T:$B|"
        PARTITION_COUNT=$(( PARTITION_COUNT + 1 ))

        # skip other unkown output
        while [ $# -gt 0 ] && ! string_begins_with "$1" "$DEV";do
            shift
        done
    done
}

get_partition()
{
    _result="$1"
    _NUMBER="$2"

    OLDIFS=$IFS
    IFS="|"
    # shellcheck disable=2086
    set -- $3
    IFS=$OLDIFS

    if [ $# -le "${_NUMBER}" ];then
        return 1
    fi
    shift "${_NUMBER}"

    eval "$_result=$1"

    return 0
}

#================================
# Set the given variables for name size type and bootable
# for the given partition number in the given partition data
# as read with read_partition_table
# For example: get_partition_data name start size typ bootable 5 "$PARTITIONS"
#===============================
get_partition_data()
{
    __name="$1"
    __start="$2"
    __size="$3"
    __type="$4"
    __bootable="$5"

    get_partition _tmp "$6" "$7" || return $?
    OLDIFS=$IFS
    IFS=":"
    # shellcheck disable=2086
    set -- $_tmp
    IFS=$OLDIFS

    if [ $# -lt "5" ];then
        return 1
    fi

    eval "$__name=$1"
    eval "$__start=$2"
    eval "$__size=$3"
    eval "$__type=$4"
    eval "$__bootable=$5"
    return 0
}

#================================
# set $1 to the first bootable partition found
# returns 1 if none
#================================
get_partition_bootable()
{
    __var="$1"
    p=0
    while get_partition_data N ST SI T B "$p" "$PARTITIONS"
    do
        if [ "$B" -eq "1" ];then
            eval "$__var=$N"
            return 0
        fi
        p=$(( p + 1 ))
    done
    return 1
}

#=============================
# Returns the number of the partition by name pattern
# Working on /dev/mmclkYpX: p1 is 0, p2 is 1 ...
#  TODO adapt for sda1 sdb2 ...
#=============================
get_partition_number()
{
    __number="$1"
    out=${2#/dev/}
    if string_begins_with "$out" "mmcblk";then
        out=${out#*p} # get the last number of mmcblkYpX
    elif string_begins_with "$out" "p";then
        out=${out#p} # get the number of pX
    else
        return 1
    fi
    is_number "$out" || return 1
    out=$(( out - 1 ))
    eval "$__number=$out"
    return 0
}

#=============================
# Detects the filesystem type and returns
# it via the return variable.
# The first parameter is the name of the return
# variable, and the second is the path to the partition
# (e.g. /dev/mmcblk0p8).
#=============================
get_partition_fstype()
{
    __result="$1"
    __partition="$2"

    if ! __partition_info=$(blkid -s TYPE "$__partition"); then
        return 1
    fi

    __partition_fstype=$(echo "$__partition_info" | cut -d'"' -f2)
    eval "${__result}=${__partition_fstype}"

    return 0
}

#================================
# Track the KEEP_PARTITIONS_CNT number
# pass /dev/mmcblkYp3 and KEEP_PARTITIONS_CNT
# is set to 2
#================================
dont_keep_partition()
{
    # no error if called without parameter (or empty variable), just a nop
    [ -z "$1" ] && return 0

    # ignore calls with /dev/mmcblkXbootY, they are not part of the partition table
    string_contains "$1" 'boot' && return 0

    if [ -z "$KEEP_PARTITIONS_CNT" ];then
        if [ -z "$PARTITION_COUNT" ];then
            exit_error "PARTITION_COUNT is not set, call analyze_partitions before"
        fi
        KEEP_PARTITIONS_CNT=$PARTITION_COUNT
    fi
    get_partition_number num "$1" || return
    if [ "$num" -lt "$KEEP_PARTITIONS_CNT" ];then
        KEEP_PARTITIONS_CNT="$num"
    fi
}

#=================================================================
# Set some defaults according to command line parameter and PART_SCHEME
#=================================================================
set_defaults_for_partscheme()
{
    _scheme="$1"
    if [ -z "$_scheme" ];then
        _scheme=PART_SCHEME_DEFAULT
    fi

    # when size is given on the command line,
    # check it during analyze
    if [ "$PART_USER_SIZE_PARAM" -gt 0 ];then
        # shellcheck disable=2034
        # variable is used in analyze_partitions
        PART_USER_SIZE_MIN="$PART_USER_SIZE_PARAM"
        PART_USER_SIZE_DEFAULT="$PART_USER_SIZE_PARAM"
    else
        if [ "$_scheme" = "1" ];then
            PART_USER_SIZE_DEFAULT="0"
        fi
    fi

    if [ -n "$AB_PARTITIONING_PARAM" ];then
        AB_PARTITIONING=$AB_PARTITIONING_PARAM
    else
        if [ "$_scheme" = "1" ];then
            AB_PARTITIONING=false
        fi
    fi
}

#=======================================================
# Analyze the current partition
#
# Sets global variables identifying all known partition
# in the commonly used layout
#
# We have basically two partition layout concepts
# in the wild
#
# The simple older layout: called PART_SCHEME=1
#      p1: FNGsystem
#      p2: Boot (kernel, boot.cfg ...)
#      p3: Rootfs
#      boot0: Bootloader
#      boot1: Config (xml, ...)
#
# The new A-B Boot concept, which should also work on SD Card, called PART_SCHEME=2
#      p1: Bootloader
#      p2: Bootloader Data
#      p3: Config
#      p4: Extended
#      p5: FNG System
#      p6: BootA
#      p7: BootB, or Rootfs A
#      ...
#      the upper partitions depend on A-B or single, and if a user partition is available.
#
# The first test, is to find out, which of these is
# installed (or non yet)
# The partition type of the first partition gives us the hint,
# 0xF8 for bootloader vs 0x0b for fat/FNGSystem)
#
#=======================================================
analyze_partitions()
{
    DEV="$1"
    milestone "Analyze partition table on $DEV ..."

    # These variables are the 'return' of the function.
    # if found they are set to the name of the corresponding partition
    for PART in $ALL_PARTITIONS;do
            eval "${PART}=''"
            eval "${PART}_SIZE=''"
            eval "${PART}_TYPE=''"
            eval "${PART}_OK=false"
    done

    # Read the partitions with sfdisk
    # sets the variable PARTITIONS and PARTITION_COUNT
    read_partition_table "$DEV"

    # Start with all partitions and remove unusable partitions later
    KEEP_PARTITIONS_CNT=$PARTITION_COUNT

    # Find out the general layout based on the type of
    # the first partition
    # Check type of first partition
    if ! get_partition_data P ST SI T B 0 "$PARTITIONS";then
        info_msg "No partitions found in $DEV"
        KEEP_PARTITIONS_CNT=0
        PART_SCHEME="$PART_SCHEME_DEFAULT"
    elif [ "$T" = "b" ] || [ "$T" = "$GUID_EFI_SYSTEM" ];then
        PART_SCHEME=1
    elif [ "$T" = "c" ];then
        # This is probable the old layout from the wic image
        # in this case, we don't want to keep the partition scheme
        KEEP_PARTITIONS_CNT=0
        PART_SCHEME="$PART_SCHEME_DEFAULT"
    elif [ "$T" = "f8" ] || [ "$T" = "$GUID_BIOS" ];then
        PART_SCHEME=2
    else
        info_msg "Unknown partition table on $DEV"
        KEEP_PARTITIONS_CNT=0
    fi

    set_defaults_for_partscheme "$PART_SCHEME"

    #====================================================
    # Old Layout uses boot partitions
    #====================================================
    if [ 1 -eq "$PART_SCHEME" ];then
        if [ -b "${DEV}boot0" ];then
            PART_BOOTLOADER="${DEV}boot0"
            get_disk_or_partition_size PART_BOOTLOADER_SIZE M "$PART_BOOTLOADER"
            if [ "$PART_BOOTLOADER_SIZE" -ge "$PART_BOOTLOADER_SIZE_MIN" ];then
                PART_BOOTLOADER_OK=true;
            fi
        fi
        if [ -b "${DEV}boot1" ];then
            PART_CONFIG="${DEV}boot1"
            get_disk_or_partition_size PART_CONFIG_SIZE M "$PART_CONFIG"
            if [ "$PART_CONFIG_SIZE" -ge "$PART_CONFIG_SIZE_MIN" ];then
                PART_CONFIG_OK=true;
            fi
        fi
    fi
    #====================================================
    # New and Old Layout
    # Loop over all found partitions in "PARTITIONS" and
    # sort them by type and sequence to the PART_XX variable
    # to use these later
    #====================================================
    p=-1
    while true;do
        p=$(( p + 1 ))
        if ! get_partition_data P ST SI T B $p "$PARTITIONS";then
            break
        fi
        sector_to_MB S "$SI"
        case $T in
            # The type f8 is used for the u-boot partitions
            f8) if [ -z "$PART_BOOTLOADER" ];then
                    PART="PART_BOOTLOADER"
                elif [ -z "$PART_BOOTLOADER_EXTRA" ];then
                    PART="PART_BOOTLOADER_EXTRA"
                else
                    error_msg "Unknown partition $DEV$P, type $T, size $S"
                    continue
                fi
                ;;
            # The type b for fat is used for config, fngsystem and the boot partitions
            b)  if [ -z "$PART_CONFIG" ];then
                    PART="PART_CONFIG"
                elif [ -z "$PART_FNGSYSTEM" ];then
                    PART="PART_FNGSYSTEM"
                elif [ -z "$PART_A_BOOT" ];then
                    PART="PART_A_BOOT"
                elif [ -z "$PART_B_BOOT" ];then
                    PART="PART_B_BOOT"
                elif [ -z "$PART_USER" ];then
                    PART="PART_USER"
                else
                    error_msg "Unknown partition $DEV$P, type $T, size $S"
                    continue
                fi
                ;;
            # The type 83 is used for root and user partitions
            83) if [ -z "$PART_A_ROOT" ];then
                    PART="PART_A_ROOT"
                elif [ -n "$PART_B_BOOT" ] && [ -z "$PART_B_ROOT" ];then # Second linux type partition is Root B is a corresponding boot partition is available
                    PART="PART_B_ROOT"
                elif [ -z "$PART_USER" ];then
                    PART="PART_USER"
                else
                    error_msg "Unknown partition $DEV$P, type $T, size $S"
                    continue
                fi
                ;;
            5)  # Extended partition
                PART="PART_EXTENDED"
                continue
                ;;
            "$GUID_BIOS")
                if [ -z "$PART_BOOTLOADER" ];then
                    PART="PART_BOOTLOADER"
                elif [ -z "$PART_BOOTLOADER_EXTRA" ];then
                    PART="PART_BOOTLOADER_EXTRA"
                else
                    error_msg "Unknown partition $DEV$P, type $T, size $S"
                    continue
                fi
                ;;
            "$GUID_EFI_SYSTEM")
                if [ -z "$PART_CONFIG" ];then
                    PART="PART_CONFIG"
                elif [ -z "$PART_FNGSYSTEM" ];then
                    PART="PART_FNGSYSTEM"
                elif [ -z "$PART_A_BOOT" ];then
                    PART="PART_A_BOOT"
                elif [ -z "$PART_B_BOOT" ];then
                    PART="PART_B_BOOT"
                elif [ -z "$PART_USER" ];then
                    PART="PART_USER"
                else
                    error_msg "Unknown partition $DEV$P, type $T, size $S"
                    continue
                fi
                ;;
            "$GUID_LINUX_FS")
                if [ -z "$PART_A_ROOT" ];then
                    PART="PART_A_ROOT"
                elif [ -n "$PART_B_BOOT" ] && [ -z "$PART_B_ROOT" ];then # Second linux type partition is Root B is a corresponding boot partition is available
                    PART="PART_B_ROOT"
                elif [ -z "$PART_USER" ];then
                    PART="PART_USER"
                else
                    error_msg "Unknown partition $DEV$P, type $T, size $S"
                    continue
                fi
                ;;
            "$GUID_MS_RESERVED")  # Extended partition
                PART="PART_EXTENDED"
                continue
                ;;
            # Sometime an empty entry in the partion table is found
            # This partition should be overwritten if needed
            0)  # Empty entry, don't try to keep it
                dont_keep_partition "$P"
                continue
                ;;
            *)
                error_msg "Unknown partition $DEV$P, type $T, size $S"
                continue
                ;;
        esac

        # Store the result in the variables
        # This uses a little shell magic to set the variables for the
        # partition name set in the case above. PART is PART_A_ROOT
        # for example, eval "${PART}_SIZE=$S" set PART_A_ROOT_SIZE to $S,
        # the size returned from get_partition_data above.
        eval "${PART}=$DEV$P"
        eval "${PART}_SIZE=$S"
        eval "${PART}_TYPE=$T"
        eval "S_MIN=$"${PART}_SIZE_MIN""

        # Compare with the expected sizes and set a PART_XX_OK variable
        # meaning the partition was found and the size in inside the limits
        if [ -n "$S_MIN" ];then
            if [ "$S_MIN" -le "$S" ];then
                eval "${PART}_OK=true"
            else
                info_msg "Partition $PART ( $DEV$P) is too small ( ${S}M ), will not be reused"
            fi
        fi
    done
    # Loop over all partitions again and mark those 'not OK'
    # to not keep them, meaning they will be rewritten later.
    # The mmc boot partitions are skipped, as their size is fix.
    for PART in $ALL_PARTITIONS;do
            # shellcheck disable=2027,2086
            eval "P=$"${PART}""
            # skip check for boot0 and boot1 partition
            if string_contains "$P" 'boot' ;then
                continue
            fi
            # shellcheck disable=2027,2086
            eval "OK=$"${PART}_OK""
            $OK || dont_keep_partition "$P"
    done

    return 0
}

#=====================================================================
# Check if partition table matches specified layout (via command line)
#
# The function set 'REPARTITION_NEEDED' and 'KEEP_PARTITIONS_CNT'
# according to the result
#=====================================================================
check_partitions_match_spec()
{
    if [ -n "$PART_SCHEME_PARAM" ] && [ "$PART_SCHEME" != "$PART_SCHEME_PARAM" ];
    then
        partition_warning "Found partition scheme '$PART_SCHEME' on disk but partition\n\tscheme '$PART_SCHEME_PARAM' was specified on command line."
        dont_keep_partition "$PART_FNGSYSTEM"
        dont_keep_partition "$PART_BOOTLOADER"
    fi

    # User partition layout
    if [ "$PART_USER_SIZE_PARAM" -gt 0 ] && ! $PART_USER_OK;then
        if [ -n "$PART_USER" ];then
            partition_warning "User partition specified as parameter but size on disk (${PART_USER_SIZE}M) is smaller then given size."
        else
            partition_warning "User partition specified as parameter but none found on disk."
        fi
        dont_keep_partition "$PART_USER"
        dont_keep_partition "$PART_A_ROOT"
        dont_keep_partition "$PART_B_ROOT"
    fi

    if [ "$PART_USER_SIZE_PARAM" -eq 0 ] && [ -n "$PART_USER" ];then
        partition_warning "No user partition specified as parameter but found on disk (${PART_USER_SIZE}M)."
        dont_keep_partition "$PART_USER"
        dont_keep_partition "$PART_A_ROOT"
        dont_keep_partition "$PART_B_ROOT"
    fi

    # Check A-B layout if specified on the commandline
    if [ -n "$AB_PARTITIONING_PARAM" ];then
        #  The A-B scheme was specified on the command line

        $PART_A_BOOT_OK || partition_warning "Boot Partition for OS A have not been found."
        $PART_A_ROOT_OK || partition_warning "Root Partition for OS A have not been found."

        if $AB_PARTITIONING; then
            if ! $PART_B_BOOT_OK || ! $PART_B_ROOT_OK;then
                partition_warning "Partitions for secondary OS have not been found, but A-B partition scheme\n\t has been selected on the command line."
                # If change from A-only to A-B layout all partitions after
                # PART_A_BOOT have to be deleted
                dont_keep_partition "$PART_A_ROOT"
            fi
        else
            if [ -n "$PART_B_ROOT" ] || [ -n "$PART_B_BOOT" ];then
                partition_warning "Partitions for secondary OS have been found, but A-only scheme has been\n\t selected on the command line."
                # If change from A-B to A-only layout all partitions after
                # PART_A_BOOT have to be deleted
                dont_keep_partition "$PART_B_BOOT"
                dont_keep_partition "$PART_A_ROOT"
                dont_keep_partition "$PART_B_ROOT"
            fi
        fi
    fi
}

#===========================================================
# See if all given nodes exists or wait until they do
#====================================================
wait_for_devnodes()
{
    timeout=10
    # Wait until device nodes are there
    sleep 2

    nodes=$*
    for node in $nodes; do
        while [ ! -b "$node" ]; do
            printf "."
            timeout=$(( timeout - 1))
            if [ 0 = $timeout ]; then error_msg "Timeout while waiting for $node"
                return 1
            fi
            sleep 1;
        done
    done
    info_msg "New nodes $nodes found"
    return 0
}

#====================================================
get_user_part_size()
{
    if [ -n "$PART_USER_SIZE_DEFAULT" ];then
        return
    elif [ "$PART_USER_SIZE_PARAM" -gt -1 ];then
        PART_USER_SIZE_DEFAULT=$PART_USER_SIZE_PARAM
    elif [ "$1" -gt 15000 ];then
        PART_USER_SIZE_DEFAULT=$USERPART_SIZE_16G
    elif [ "$1" -gt 7000 ];then
        PART_USER_SIZE_DEFAULT=$USERPART_SIZE_8G
    elif [ "$1" -gt 3000 ];then
        PART_USER_SIZE_DEFAULT=$USERPART_SIZE_4G
    else
        PART_USER_SIZE_DEFAULT=$USERPART_SIZE_2G
    fi
}
#========================================
# Umount all eMMC mountpoints
#========================================
umount_emmc()
{
    sed -n "s|^/dev/${TARGETDEVICE}p[0-9]\+ \([^ ]\+\) .*|\1|p" /proc/mounts |
        while read -r mnt
    do
        # Skip partition if FILES_ON_TARGETDEV is set and the mount path is a substring
        # of the install script path
        if $FILES_ON_TARGETDEV && string_contains "$SCRIPTPATH" "$mnt"
        then
            continue
        fi
        umount "$mnt" || umount -l "$mnt" || error_msg "Failed to unmount $mnt"
    done
}

#========================================
# Appends one partition line to a sfdisk
# partition file
# Parameter:
# Start Size Type
# If start is empty, the partition is just
# added after the previous one
#========================================
PARTITION_NEXT_START_SECTOR=8
append_partition()
{
    start="$1"
    if [ -z "$start" ];then
        start="$PARTITION_NEXT_START_SECTOR"
    fi
    if [ "$4" = "1" ];then # Bootable flag
        _B=",*"
    else
        _B=""
    fi
    echo "$start,$2,$3$_B"

    PARTITION_NEXT_START_SECTOR=$(( start + $2 + 1 ))
}

#========================================
# Create new partition table/ or new partitions
#
# Create new partitions on mmcblkY, keep the first 4 partitions
# create_partitions /dev/mmcblkY 4
#
# Create complete new table
# create_partitions /dev/mmcblkY 0
#
#========================================

create_partitions()
{
    DEV=$1
    if [ -n "$PART_SCHEME_PARAM" ];then
        PART_SCHEME_NEW="$PART_SCHEME_PARAM"
    elif [ -z "$PART_SCHEME" ];then
        PART_SCHEME_NEW="$PART_SCHEME_DEFAULT"
    else
        PART_SCHEME_NEW="$PART_SCHEME"
    fi

    # Number of partitions to keep, from the beginning
    #
    _KEEP=$2
    [ -z "$_KEEP" ] && _KEEP=0;

    if [ "$_KEEP" -gt 0 ] && [ "$PART_SCHEME" != "$PART_SCHEME_NEW" ];then
        exit_error "Can't change the partition scheme and keep partitions."
    fi

    set_defaults_for_partscheme "$PART_SCHEME_NEW"

    # these are normally called before
    if [ -z "$PARTITION_COUNT" ];then
        analyze_partitions "$DEV" || exit 1
    fi
    if [ -z "$TOTAL_SIZE_S" ];then
        get_disk_or_partition_size TOTAL_SIZE_S S "$DEV" || exit 1
    fi

    # Some checks regarding the keep parameter
    if [ "$_KEEP" -gt 0 ];then
        if [ "$_KEEP" -gt "$PARTITION_COUNT" ];then
            exit_error "Can't keep more partitions then available"
        fi
    fi

    milestone "Create partitions on $DEV ..."

    # Set the size of the USER Partition according to
    # the total available space
    sector_to_MB TOTAL_SIZE_M "$TOTAL_SIZE_S"

    # Set the default user partition size depending on the emmc size
    get_user_part_size "$TOTAL_SIZE_M"

    ALIGNMENT_OVERHEAD_S=20000 # To have the last partition the size we want, or more, we need to
                                 # take some overhead into account.
    SIZE_FILLED_S=$(( ALIGNMENT_OVERHEAD_S ))

    # Convert everything to sectors and sum up
    MB_to_sector PART_FNGSYSTEM_SIZE_DEFAULT_S $PART_FNGSYSTEM_SIZE_DEFAULT
    MB_to_sector PART_A_BOOT_SIZE_DEFAULT_S $PART_A_BOOT_SIZE_DEFAULT
    MB_to_sector PART_B_BOOT_SIZE_DEFAULT_S $PART_B_BOOT_SIZE_DEFAULT
    MB_to_sector PART_USER_SIZE_DEFAULT_S "$PART_USER_SIZE_DEFAULT"

    SIZE_FILLED_S=$((SIZE_FILLED_S + PART_FNGSYSTEM_SIZE_DEFAULT_S))
    SIZE_FILLED_S=$((SIZE_FILLED_S + PART_A_BOOT_SIZE_DEFAULT_S))
    SIZE_FILLED_S=$((SIZE_FILLED_S + PART_USER_SIZE_DEFAULT_S))
    $AB_PARTITIONING && SIZE_FILLED_S=$((SIZE_FILLED_S + PART_B_BOOT_SIZE_DEFAULT_S))

    if [ "$PART_SCHEME_NEW" -eq 1 ];then
        SIZE_FILLED_S=$((SIZE_FILLED_S + 8 )) # FNGSYSTEM_START_S
    fi

    if [ "$PART_SCHEME_NEW" -eq 2 ];then
        BOOTLOADER_START_S=$(( BOOTLOADER_SEEK_USER * 1024 / SECTOR_SIZE ))
        BOOTLOADER_ENV_START_S=$(( BOOTLOADER_ENV_START / SECTOR_SIZE ))
        PART_BOOTLOADER_SIZE_DEFAULT_S=$(( BOOTLOADER_ENV_START_S - BOOTLOADER_START_S - 2 ))
        MB_to_sector PART_BOOTLOADER_EXTRA_SIZE_DEFAULT_S $PART_BOOTLOADER_EXTRA_SIZE_DEFAULT
        MB_to_sector PART_CONFIG_SIZE_DEFAULT_S $PART_CONFIG_SIZE_DEFAULT

        SIZE_FILLED_S=$((SIZE_FILLED_S + BOOTLOADER_START_S ))
        SIZE_FILLED_S=$((SIZE_FILLED_S + PART_BOOTLOADER_SIZE_DEFAULT_S))
        SIZE_FILLED_S=$((SIZE_FILLED_S + PART_BOOTLOADER_EXTRA_SIZE_DEFAULT_S))
        SIZE_FILLED_S=$((SIZE_FILLED_S + PART_CONFIG_SIZE_DEFAULT_S))

        # Check if the bootloader partition is small enough to fit before
        # the environment partition
        if [ $(( BOOTLOADER_START_S + PART_BOOTLOADER_SIZE_DEFAULT_S)) \
            -ge $(( BOOTLOADER_ENV_START_S )) ];
        then
            error_msg "Bootloader partition and u-boot environment partition do overlap."
            $FORCE || exit 1
        fi
    fi

    ROOTFS_SIZE_S=$(( TOTAL_SIZE_S - SIZE_FILLED_S ))

    $AB_PARTITIONING && ROOTFS_SIZE_S=$(( ROOTFS_SIZE_S / 2 ))

    sector_to_MB ROOTFS_SIZE_M "$ROOTFS_SIZE_S"
    if [ "$ROOTFS_SIZE_M" -lt "$PART_A_ROOT_SIZE_MIN" ];then
        error_msg "The resulting rootfs size is too small: only $ROOTFS_SIZE_M"M left.
        $FORCE || exit 1
    fi

    SFDISK_DRY="";
    # -n is sfdisk dry run (--no-act, but the long option is not supported in i
    # FNGSystem 15)
    [ -n "$DRYRUN" ] && SFDISK_DRY="-n"

    if [ -n "$PART_TABLE_TYPE_PARAM" ];then
        PART_TABLE_TYPE="$PART_TABLE_TYPE_PARAM"
    else
        PART_TABLE_TYPE="mbr"
    fi

    # Compatibility for old sfdisk
    SFDISK_VERSION=$(get_sfdisk_version)
    SFDISK_APPEND=""
    if [ "$SFDISK_VERSION" -le 225 ];
    then
        SFDISK_APPEND="--force" # Old sfdisk complains about stupid cylinder boundry issues
        SFDISK_LABEL=""         # Old sfdisk does not support disk labels
    else
        SFDISK_LABEL="label:$(echo "$PART_TABLE_TYPE" | tr '[:upper:]' '[:lower:]' | sed 's/mbr/dos/')"
    fi

    if [ "$PART_TABLE_TYPE" = "mbr" ];then
        BOOTLOADER_PART_T="f8"
        BOOTLOADER_ENV_PART_T="f8"
        CONFIG_PART_T="b"
        FNGSYSTEM_START_S="8"
        FNGSYSTEM_PART_T="b"
        PART_A_BOOT_T="b"
        PART_B_BOOT_T="b"
        ROOTFS_T="83"
        PART_USER_T="83"
        PART_EXTENDED_T="E"
        PART_EXTENDED_S=""
    elif [ "$PART_TABLE_TYPE" = "gpt" ];then
        BOOTLOADER_PART_T="$GUID_BIOS"
        BOOTLOADER_ENV_PART_T="$GUID_BIOS"
        CONFIG_PART_T="$GUID_EFI_SYSTEM"
        FNGSYSTEM_START_S="2048"
        FNGSYSTEM_PART_T="$GUID_EFI_SYSTEM"
        PART_A_BOOT_T="$GUID_EFI_SYSTEM"
        PART_B_BOOT_T="$GUID_EFI_SYSTEM"
        ROOTFS_T="$GUID_LINUX_FS"
        PART_USER_T="$GUID_LINUX_FS"
        PART_EXTENDED_T="$GUID_MS_RESERVED"
        PART_EXTENDED_S="2"
    else
        exit_error "Unknown PART_TABLE_TYPE ($PART_TABLE_TYPE)."
    fi

    CURRENT_PART_TABLE_TYPE=$(get_part_table_type)
    if [ -n "$CURRENT_PART_TABLE_TYPE" ];then
        info_msg "Found '$CURRENT_PART_TABLE_TYPE' partition table."
    fi

    # Destroy GPT data structures when switching back from GPT to MBR.
    # This is needed to correctly remove the secondary GPT header.
    if [ "$CURRENT_PART_TABLE_TYPE" = "gpt" ] && [ "$PART_TABLE_TYPE" = "mbr" ];then
        if command -v "sgdisk" 1>/dev/null;then
            $DRYRUN sgdisk --zap "$DEV"
        elif command -v "wipefs" 1>/dev/null;then
            $DRYRUN wipefs -a "$DEV"
        else
            warn_msg "Neither sgdisk nor wipefs is found."\
                     "The secondary (backup) GPT header will not be removed."\
                     "Some tools, such as sgdisk, may complain about this."
        fi
    fi

    # If there is no partition table, create a new one. Without partition table it will
    # not be possible to parse "parted" output due to error "unrecognised disk label".
    if [ "$CURRENT_PART_TABLE_TYPE" = "none" ];then
        PARTED_LABEL="$(echo "$PART_TABLE_TYPE" | tr '[:upper:]' '[:lower:]' | sed 's/mbr/msdos/')"
        parted -s "$DEV" mktable "$PARTED_LABEL" 2>/dev/null
    fi


    { # Start to write stdout to the 'partitions' file
        if [ "$_KEEP" -gt 0 ];then
            if [ "$SFDISK_VERSION" -le 225 ];
            then
                # The old sfdisk does not now --delete and --append, so I need to
                # add the partitions to keep to the partition file, with the same sectors as already on disk
                p=0
                while [ "$p" -lt "$_KEEP" ];
                do
                    get_partition_data N ST SI T B "$p" "$PARTITIONS"
                    append_partition "$ST" "$SI" "$T" "$B"
                    p=$(( p + 1 ))
                done
            else
                DELETE_PARTS=$( seq -s ' ' $PARTITION_COUNT -1 $(( _KEEP + 1 ))  )
                SFDISK_APPEND="--append"
                # Set the start sector do we continue at the existing table
                get_partition_data N ST SI T B "$(( _KEEP - 1))" "$PARTITIONS"
                PARTITION_NEXT_START_SECTOR=$(( ST + SI + 1 ))
            fi
        else
            DELETE_PARTS=""
        fi
        #=============================================
        # Write partition file for sfdisk
        #=============================================

        # In the first partition scheme, the bootloader is located in the eMMC boot#0 area.
        # This scheme isn't applicable to SD-Cards, because they don't have the required boot areas.
        printf "%s\n" "$SFDISK_LABEL"
        if [ "$PART_SCHEME_NEW" -eq 1 ];then
            [ 1 -gt $_KEEP ] && append_partition "$FNGSYSTEM_START_S" "$PART_FNGSYSTEM_SIZE_DEFAULT_S" \
                                                 "$FNGSYSTEM_PART_T"                                       # Flash-N-Go system
            [ 2 -gt $_KEEP ] && append_partition ""  "$PART_A_BOOT_SIZE_DEFAULT_S" "$PART_A_BOOT_T"        # First Boot partition

            if $AB_PARTITIONING; then
                [ 3 -gt $_KEEP ] && append_partition "" "$PART_B_BOOT_SIZE_DEFAULT_S" "$PART_B_BOOT_T"    # Second boot partition
                [ 4 -gt $_KEEP ] && append_partition "" "$PART_EXTENDED_S" "$PART_EXTENDED_T"             # Extended partition
                [ 5 -gt $_KEEP ] && append_partition "" "$ROOTFS_SIZE_S" "$ROOTFS_T"                      # First rootfs partition

                if [ "$PART_USER_SIZE_DEFAULT_S" -eq 0 ];then
                    [ 6 -gt $_KEEP ] && append_partition "" "" "$ROOTFS_T"               # Second root partition, take the rest
                else
                    [ 6 -gt $_KEEP ] && append_partition "" "$ROOTFS_SIZE_S" "$ROOTFS_T" # Second rootfs partition
                    [ 7 -gt $_KEEP ] && append_partition "" "" "$PART_USER_T"            # User FS Partition is the last, take the rest
                fi
            else
                if [ "$PART_USER_SIZE_DEFAULT_S" -eq 0 ];then
                    [ 3 -gt $_KEEP ] && append_partition "" "" "$ROOTFS_T"               # First root partition, take the rest
                else
                    [ 3 -gt $_KEEP ] && append_partition "" "$ROOTFS_SIZE_S" "$ROOTFS_T" # First rootfs partition
                    [ 4 -gt $_KEEP ] && append_partition "" "" "$PART_USER_T"            # User FS Partittion is the last, take the rest
                fi
            fi

        # In the second partition scheme, the bootloader is located at the beginning of the user
        # data area. This also works on SD-Cards (which don't have separate boot areas).
        elif [ "$PART_SCHEME_NEW" -eq 2 ];then

            # Specify the first usable sector for GPT partitions (default: 2048).
            # We need this to create U-Boot partition at BOOTLOADER_SEEK offset.
            if [ "$PART_TABLE_TYPE" = "gpt" ];then
                echo "first-lba: 34"
            fi

            [ 1 -gt $_KEEP ] && append_partition "$BOOTLOADER_START_S" "$PART_BOOTLOADER_SIZE_DEFAULT_S" \
                                                 "$BOOTLOADER_PART_T"                                     # U-Boot
            [ 2 -gt $_KEEP ] && append_partition "$BOOTLOADER_ENV_START_S" "$PART_BOOTLOADER_EXTRA_SIZE_DEFAULT_S" \
                                                 "$BOOTLOADER_ENV_PART_T"                                 # U-Boot environment
            [ 3 -gt $_KEEP ] && append_partition "" "$PART_CONFIG_SIZE_DEFAULT_S" "$CONFIG_PART_T"        # Config partition, xml ...
            [ 4 -gt $_KEEP ] && append_partition "" "$PART_EXTENDED_S" "$PART_EXTENDED_T"                 # Extended partition
            [ 5 -gt $_KEEP ] && append_partition "" "$PART_FNGSYSTEM_SIZE_DEFAULT_S" "$FNGSYSTEM_PART_T"  # Flash-N-Go system
            [ 6 -gt $_KEEP ] && append_partition "" "$PART_A_BOOT_SIZE_DEFAULT_S" "$PART_A_BOOT_T"        # First Boot partition

            if $AB_PARTITIONING; then
                [ 7 -gt $_KEEP ] && append_partition "" "$PART_B_BOOT_SIZE_DEFAULT_S" "$PART_B_BOOT_T"    # Second boot partition
                [ 8 -gt $_KEEP ] && append_partition "" "$ROOTFS_SIZE_S" "$ROOTFS_T"                      # First rootfs partition

                if [ "$PART_USER_SIZE_DEFAULT_S" -eq 0 ];then
                    [ 9 -gt $_KEEP ] && append_partition "" "" "$ROOTFS_T"               # Second root partition, take the rest
                else
                    [ 9 -gt $_KEEP ] && append_partition "" "$ROOTFS_SIZE_S" "$ROOTFS_T" # Second rootfs partition
                    [ 10 -gt $_KEEP ] && append_partition "" "" "$ROOTFS_T"              # User FS Partition is the last, take the rest
                fi
            else
                if [ "$PART_USER_SIZE_DEFAULT_S" -eq 0 ];then
                    [ 7 -gt $_KEEP ] && append_partition "" "" "$ROOTFS_T"               # First root partition, take the rest
                else
                    [ 7 -gt $_KEEP ] && append_partition "" "$ROOTFS_SIZE_S" "$ROOTFS_T" # First rootfs partition
                    [ 8 -gt $_KEEP ] && append_partition "" "" "$ROOTFS_T"               # User FS Partittion is the last, take the rest
                fi
            fi
        else
            exit_error "Unknown partition scheme."
        fi
    } > "$TMPDIR/partitiontable"

    umount_emmc

    #===============================================================
    # delete partitions that should be recreated
    #===============================================================
    if [ "$SFDISK_VERSION" -gt 225 ];then # The old sfdisk does not support --delete
        if [ -n "$DELETE_PARTS" ];then
            milestone "Deleting partitions '$DELETE_PARTS' from $DEV"
            [ -z "$DRYRUN" ] || $DRYRUN sfdisk $SFDISK_DRY $FORCE_SFDISK -uS "$DEV" --delete "$DELETE_PARTS"
            sfdisk $SFDISK_DRY $FORCE_SFDISK -uS "$DEV" --delete "$DELETE_PARTS"
            sleep 1 # 0.X is not supported by FNGSystem 15, was 0.2
        fi
    fi
    #===============================================================
    milestone "Writing new partitions to $DEV"
    if [ -n "$DRYRUN" ];then
        $DRYRUN sfdisk $SFDISK_DRY $FORCE_SFDISK $SFDISK_APPEND -uS "$DEV < $(cat "$TMPDIR/partitiontable")"
    fi
    sfdisk $SFDISK_DRY $FORCE_SFDISK $SFDISK_APPEND -uS "$DEV"  < "$TMPDIR/partitiontable"
    sleep 2

    if ! analyze_partitions "/dev/$TARGETDEVICE";then
        exit_error "Something went wrong in the partitioning step."
    fi
}

partition_write_access()
{
    _part="$1"
    _value=0
    [ "$2" = "disable" ] && _value=1
    _force_ro_path="/sys/block/${_part#/dev/}/force_ro"
    if string_contains "$_part" "boot";then
        $DRYRUN echo "$_value" > "$_force_ro_path"
    fi
}

#====================================================
# Restore config partition content
#
# Checks if the partition can be mounted, if not create fs
# Mount the partition, copy content from temp folder bak into it
# Make sure that the correct partition is has the CONFIG label
#
#====================================================
restore_config_partition()
{
    mkdir -p /etc/shared # Just in case we don't have the mountpoint yet
    mount "$PART_CONFIG" /etc/shared || true

    # Create file system on config partition if needed
    if ! grep -q "$PART_CONFIG" /proc/mounts;then
        partition_write_access "$PART_CONFIG"

        if ! fsck.vfat -a "$PART_CONFIG" 1>/dev/null 2>&1 && \
           ! fsck.vfat "$PART_CONFIG" 1>/dev/null 2>&1; then

            milestone "Creating filesystem on config partition..."
            $DRYRUN mkfs.vfat -n CONFIG -F 12 "$PART_CONFIG"
            PART_CONFIG_FORMATED=true
            sync
            sleep 1 # 0.X is not supported by FNGSystem 15, was 0.2
        fi

        mount "$PART_CONFIG" /etc/shared || true
    fi

    # when the device for the config partition has changed
    # relabel the old device
    CONFIG_PARTITIONS=$( blkid "$DEV"* )
    OLDIFS=$IFS
    IFS="$NEWLINE"
    for p in $CONFIG_PARTITIONS;do
        if string_begins_with "$p" "$PART_CONFIG";then
            if ! string_contains "$p" 'LABEL="CONFIG"';then
                partition_write_access "$PART_CONFIG"
                if [ -z "$DRYRUN" ];then # The normal DRYRUN way does not work here, because of changed IFS
                    if command -v dosfslabel 1>/dev/null;then
                        dosfslabel "$PART_CONFIG" "CONFIG"
                    elif command -v mlabel 1>/dev/null;then
                        mlabel -i "$PART_CONFIG" "::CONFIG"
                    fi
                else
                    error_text "DRYRUN Skipping: dosfslabel $PART_CONFIG CONFIG"
                fi
            fi
            continue;
        fi
        if string_contains "$p" 'LABEL="CONFIG"';then
            # Additional CONFIG Partition, needs relabeling
            OLD_CONFIG_PART="${p%%:*}"
            partition_write_access "$OLD_CONFIG_PART"
            if [ -z "$DRYRUN" ];then # The normal DRYRUN way does not work here, because of changed IFS
                if command -v dosfslabel 1>/dev/null;then
                    dosfslabel "$OLD_CONFIG_PART" "CONFIGBAK"
                elif command -v mlabel 1>/dev/null;then
                    mlabel -i "$OLD_CONFIG_PART" "::CONFIGBAK"
                fi
            else
                error_text "DRYRUN Skipping: dosfslabel $OLD_CONFIG_PART CONFIGBAK"
            fi
        fi
    done
    IFS=$OLDIFS

    if $PART_CONFIG_FORMATED || [ "$PART_CONFIG_OLD" != "$PART_CONFIG" ];
    then
        if [ -d "$TMPDIR/shared" ];then
            $DRYRUN cp -r -f "$TMPDIR/shared/" /etc/
        fi
    fi
}

backup_config_partition()
{
    # Preserve config partition content
    if [ -d /etc/shared ];then
        if [ $VERBOSE -ge 2 ];then
            cp -r -v /etc/shared "$TMPDIR"
        else
            cp -r /etc/shared "$TMPDIR"
        fi
    fi
    PART_CONFIG_OLD="$PART_CONFIG"
}

#==================================================
# Checks if the config partition can be used for
# writing. If not a filesystem is created, so the
# post-install scripts may read or modify it
#==================================================
check_and_prepare_config_partition()
{
    if $PART_CONFIG_OK;then
        milestone "Checking filesystem on config partition $PART_CONFIG"
        # Do a check and try to repair, like dirty bit or other minor
        # stuff. Do a check again
        if ! grep -q "$PART_CONFIG" /proc/mounts;then
            if ! fsck.vfat -a "$PART_CONFIG" 2>/dev/null && \
               ! fsck.vfat "$PART_CONFIG" 2>/dev/null; then

                info_msg "WARNING: Filesystem on config partition $PART_CONFIG is broken. Will be reformated."

                FORMAT_CONFIG=true
            fi
        fi
    else
        FORMAT_CONFIG=true
    fi

    # Create this here, so the config can be filled by a post install script
    if $FORMAT_CONFIG
    then
        milestone "Creating  filesystem on config partition..."

        # If the partition wasn't found at all we need to assume defaults
        if [ -z "$PART_CONFIG" ];then
            DEFAULT_PART_CONFIG="${DEV}boot1"
            [ "$PART_SCHEME" -eq 2 ] && DEFAULT_PART_CONFIG="${DEV}p3"
            PART_CONFIG="$DEFAULT_PART_CONFIG"
            dont_keep_partition "$PART_CONFIG"
        fi

        partition_write_access "$PART_CONFIG"
        if [ -b "$PART_CONFIG" ];then
            umount /etc/shared || umount -l /etc/shared || true
            $DRYRUN mkfs.vfat -n CONFIG -F 12 "$PART_CONFIG"
            PART_CONFIG_FORMATED=true
        fi
    fi
}

format_user_partition()
{
    if [ -n "$PART_USER" ]; then
        # Skip if the installation is probably triggered from user partition
        if $FILES_ON_TARGETDEV && mount | grep -q "$PART_USER"; then
            info_msg "The user partition is mounted, skip formatting"
            return 0
        fi

        # If the user partition was just created, no filesystem is present
        # on the partition. We therefore check the filesystem, but assume
        # a newly created partition if the check fails.
        CURRENT_USER_PART_FSTYPE=""

        # The first parameter is the name of the return value variable
        if get_partition_fstype 'CURRENT_USER_PART_FSTYPE' "$PART_USER"; then
            info_msg "Unable to read filesystem info from ${PART_USER}"
        fi

        info_msg "User partition: $PART_USER ($CURRENT_USER_PART_FSTYPE / $USERFS_TYPE)"

        if [ -z "$CURRENT_USER_PART_FSTYPE" ] || [ "$CURRENT_USER_PART_FSTYPE" != "$USERFS_TYPE" ] || \
                [ "$REPARTITION_NEEDED" = "true" ] || [ "$FORMAT_PARTITIONS" = "true" ]; then
            # The label is already contained in the USERFS_MKFSPARAM variable
            # shellcheck disable=SC2086
            $DRYRUN mkfs."$USERFS_TYPE" $USERFS_MKFSPARAM "$PART_USER" \
                || exit_error "Failed to format partition $PART_USER"
            # @TODO The wait_for_mmc function doesn't work correctly at the
            # moment. Disable the function and sleep for 2 seconds instead.
            #wait_for_mmc "$DEV"
            sleep 2
        fi
    fi
}
#====================================================

#====================================================
# Tries to read the current u-boot environment
# and stores it in the given file
# Return parameters are:
#  return code i
#         0: reading was successful, 
#         1: access failed
#         2: fw_printenv is not available
#  $1: file name to store the results
#====================================================
u_boot_env_backup()
{
    if ! command -v fw_printenv 1>/dev/null;then
       warn_msg "fw_printenv is not available, skipping backup if u-boot environment" 
    fi
    if fw_printenv -f none 2>/dev/null > "$1";then
        return 0
    else
        # The normal access failed, try out different sizes and locations

        u_boot_env_backup_partitions="mmcblk0 mmcblk0p2 mmcblk0p1 mmcblk1 mmcblk1p2 mmcblk1p1"
        u_boot_env_backup_offsets="0x400000 0x0 0x800000"
        u_boot_env_backup_sizes="0x1000 0x2000 0x4000 0x8000"

        # workarround fw_printenv bugs, simple compare as we only have 0.3.1
        # and may be newer in use
        if [ "$(fw_printenv -V)" = "0.3.1" ];then
            FW_PRINTENV_WORKARROUND=true

            # if the PARTITIONS variable is not set, we need to call analyze partitions
            # normally this is done during the installation anyway
            if [ -z "$PARTITIONS" ] ;then
                analyze_partitions
            fi
        fi

        conffile=$(mktemp)
        for p in $u_boot_env_backup_partitions;do
            # skip if the partition does not exists ( is not a blockdevice ) 
            p="/dev/$p"
            [ -b "$p" ] || continue
            if $FW_PRINTENV_WORKARROUND;then
                if get_partition_number num "$p";then
                    if ! get_partition_data P ST SI T B "$num" "$PARTITIONS";then
                        size=$(( 0x10000000 )) # Just any big size, bigger then the maximal offset to skip the check below, converted to decimal
                    fi
                    sector_to_B size "$SI"
                else
                    size=$(( 0x10000000 )) # Just any big size, bigger then the maximal offset to skip the check below, converted to decimal
                fi
            fi

            for o in $u_boot_env_backup_offsets;do
                # skip if offset is behind end of partition 
                [ "$(( o ))" -ge "$size" ] && continue
                for s in $u_boot_env_backup_sizes;do
                    # skip if offset is behind end of partition 
                    # due to a bug in libubootenv 3.1 fw_printenv hangs forever in
                    # this case, so we need an additional check here
                    [ "$(( o + s ))" -ge "$size" ] && continue

                    # Write a temporary config file
                    echo "$p    $o       $s" > "$conffile"
                    if fw_printenv -f none -c "$conffile" 2>/dev/null  > "$1";
                    then
                        return 0
                    fi
                done
            done
        done
    fi
    info_msg "Old u-boot environment could not be found, skipping backup"
    return 1
}

#====================================================
# Simple way to read partition offset and size from the 
# fw_env.conf
# First entry not starting with a comment is taken
# $1: return variable name for the partition
# $2: return variable name for the offset
# $3: return variable name for the size
# $4: filename
#====================================================
fw_env_conf_decode()
{
    fw_env_conf_decode_part="$1"
    fw_env_conf_decode_offset="$2"
    fw_env_conf_decode_size="$3"
    if [ ! -r "$4" ];then
        echo "Failed to read $4"
        return 1
    fi
    while read -r line;do
        [ -z "$line" ] && continue # Forward empty lines
        string_begins_with "$line" "#" && continue # Just forward on comments
        break
    done < "$4"
    if [ -z "$line" ];then
        echo "Failed to read config from $4"
        return 1
    fi

    OLDIFS="$IFS"
    IFS=" "

    # shellcheck disable=2086
    set -- $line
    IFS="$OLDIFS"
    
    eval "$fw_env_conf_decode_part=$1"
    eval "$fw_env_conf_decode_offset=$2"
    eval "$fw_env_conf_decode_size=$3"
    return 0
}
#=========================
# fnginstall common
# End of include file 
#=========================

# We don't want to override the fngsystem partition
# with the yocto installation so a smaller value
# then during the fngsystem installation is OK here
# shellcheck disable=2034
PART_FNGSYSTEM_SIZE_MIN=62 # Allow here a smaller partition as during the fng system installation

ROOTFS_DEFAULT='seconorth-image-seco-mx8mp.tar.gz'
# The next is only needed to use the install script for debugging
# directly from the source folder
if string_contains "$ROOTFS_DEFAULT" "ROOTFS@"
then
    # Use single quote to expand "machine" later when set
    #shellcheck disable=2016
    ROOTFS_DEFAULT='seconorth-image-${MACHINE}.tar.gz'
fi
ROOTFS=""
DEVICETREE=""
BOOTSCRIPT=""
IMAGE=""
BACKGROUNDCOLOR=""
ROOTFS_TYPE="ext4"
ALLOW_INSTALLATION_IN_YOCTO=true

#======================================================================
# Normaly we don't retry
TOTAL_RETRIES=1

# Fillup internal variables
DEVICETREEOV=""
RPM=""
PostInstallScript=""
PreInstallScript=""
TFTPFILES=""
USERPARTITION=""
USERSCRIPT=""
ERASERPM=""
AB_PARTITION_TARGET_PARAM=""

#=====================================================================
TARGETROOT="/mnt/target"
TARGETTMP="/var/volatile/"
ABSTARGETTMP="${TARGETROOT}${TARGETTMP}"
POSTINSTALL_STATUS_FILE="/tmp/yocto-postinstall-status"

AB_PARTITIONING_FOUND=false
AB_PARTITIONING_TARGET="A"
#======================================================================

parse_extra_args()
{
    ret=1
    case $1 in
        -b|--BS|-b=*|--BS=*)
            parse_args_value BOOTSCRIPT "$1" "$2" || ret=2
            BOOTSCRIPT="${SUBDIR}$BOOTSCRIPT"
            ;;
        --BackgroundColor|--BackgroundColor=*)
            parse_args_value BACKGROUNDCOLORParam "$1" "$2" || ret=2
            BACKGROUNDCOLOR="$BACKGROUNDCOLORParam"
            ;;
        -d|--DTB|-d=*|--DTB=*)
            parse_args_value DTBParam "$1" "$2" || ret=2
            DEVICETREE="${SUBDIR}$DTBParam"
            ;;
        --DTBO|--DTBO=*)
            parse_args_value DTBOV "$1" "$2" || ret=2
            DEVICETREEOV="$DEVICETREEOV ${SUBDIR}$DTBOV"
            ;;
        -i|--Image|-i=*|--Image=*)
            parse_args_value IMAGE "$1" "$2" || ret=2
            IMAGE="${SUBDIR}$IMAGE"
            ;;
        -r|--RPM|-r=*|--RPM=*)
            parse_args_value RPMParam "$1" "$2" || ret=2
            RPM="$RPM ${SUBDIR}$RPMParam"
            ;;
        --PostInstallScript|--PostInstallScript=*)
            parse_args_value POSTINSTALLSCRIPTParam "$1" "$2" || ret=2
            PostInstallScript="$PostInstallScript ${SUBDIR}$POSTINSTALLSCRIPTParam"
            ;;
        --PreInstallScript|--PreInstallScript=*)
            parse_args_value PREINSTALLSCRIPTParam "$1" "$2" || ret=2
            PreInstallScript="$PreInstallScript ${SUBDIR}$PREINSTALLSCRIPTParam"
            ;;
        -t|--TFTP|--FTP|-t=*|--TFTP=*|--FTP=*)
            parse_args_value TFTPParam "$1" "$2" || ret=2
            TFTPFILES="$TFTPFILES ${SUBDIR}$TFTPParam"
            ;;
        -f|--FS|-f=*|--FS=*)
            parse_args_value ROOTFSParam "$1" "$2" || ret=2
            ROOTFS="$SUBDIR$ROOTFSParam"
            ;;
        -s|--UserScript|-s=*|--UserScript=*)
            parse_args_value USERSCRIPTParam "$1" "$2" || ret=2
            USERSCRIPT="${USERSCRIPT} ${SUBDIR}$USERSCRIPTParam"
            ;;
        -e|--EraseRPM|-e=*|--EraseRPM=*)
            parse_args_value ERASERPMParam "$1" "$2" || ret=2
            ERASERPM="${ERASERPM} $ERASERPMParam"
            ;;
        --AB-target|--AB-target=*)
            $is_fng_system || param_error "--AB-target does only work on FNG-System."
            parse_args_value AB_PARTITION_TARGET_PARAM "$1" "$2" || ret=2
            case $AB_PARTITION_TARGET_PARAM in
                A|B) AB_PARTITIONING_PARAM=true;;
                *) param_error "--AB-target has to be A or B"
            esac
            ;;
        #TODO these may be global
        --retry|--retry=*)
            parse_args_value TOTAL_RETRIES "$1" "$2" || ret=2
            ;;

        # TODO will go away
        -k|--KeepPartitions)
            KEEP_PARTITIONS="true"
            ;;
        *)  return 0 ;;
    esac
    return "$ret"
}

#========================================
# Cleanup on exit
#========================================
cleanup()
{
    trap nop EXIT
    trap nop INT
    info_msg
    info_msg "Cleanup workspace..."
    umount_emmc

    rmdir "$TMP_MOUNT_POINT" 2>/dev/null

    # Re-enable automounter
    if [ -e /usr/sbin/automount.sh ];then
        chmod +x /usr/sbin/automount.sh
    fi

    restore_mount
}


#===========================================================
# Mounts the target os and prepares it for change
# into it.
# the tmp folder in the target os is created if it
# does not exists.
#===========================================================
mounttarget()
{
    # TODO create common functions to prepare chroot
    mkdir -p ${TARGETROOT}
    mount "${TARGET_PARTITION_ROOT}" ${TARGETROOT}
    mkdir -p $TARGETROOT/boot
    mount "${TARGET_PARTITION_BOOT}" ${TARGETROOT}/boot

    #shellcheck disable=2174
    mkdir -m 1777 -p $ABSTARGETTMP
    mount -t tmpfs none $ABSTARGETTMP
    mkdir $ABSTARGETTMP/log
    mkdir $ABSTARGETTMP/tmp
    mkdir -p $TARGETROOT/tmp
    mkdir -p $TARGETROOT/var/tmp
    mount -o bind /tmp $TARGETROOT/tmp
    mount -o bind /tmp $TARGETROOT/var/tmp

    mkdir -p $TARGETROOT/var/tmp

    mkdir -p $TARGETROOT/etc/shared

    mount -t proc proc ${TARGETROOT}/proc/
    mount -t sysfs sys ${TARGETROOT}/sys/
    mount -o bind /dev ${TARGETROOT}/dev/


    if ! mount -L CONFIG "${TARGETROOT}/etc/shared" 2>/dev/null;
    then
        if [ -z "$PART_CONFIG" ];then
            PART_CONFIG=$(blkid -L CONFIG)
        fi
        if [ -n "$PART_CONFIG" ];then
            mount "$PART_CONFIG" "${TARGETROOT}/etc/shared" || true
        fi
    fi

}

#===========================================================
# Unmounts the target os when mounted with 'mounttarget'
# Deletes the tmp folder if it was created before
#===========================================================
unmounttarget()
{
    umount $TARGETROOT/etc/shared || true
    umount $TARGETROOT/var/tmp || true
    umount $TARGETROOT/tmp || true
    umount $ABSTARGETTMP || true
    umount ${TARGETROOT}/proc/ || true
    umount ${TARGETROOT}/sys/ || true
    umount ${TARGETROOT}/dev/ || true
    umount ${TARGETROOT}/boot || true
    umount ${TARGETROOT} || true
}

#=========================================
# Executes rpm installation given with
#   -r|--RPM  after change root in the
# target os
#=========================================
rpm_install()
{
    RPM_FILES=$*

    if [ -z "$RPM_FILES" ] ; then return 0; fi

    info_msg "Installing following RPM files: ${RPM} ..."

    mounttarget

    cd $ABSTARGETTMP
    for f in $RPM_FILES; do
        sync
        do_load "$f"
        RPMBASES="$RPMBASES $(basename "$f")"
    done

    echo "#!/bin/bash" > $ABSTARGETTMP/rpminstall
    echo "cd ${TARGETTMP}" >> $ABSTARGETTMP/rpminstall
    echo "rpm -i $RPMBASES" >> $ABSTARGETTMP/rpminstall
    chmod a+x $ABSTARGETTMP/rpminstall

    chroot ${TARGETROOT} bash ${TARGETTMP}/rpminstall

    cd -
    milestone "Finished writing rpms"
    unmounttarget
    umount_emmc
}

#=========================================
# Executes rpm removal given with
#   -e|--EraseRPM  after change root in the
# target os
#=========================================
rpm_erase()
{
    RPM_FILES=$*

    if [ -z "$RPM_FILES" ] ; then return 0; fi

    info_msg "Removing following RPM packages: ${RPM_FILES} ..."

    mounttarget

    cd $ABSTARGETTMP
    echo "#!/bin/bash" > $ABSTARGETTMP/rpmerase
    echo "cd ${TARGETTMP}" >> $ABSTARGETTMP/rpmerase
    echo "rpm -e $RPM_FILES" >> $ABSTARGETTMP/rpmerase
    chmod a+x $ABSTARGETTMP/rpmerase

    chroot ${TARGETROOT} bash ${TARGETTMP}/rpmerase

    cd -
    milestone "Finished erasing rpms"
    unmounttarget
    umount_emmc
}

#=================================================
# Install arbitrary files given with -t|--TFTP
#  (Format "<file>:<location>" where
#  location can be a filename or an existing dir.
#=================================================
set_mode()
{
    FILE="$1"
    MODE="$2"
    OWNER="$3"
    GROUP="$4"
    if [ -n "$MODE" ];then
        chmod "$MODE ${FILE}"
    fi
    if [ -n "$OWNER" ];then
        chown "$OWNER" "${FILE}"
    fi
    if [ -n "$GROUP" ];then
        chgrp "$GROUP" "${FILE}"
    fi
}

tftp_install()
{
    TFTP_FILES=$*

    if [ -z "$TFTP_FILES" ]; then
        return 0;
    fi

    emmc_mount
    for t in $TFTP_FILES ;
    do
        #split by ':'
        OLDIFS="$IFS"
        IFS=':'
        # shellcheck disable=2086
        set -- $t
        IFS="$OLDIFS"
        FILE="$1"
        LOCATION="$2"

        if [ -n "$FILE" ] && [ -n "$LOCATION" ]; then
            if [ -d "$LOCATION" ]; then
                ( # subshell to preserve current directory
                    cd  "${LOCATION}"
                    do_load "${FILE}"
                    set_mode "$FILE" "$3" "$4" "$5"
                ) || exit $?
            else
                milestone "Loading ${FILE} to ${LOCATION}..."
                mkdir -p "$(dirname "${LOCATION}" )"
                do_load "${FILE}" "${LOCATION}"
                set_mode "$LOCATION" "$3" "$4" "$5"
            fi
        fi
    done
    umount_emmc
    sync
}

#=========================================
# Executes 'InstallScripts'  given with
# --[Pre|Post]InstallScripts in the Flash-N-Go
# environment
#=========================================
run_install_scripts()
{
    INSTALL_SCRIPTS=$*

    if [ -z "$INSTALL_SCRIPTS" ] ; then
        return 0
    fi

    info_msg "Try to execute SCRIPTS: ${INSTALL_SCRIPTS} ..."

    ( # subshell to preserve current directory
        TMP=$(mktemp -d)
        cd "$TMP"
        for f in $INSTALL_SCRIPTS; do
            sync
            milestone "Installing $f ..."
            do_load "$f"

            chmod +x "${TMP}/${f##*/}"
            # Run the script
            "${TMP}/${f##*/}"
            milestone "Execution of  $f finished."
        done
    ) || exit $?
    sync
}

#=========================================
# Executes 'postinstall' script if it exists
# in the rootfs
#=========================================
target_os_postinstall()
{
    # TODO create common function for this
    mounttarget
    TARGET_POSTINSTALL_SCRIPT="$(find \
        "${TARGETROOT}"/usr/local/sbin \
        "${TARGETROOT}"/usr/local/bin \
        "${TARGETROOT}"/usr/sbin \
        "${TARGETROOT}"/usr/bin \
        "${TARGETROOT}"/sbin \
        "${TARGETROOT}"/bin \
        \( -type f -o -type l \) \
        -name fnginstall-postinstallation 2> /dev/null | head -n 1)"
    if [ -n "$TARGET_POSTINSTALL_SCRIPT" ]; then
        ( # subshell to preserve current directory
            TARGET_POSTINSTALL_SCRIPT="${TARGET_POSTINSTALL_SCRIPT##"$TARGETROOT"}"
            milestone "Executing postinstall step $TARGET_POSTINSTALL_SCRIPT in target OS"
            cd $ABSTARGETTMP
            # postinstall script will save exit code to $POSTINSTALL_STATUS_FILE file
            chroot "${TARGETROOT}" sh -c " \
                VERBOSE='$VERBOSE' \
                ROOTPARTITION='$TARGET_PARTITION_ROOT' \
                BOOTPARTITION='$TARGET_PARTITION_BOOT' \
                $TARGET_POSTINSTALL_SCRIPT" 2>&1 | tee -a "$LOGFILE" >&5
            if [ "$(cat $POSTINSTALL_STATUS_FILE)" = "0" ]; then
                milestone "Executing postinstall step done."
            else
                error_msg "Executing postinstall $TARGET_POSTINSTALL_SCRIPT step failed."
            fi
        )
    else
        echo "No postinstallation scripts found on target rootfs."
        echo 0 > $POSTINSTALL_STATUS_FILE
    fi

    unmounttarget
    umount_emmc
    sync
}

#=========================================
# Executes 'user' install script given with
#  -s|--UserScript after change root in
# the target os
#=========================================
user_install()
{
    USER_SCRIPT=$*

    if [ -z "$USER_SCRIPT" ] ; then
        return 0
    fi

    info_msg "Try to execute USER_SCRIPT files: ${USER_SCRIPT} ..."

    mounttarget

    ( # subshell to preserve current directory
        cd $ABSTARGETTMP
        for f in $USER_SCRIPT; do
            sync
            do_load "$f"

            chmod a+x "$ABSTARGETTMP/${f##*/}"
            chroot "${TARGETROOT}" "${TARGETTMP}/${f##*/}"
            milestone "Installing $f finished."
        done
    ) || exit $?

    unmounttarget
    umount_emmc
    sync
}

#=========================================================
# Executed Tests:
#   After installation all partitions should be
#   mounted to detect problems with the created partitions.
#
#  Params: 1: device 2: filesystem type
#=========================================================
posttest_check_partition()
{
    p=$1
    fstype=$2

    mount -t "$fstype" "$p" "${TMP_MOUNT_POINT}" || exit_error "Mounting $p failed"

    mount | grep "${TMP_MOUNT_POINT}" | grep -q "type $fstype" ||
        exit_error "Wrong fstype seen, expected $fstype, mount returned:  $(mount | grep "${TMP_MOUNT_POINT}" )"

    if $FILES_ON_TARGETDEV;then
        umount_emmc
    else
        umount "$p" || exit_error "Unmounting $p failed."
    fi

    info_msg "Mounting $p was successful."
}


postinstallation_tests()
{
    milestone "Executing postinstallation test"

    partitions="${TARGET_PARTITION_BOOT}:vfat ${TARGET_PARTITION_ROOT}:$ROOTFS_TYPE"

    # If the script was called with the --UserPartition parameter
    # we also check the user partition
    if [ -n "$USERFS_TYPE" ] && [ -n "$PART_USER" ]; then
        partitions="$partititons ${PART_USER}:$USERFS_TYPE"
    fi

    for p in $partitions;
    do
        fstype=${p#*:};
        p=${p%:*};

        posttest_check_partition "$p" "$fstype"
    done

    milestone "Executing postinstallation test finished"
    return 0
}


#=========================================
# Tries to download # to see if the
# network connection is OK
# and the file is reachable
#=========================================
verify_file_access()
{
    # TODO this may also be a common function
    # Test if the download works, attempt to load the beginning of the rootfs file
    while true;
    do
        ROOTFS_TMP=$(mktemp) || exit_error "Failed to create temp file"

        OLDIFS=$IFS;IFS='|'
        # shellcheck disable=2086
        curl ${CURL_ARGS#|} --max-filesize 128000 --max-time 5 "${CURL_PREFIX}/${ROOTFS}" > "$ROOTFS_TMP" &
        PID=$!
        IFS=$OLDIFS

        # Timeout loop:
        # For the case where max-time and max-filesize do not work ( FNG System 15 and TFTP)
        # we still need the loop to kill the download
        cnt=0
        # stop when curl stopped anyway or after 5 seconds
        while kill -0 $PID && [ $cnt -lt 50 ];
        do
            usleep 100000
            cnt=$(( cnt  + 1 ))
        done
        kill $PID 2>/dev/null
        wait $PID
        ret=$?

        # Handle the 'links' returned by gitlab, actually textfiles containing the real name.
        if [ "$ret" -eq 0 ];then
            ROOTFS_HEADER=$( head -n 1 "$ROOTFS_TMP")
            if tar_compression "$ROOTFS_HEADER" -q;then
                ROOTFS="$( dirname "$ROOTFS")/$ROOTFS_HEADER"
                rm "$ROOTFS_TMP"
                continue # Try the download again with the new ROOTFS parameter
            fi
        fi
        rm "$ROOTFS_TMP"

        # Successful return values
        # 28 is the return code for --max-time timeout, which means that the download started but was not finished
        #   (the --max-filessize option does not always work, see manpage, and is ignored if not, so we need to have another way out).
        # 63 is the return code for max filesize exceeded, which means what a big file was found which is enough
        # 143 is the return code when the process was killed during a running download, which is good
        # 22 would be the not found case
        # 0 is also OK, when the filesize was ignored and the whole image was downloaded in max-time
        case "$ret" in
            143|63|28|0)
                return 0
        esac

        error_text
        error_msg " Download from \"${CURL_PREFIX}/${ROOTFS}\" failed."
        error_text

        if [ -z "$URL" ]
        then
            error_text "\"${ROOTFS}\" was not found in script directory."
            error_text "Make sure that all files belonging to the release are contained in the script"
            error_text "directory, or export URL variable to get files from  server."
        else
            error_text "Network connection does not seem to work."
            error_text "Make sure to set the correct address and path with export URL=..., or unset"
            error_text "URL/TFTP to use local files."
        fi
        exit 1
    done
}

#===================================================
# TODO rework using common partition functions
#===================================================
check_partitions()
{
    DEV="/dev/$TARGETDEVICE"

    # see if sfdisk is OK with the table
    if ! sfdisk -V -q ${DEV} 2>&1
    then
        error_msg "sfdisk can't read partition table"
        exit 1;
    fi

    # Check if we have at least the mandatory partitions and the size is OK
    size=$( sfdisk -l -uS ${DEV} | grep ${DEV}p1 | awk -v sectorsize="$SECTORSIZE" '{MB=$4*sectorsize/(1024*1024); print MB}')
    if [ -z "$size" ]; then
        error_msg "Missing Partition ${DEV}p1"
        exit 1
    fi

    size=$( sfdisk -l -uS ${DEV} | grep ${DEV}p2 | awk -v sectorsize="$SECTORSIZE" '{MB=$4*sectorsize/(1024*1024); print MB}')
    if [ -z "$size" ]; then
        error_msg "Missing Partition ${DEV}p2"
        exit 1
    fi
    if [ "$size" -lt "$BOOT_PART_SIZE"  ]; then
        error_msg "Partition ${DEV}p2 is too small: Needing $BOOT_PART_SIZE MB, ${DEV}p2 has only $size MB"
        exit 1
    fi

    size=$( sfdisk -l -uS ${DEV} | grep ${DEV}p3 | awk -v sectorsize="$SECTORSIZE" '{MB=$4*sectorsize/(1024*1024); print MB}')
    if [ -z "$size" ]; then
        error_msg "Missing Partition ${DEV}p3"
        exit 1
    fi
    if [ "$size" -lt "$ROOTFS_PART_MINSIZE"  ]; then
        error_msg "Partition ${DEV}p3 is too small: Needing $BOOT_PART_SIZE MB, ${DEV}p3 has only $size MB"
        exit 1
    fi

    return 0
}


#===================================================
# Try to find out everything about A-B layout
# Sets AB_PARTITIONING_TARGET to A or B
# Sets AB_PARTITIONING_FOUND to true if everything is
# setup for AB Mode
#===================================================
detect_AB_partitioning()
{
    AB_PARTITIONING_FOUND=false
    AB_PARTITIONING_TARGET="A"
    if $PART_B_BOOT_OK && $PART_B_ROOT_OK;then
        AB_PARTITIONING_FOUND=true

        # If we are running in FNG System ( still the default)
        # we always use 'A' if not explicitly specified
        # by --ABTarget
        AB_PARTITIONING_TARGET="A"
        if $is_fng_system;then
            if [ "B" = "$AB_PARTITION_TARGET_PARAM" ];then
                AB_PARTITIONING_TARGET="B"
            fi
        else
            # We will continue only if AB Partition Scheme is found
            # and install to the unused partition pair.
            #
            # Selecting the partitions is done on the kernel cmdline
            #   root= entry.
            #   and the boot partition mount via fs tab

            # Search the cmdline for root=
            # shellcheck disable=2016
            splitstring current_rootfs " =" "$(cat /proc/cmdline)" '$1' root
            if [ "$current_rootfs" = "$PART_A_ROOT" ];then
                AB_PARTITIONING_TARGET="B"
            elif [ "$current_rootfs" = "$PART_B_ROOT" ];then
                AB_PARTITIONING_TARGET="A"
            else
                exit_error "Failed to detect the correct A-B partitions."
            fi
        fi
    fi
}


#===================================================
# Analyze the partition table
# Recreate partitions if needed
# Dectect partition scheme and target partition for A-B boot
# Format partitions
#===================================================
prepare_partitions()
{
    #===========================================================
    # Check current partition layout
    #===========================================================
    DEV="/dev/$TARGETDEVICE"

    # Analyze partitions always, so the PART_XX variables all are set
    if ! analyze_partitions "$DEV";then
        # If the analyze of the current parition situation fails
        # repatitioning is needed
        REPARTITION_NEEDED=true
    fi

    # If FORCE_REPARTITION is set, we mark all 'yocto' partitions
    # not to be keeped. They are recreated during parition generation
    # later in this case
    if $FORCE_REPARTITION;then
        REPARTITION_NEEDED=true
        dont_keep_partition "$PART_A_BOOT"
        dont_keep_partition "$PART_A_ROOT"
        dont_keep_partition "$PART_B_BOOT"
        dont_keep_partition "$PART_B_ROOT"
    fi

    # Sets AB_PARTITIONING_FOUND and AB_PARTITIONING_TARGET
    detect_AB_partitioning

    if ! $is_fng_system && ! $AB_PARTITIONING_FOUND;then
        exit_error "When running from yocto the partition scheme" \
                   "has to match the needs of the installation." \
                   "At least the A-B Partition scheme is needed," \
                   "but has not been found. Use the installation" \
                   "in Flash-N-Go system, to create the correct" \
                   "setup."
    fi

    # Check the target partition depending on the
    # A/B setting, if they are not OK trigger recreation.
    if [ "$AB_PARTITIONING_TARGET" = "B" ];then
        $PART_B_BOOT_OK || REPARTITION_NEEDED=true
        $PART_B_ROOT_OK || REPARTITION_NEEDED=true
    else
        # 'A' and default
        $PART_A_BOOT_OK || REPARTITION_NEEDED=true
        $PART_A_ROOT_OK || REPARTITION_NEEDED=true
    fi

    #============================================

    if ! $REPARTITION_NEEDED;then
        # Analyze partitions already does basic partition checks
        # here we check the needs for the yocto installation
        # if REPARTITION_NEEDED is set, this can be skipped,
        # as partitions will be recreated the way yocto needs
        # them anyway

        # Basically this checks if the layout matches the command
        # line settings. If not the functions bails out with a
        # warning and some hints.
        check_partitions_match_spec
    fi

    #============================================
    # Partitioning
    #============================================
    # The partitioning is completly skipped if the
    # current partition table is in a working state
    # matching the specs
    if $REPARTITION_NEEDED;then
        if ! $is_fng_system; then
            exit_error "When running the installation from yocto" \
                       "the available partitions needs to match the needs," \
                       "repartitioning is not available." \
                       "Use the installation in Flash-N-Go system" \
                       "to create the correct setup."
        fi
        #
        # Do not delete FNG System accidently when installing yocto
        if ! $PART_FNGSYSTEM_OK; then
            exit_error "Flash-N-Go system partition is not okay.\n" \
                       "Please reinstall Flash-N-Go System with\n" \
                       "'-f/--force-repartition' to update the partition."
        fi
        #
        if $FORCE_REPARTITION;then
            # Recreate all partitions
            create_partitions "$DEV"
        else
            # Recreate only needed partitions, the second parameter
            # holds the number of partitions to keep/not to change.
            # Only partitions at the beginning of the disk are
            # keeped to allow resizing
            create_partitions "$DEV" "$KEEP_PARTITIONS_CNT"
        fi
    fi

    #===========================================
    # Update the AB Settings according to the changed layout
    # Sets AB_PARTITIONING_FOUND and AB_PARTITIONING_TARGET
    #===========================================
    detect_AB_partitioning
    #===========================================
    # Store target partitions
    # Set the target partitions for the installation,
    # depending on the A/B setting
    # This is done after create_partitions, as it rereads
    # the partition table again
    #===========================================
    if [ "$AB_PARTITIONING_TARGET" = "B" ];then
        TARGET_PARTITION_BOOT="$PART_B_BOOT"
        TARGET_PARTITION_ROOT="$PART_B_ROOT"
    else
        TARGET_PARTITION_BOOT="$PART_A_BOOT"
        TARGET_PARTITION_ROOT="$PART_A_ROOT"
    fi

    #============================================
    # Verify the boot partition is not mounted
    #   This may happen if we are in yocto and something
    #   was messed up during the detection or the installation
    # Just error out in that case
    #============================================
    if find_mountpoint m "$TARGET_PARTITION_BOOT";then
        exit_error "The selected boot partition $TARGET_PARTITION_BOOT is" \
                   "already mounted at $m. This means something went wrong" \
                   "and we stop here."
    fi
}

#===========================================
# execute mkfs on all needed partitions
#===========================================
format_partitions()
{
    milestone "Creating filesystems"

    case "$AB_PARTITIONING_TARGET" in
        B)   BOOTLABEL="LINUXB"
             ROOTLABEL="ROOTFSB"
            ;;
        A|*) BOOTLABEL="LINUX"
             ROOTLABEL="ROOTFS"
            ;;
    esac

    # Format Linux partitions and adapt the labels according to A or B Target
    wait_for_mmc "$DEV"
    $DRYRUN mkfs.vfat -n "$BOOTLABEL" -F 16 "$TARGET_PARTITION_BOOT" \
        || exit_error "Failed to format partition $TARGET_PARTITION_BOOT as boot"
    wait_for_mmc "$DEV"
    $DRYRUN mkfs.${ROOTFS_TYPE} -F -L "$ROOTLABEL" "$TARGET_PARTITION_ROOT" \
        || exit_error "Failed to format partition $TARGET_PARTITION_ROOT as root"
    wait_for_mmc "$DEV"

    format_user_partition

    milestone "Creating filesystems finished"
}

#===========================================
# Loads file using curl
# repeats if needed
#===========================================
load_file()
{
    FILE=$1
    TARGET=$2
    repeat=3

    milestone "Writing ${FILE} ..."

    while true;
    do
        set +e
        do_load "${FILE}" "${TARGET}"
        ret=$?

        set -e

        if [ "$ret" -eq "0" ]
        then
            milestone "Writing ${FILE} finished."
            return 0
        fi
        if [ 1 = $repeat ]; then
            error_msg "Download of ${FILE} failed, giving up."
            exit 1
        fi
        info_msg "Download of ${FILE} failed, trying again."
        repeat=$(( repeat - 1 ))
    done

}

load_and_untar()
{
    FILE=$1
    TARGET=$2
    repeat=3

    while true;
    do
        milestone "Writing rootfs (${FILE})"

        # get decompression parameter from file extension,
        # as the busybox tar does not autodetect it
        COMPRESSION="$(tar_compression "$FILE")"

        set +e

        # The pipe hides curl errors, but hopefully tar also
        # fails when the input is broken.
        OLDIFS=$IFS;IFS='|'
        # shellcheck disable=2086
        if [ -z "$DRYRUN" ];then
            curl ${CURL_ARGS#|} "${CURL_PREFIX}/${FILE}"  2>&5 | tar -C "${TARGET}" -x"$COMPRESSION"
        else
            error_text DRYRUN: Skip: curl ${CURL_ARGS#|} ${CURL_PREFIX}/${FILE} \| tar -C ${TARGET} -x$COMPRESSION
        fi
        ret=$?
        IFS=$OLDIFS

        set -e

        if [ "$ret" = "0" ]
        then
            sync
            milestone "Writing rootfs (${FILE}) finished."
            return 0
        fi
        if [ 0 = $repeat ]; then
            error_msg "Download of ${FILE} failed, giving up."
            exit 1
        fi
        info_msg "Download of ${FILE} failed, trying again."
        repeat=$(( repeat - 1))
    done
}

#===========================================
# download files and write to the needed places
#===========================================
load_and_write_files()
{
    info_msg
    info_msg "== Writing files =="
    info_msg

    # Write rootfs partition
    mount "$TARGET_PARTITION_ROOT" -t "${ROOTFS_TYPE}" "${TMP_MOUNT_POINT}"

    # Mount the boot partition, so kernel and so on may be part of the tar
    mkdir -p "${TMP_MOUNT_POINT}"/boot
    mount "$TARGET_PARTITION_BOOT" "${TMP_MOUNT_POINT}"/boot

    load_and_untar "${ROOTFS}" "${TMP_MOUNT_POINT}"

    umount "${TMP_MOUNT_POINT}/boot"
    umount "${TMP_MOUNT_POINT}"

    # Mount the boot partition separatly
    mount "$TARGET_PARTITION_BOOT" "${TMP_MOUNT_POINT}"

    # Write boot script to flash
    if [ -n "${BOOTSCRIPT}" ]; then
        load_file "${BOOTSCRIPT}"  "${TMP_MOUNT_POINT}/boot.cfg"
    fi

    # Write boot logo to flash
    if [ -n "${IMAGE}" ];
    then
        load_file  "${IMAGE}" "${TMP_MOUNT_POINT}/$(basename "$IMAGE")"
    fi

    if [ -n "${BACKGROUNDCOLOR}" ];
    then
        echo "${BACKGROUNDCOLOR}" > "${TMP_MOUNT_POINT}/logo_background_color"
    fi

    # Write custom devicetree to flash (set via the arguments)
    if [ -n "${DEVICETREE}" ]
    then
        DTBASE=$( basename "${DEVICETREE}" )
        load_file "${DEVICETREE}" "${TMP_MOUNT_POINT}/${DTBASE}"
        echo "${DTBASE}" > "${TMP_MOUNT_POINT}/devicetree"
    fi

    # Load overlays that are provided via the script's arguments.
    # and add them to the overlays.txt file.

    for DT in ${DEVICETREEOV}; do
        DTBASE=$( basename "$DT" )
        load_file "${DT}" "${TMP_MOUNT_POINT}/${DTBASE}"

        if [ ! -f "${TMP_MOUNT_POINT}/overlays.txt" ]; then
            echo "overlays=" >> "${TMP_MOUNT_POINT}/overlays.txt"
        fi

        sed -i "/^overlays=/s/$/ ${DTBASE}/" "${TMP_MOUNT_POINT}/overlays.txt"
    done

    #================================================
    # Rename and adapt boot scripts for A-B booting
    #  For the B installation the bootscript needs to
    #  be adapted, so the bootloader selects the correct
    #  file
    #================================================
    if [ "$AB_PARTITIONING_TARGET" = "B" ];then
        # For u-boot the boot.scr is renamed for fngboot the boot.cfg
        if [ -e "$TMP_MOUNT_POINT/boot.scr" ];then
            mv "$TMP_MOUNT_POINT/boot.scr" "$TMP_MOUNT_POINT/bootB.scr"           
        fi
        # boot.scr is now also present for machines that use fngboot
        # Also rename boot.cfg in this case
        if [ -e "$TMP_MOUNT_POINT/boot.cfg" ];then
            mv "$TMP_MOUNT_POINT/boot.cfg" "$TMP_MOUNT_POINT/bootB.cfg"
        fi
    fi

    #================================================

    umount "${TMP_MOUNT_POINT}"
    umount_emmc
    sync
}

#=========================================================
# Update sequence
#=========================================================
do_update()
{
    # Use subshell so set -e breaks at any return != 0 finished only the subshell an we may repeat the process
    (
        # Stop on errors
        set -e;

        # Execute Preinstall Scripts, if any
        run_install_scripts "$PreInstallScript" || return $?

        # Analyze partitions, check where to install to
        # create new partitions if needed
        prepare_partitions || return $?

        partitions_ready_message

        if ! $SKIP_DOWNLOAD ;then
            # Format the new partitions
            format_partitions || return $?

            # Write mandatory file (rootfs, kernel, devicetree )
            load_and_write_files || return $?

            # Optionally install RPM files
            rpm_install "$RPM" || return $?
            # Optionally erase RPM files
            rpm_erase "$ERASERPM" || return $?
            # Optionally install 'TFTP' files
            tftp_install "$TFTPFILES" || return $?
            # Optionally execute user install scripts
            user_install "$USERSCRIPT" || return $?
        fi

        # Call postinstall in target os if it exists
        # In dunfell this combines kernel and devicetree
        target_os_postinstall || return $?

        # Execute some tests to make sure the target partitions are there and good
        postinstallation_tests || return $?

        # Execute Postinstall Scripts, if any
        run_install_scripts "$PostInstallScript" || return $?

        set +e;

        # Needs to go inside the subshell, otherwise the AB_PARTITIONING_TARGET
        # variable is not accessable

        {
        if $AB_PARTITIONING_FOUND;then
            echo "Installed as '$AB_PARTITIONING_TARGET' installation, use"
            if [ $AB_PARTITIONING_TARGET = 'B' ];then
                    echo "bootselect regularB or bootselect oneshotB"
            else
                    echo "bootselect regular or bootselect oneshotA"
            fi
            echo "to boot the newly installed image."
        fi
        }>&5
    )
    return $?
}


do_and_repeat_update()
{
    repeat=$1

    while true;
    do
        # Unmount any previously mounted partitions of the emmc
        umount_emmc

        # Execute the actual update
        do_update && return 0

        if [ 1 = "$repeat" ]; then
            exit 1
        fi
        info_msg "Update failed, trying again."
        repeat=$(( repeat - 1))
    done

}

#=========================================================
# Installation sequence
#=========================================================

do_installation()
{
    # Are we in FNG System, do we have an mmc, does the version of FNGBoot and FNG System match
    check_startup_conditions

    # Check that the network connection works and the files are reachable
    verify_file_access

    # Setup exit traps, so cleanup is called when an error happens
    trap exit_error EXIT
    trap exit_error INT

    TMP_MOUNT_POINT=$(mktemp -d -t)

    # Disable automounter
    automount disable

    # Try the update 3 times
    do_and_repeat_update "$TOTAL_RETRIES"

    # Setup cleanup function to be called on exit (overwrites the error)
    trap cleanup EXIT

    if [ -n "$DRYRUN" ];then
        milestone "No errors during dry run. Nothing updated."
    elif [ "$(cat $POSTINSTALL_STATUS_FILE)" != "0" ]; then
        error_msg "Update finished, but post-installation script failed"
    else
        milestone "Update successful"
    fi
}

#==============================================================
#==============================================================

parse_args "" "$@"

setup_download_prefix

# Sets the user partition variables according to the params given
parse_user_partition

[ -z "$ROOTFS" ] && eval ROOTFS="$ROOTFS_DEFAULT"
[ "$MACHINE" = "seco-mx8mm" ] && BOOTLOADER_MIN_VERSION="$BOOTLOADER_MIN_VERSION_imx8m"
[ "$MACHINE" = "seco-mx8mp" ] && BOOTLOADER_MIN_VERSION="$BOOTLOADER_MIN_VERSION_imx8m"

store_mount

do_installation

exit 0

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
