#!/bin/sh
# shellcheck disable=2153,2154,2034
# Shellcheck does not see the assignment of variables in the called functions
# and complains about using unassigned variables.
#
# U-Boot update script - (c) 2021 SECO Northern Europe GmbH
#

BOOTLOADER_PACKAGE=imx-boot.tar.gz
BOOTLOADER=imx-boot

# List of variables that are taken from the old u-boot environment and written
# to the new, grep regex is allowed, BOOT_ variables are used by rauc.
UBOOT_VARIABLES_TO_KEEP="eth_addr BOOT_ORDER BOOT_[[:alnum:]]\+_LEFT"

HELP="
# U-Boot update script - (c) 2021 SECO Northern Europe GmbH
#
# This script is designed to run from the Flash-N-Go system. It overwrites
# U-boot/imx-boot with a new version.
#
# ATTENTION: This step is dangerous, because it might make the system
# unbootable. Run this script only if you are absolutely sure that you
# need a newer version of U-Boot.
#
# To run the script, either setup an TFTP or HTTP[S] server to allow access
# to this script and the other needed files ( $BOOTLOADER_PACKAGE ).
# Connect your device to the Ethernet, boot Flash-N-Go system and make
# sure it can access your server (try with 'ping', configure IP address if
# necessary).
#
# Then run the following commands (replace the IP address by your server's
# IP address):
#   export URL=http://192.168.1.100;curl tftp://$URL/fnginstall-uboot.sh | sh -s -- <args>
#
# Installation from local media ( thumbdrive, ...) is also possible.
#   unset URL;# sh fng-install-uboot.sh <args>
#
# Options:
#     -b=*|--BOOTLOADER=*          Specify alternative bootloader file, default is '$BOOTLOADER_PACKAGE'
#
"

#=========================
# 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 
#=========================

FNGYSTEM_MIN_VERSION=17.0

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

    $DEBUG || rmdir -rf "$TMPDIR" 2>/dev/null
    $DEBUG && info_msg "Debug: Don't erase $TMPDIR."
}


#======================================================
# Argument parsing
#======================================================

parse_extra_args()
{
    ret=1
    case $1 in
        -b|--BOOTLOADER|-b=*|--BOOTLOADER=*)
            parse_args_value BOOTLOADER_PACKAGE "$1" "$2" || ret=2
            BOOTLOADER_PACKAGE="$SUBDIR$BOOTLOADER_PACKAGE"
            ;;
        *) return 0;;
    esac
    return $ret
}

parse_args "" "$@"

setup_download_prefix

check_startup_conditions

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


# Get Bootloader from server
cd "$TMPDIR"

while true
do
    do_load "$BOOTLOADER_PACKAGE" || exit $?

    # Handle gitlab 'link' decoding, these are textfiles containing the name of the file
    # the 'link' is pointing to
    BOOTLOADER_HEADER=$(head -n 1 "$BOOTLOADER_PACKAGE" | tr '\0' '\n' | head -n 1)
    # Check if the returned data contains the basename of the package
    if string_contains "$BOOTLOADER_HEADER" "imx-boot-$MACHINE"
    then
        BOOTLOADER_PACKAGE="$(dirname "$BOOTLOADER_PACKAGE")/$BOOTLOADER_HEADER"
    else
        break
    fi
done

# Extract the tar
COMPRESSION="$(tar_compression "$BOOTLOADER_PACKAGE")"
tar "$COMPRESSION"xf "$BOOTLOADER_PACKAGE"

MD5SUM=$(find . -name "imx-boot*.md5" | head -n 1)
MD5SUM=${MD5SUM#*/} # remove the ./
BOOTLOADER="${MD5SUM%.*}" # like the md5sum but without .md5

[ -r "$MD5SUM" ] || exit_error "$MD5SUM not found."
grep -e "  $BOOTLOADER$" "$MD5SUM" > "$MD5SUM.tmp" || exit_error "$MD5SUM does not contain entry for $BOOTLOADER"
md5sum -c "$MD5SUM.tmp" || exit_error "MD5 sum is not correct"

#============================================
#
#============================================
DEV="/dev/$TARGETDEVICE"

# Stop on errors
set -e

#============================================
# Partitioning
#============================================

# Partitioning decision tree 
#    Check partition layout
#        -> useful partition table detected ?
#            -> No: Apply default partition table
#            -> Yes: 
#                   -> Layout options on command line?
#                        -> No: Continue without repartitioning
#                        -> Yes: Further check partition table matching cmd line options?
#                                -> Yes: Continue without repartitioning
#                                -> No: AUTO_REPARTITION set
#                                      -> No: Warn about difference between options and state
#                                      -> Yes: Do the partitioning 
#                   

REPARTITION_NEEDED=$FORCE_REPARTITION

if ! analyze_partitions "$DEV";then
    REPARTITION_NEEDED=true
fi

# Analyze partitions already does basic partition checks
# here we check the needs for the bootloader installation
if ! $REPARTITION_NEEDED;then

    # Does the layout match the command line settings
    check_partitions_match_spec
    
    # Need partition for the bootloader
    if [ -n "$PART_BOOTLOADER" ];then
        $PART_BOOTLOADER_OK || \
            partition_warning "Bootloader partition is not OK."
    else
        REPARTITION_NEEDED=true
    fi

fi

if $REPARTITION_NEEDED;then
    if $FORCE_REPARTITION;then
        create_partitions "$DEV"
    else
        create_partitions "$DEV" "$KEEP_PARTITIONS_CNT"
    fi
fi

#============================================
# Write to disk
#===========================================

partition_write_access "$PART_BOOTLOADER"

# Write new system to eMMC

# INFO: The mmc bootpart command selects the boot
# partition on the eMMC.
# The selection is bitwise coded:
#   0x0 = Off
#   0x1 = Boot 1 (mmcblkNboot0)
#   0x2 = Boot 2 (mmcblkNboot1)
#   0x7 = User Area (mmcblkN)

if string_contains "$PART_BOOTLOADER" "boot";then
    TARGETDEVICE_BOOTLOADER="$PART_BOOTLOADER"
    MMC_BOOTPART_NO=1
    BOOTLOADER_SEEK=$BOOTLOADER_SEEK_BOOT
else
    TARGETDEVICE_BOOTLOADER="/dev/$TARGETDEVICE"
    # User area is 7
    MMC_BOOTPART_NO=7
    BOOTLOADER_SEEK=$BOOTLOADER_SEEK_USER
fi

milestone "Writing ${BOOTLOADER} to $TARGETDEVICE_BOOTLOADER ..."
$DRYRUN dd if="${BOOTLOADER}" of="$TARGETDEVICE_BOOTLOADER" bs=1024 seek=$BOOTLOADER_SEEK

# If the target is an emmc, set the boot config flags
if [ -b "/dev/${TARGETDEVICE}boot0" ];then
    # Set the boot mode on emmc, sdcard does not need this
    $DRYRUN mmc bootbus set dual x1 x8 "$DEV"
    $DRYRUN mmc bootpart enable "$MMC_BOOTPART_NO" 1 "$DEV"
fi

#============================================
# Now care for the u-boot-environment
#  we want to update the stored environment
#  but keep few entries from the old
#
#  Also the fw_env.config needs to be changed
#  as it represents the config of the u-boot
#  installation.
#============================================
# Try to read the old values
UBOOT_ENV_BACKUP="$TMPDIR/u-boot-env-backup.txt"
UBOOT_ENV_RECOVER="$TMPDIR/u-boot-env-recover.txt"

u_boot_env_backup "$UBOOT_ENV_BACKUP" || true # Allow to fail here, for devices
                                              # where there is not u-boot environment yet

# Get new offsets from fw_env.config
FW_ENV_CONF="$(find "$TMPDIR" -name "fw_env*.config" | head -n 1)"
if ! fw_env_conf_decode fw_env_part fw_env_offset fw_env_size "$FW_ENV_CONF";then
    error_msg "Failed to read the default storage parameters for u-boot environment."
    exit 1
fi

UBOOT_ENV_IMAGE="$(find "$TMPDIR" -name "u-boot-initial-env*.bin" | head -n 1)"
if [ ! -r "$UBOOT_ENV_IMAGE" ];then
    error_msg "Failed to find the default u-boot environment image."
    exit 1
fi

# Write the new environment
$DRYRUN dd if="$UBOOT_ENV_IMAGE" bs=1 count=$((fw_env_size)) seek=$((fw_env_offset)) of="$TARGETDEVICE_BOOTLOADER"

# Write the updated fw_env.config
if [ -d "/etc/shared" ];then
    $DRYRUN echo "$TARGETDEVICE_BOOTLOADER    $fw_env_offset    $fw_env_size" > "/etc/shared/fw_env.config"
fi

# Recover some known u-boot variables
true > "$UBOOT_ENV_RECOVER"

for v in $UBOOT_VARIABLES_TO_KEEP;do
    grep "$UBOOT_ENV_BACKUP" -e "^${v}=" >> "$UBOOT_ENV_RECOVER" || true
done
$DRYRUN fw_setenv -c "$FW_ENV_CONF" -s "$UBOOT_ENV_RECOVER"


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

# Set write protection again
partition_write_access "$PART_BOOTLOADER" disable

#============================================
# Done writing
#============================================

sync

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