#!/bin/bash
set -eo pipefail
shopt -s extglob

# Load the Slackware ARM Devkit:
source /usr/share/slackdev/buildkit.sh || exit 1

# Copyright 2025   Stuart Winter, Donostia, Spain
# All rights reserved.
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
#  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
#  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
#  EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
#  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
#  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
#  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
#  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
######################################################################################
# Program: sair
# Purpose: Slackware ARM Installer Repacker
#          Unpacks the Slackware ARM Installer image and modifies it to include
#          Hardware Model–specific changes (e.g. a vendor’s Linux kernel fork).
#          Useful when the upstream kernel does not yet provide full support
#          for the target Hardware Model.
# Author : Stuart Winter <mozes@slackware.com>
# Date...: 03-Sept-2025
# Version: 1.00
######################################################################################
# Todo: Add ISO support?
# datadir: sair.data/<hwmname> - used to store replacement/additional files.
#          No current use case - will add when requires.
# Idea to wipe partition - to add as an example in the helper script.
# better error handling - at the moment it leaves open mounts if something fails.
######################################################################################

VERSION="1.0.0 <mozes@slackware.com>"
PRGNAM="sair"

# Defaults:
TMP="/tmp/sair"
SAIRCONFDIR=${PWD}/sair.conf
# Don't compress the output image by default:
COMPRESS_DSTIMG=No
# No debuging by default:
DEBUG=No
# Asset dir for assets we'll add to the Installer. This is optional:
ASSETDIR=""

# Max compression and enable more resources for xz compression:
export XZ_OPTS="--threads 0 -z9f -C crc32"

####################### Functions for parent ################################################

function print_usage() {
printf "
Usage:
  ${PRGNAM} --src-aio-installer-image <path> \\
       --dst-aio-installer-image <path> \\
       --hardware-model <name> \\
       [--tmpdir </tmp/>] \\
       [--version] [--help]

Required (with parameters):
  -s, --src-aio-installer-image  Official source Slackware AiO image for the target Hardware Model
  -d, --dst-aio-installer-image  Destination output file (the respun image)
  -c, --config                   Hardware Model configuration script (instructions to modify the installer)

Optional (with parameter):
  -C, --compress-dst		 Compress the destination output image (default: no)
  -H, --hardware-model           Hardware Model name (e.g. "rpi4"). This can be used in the configuration
                                 script to control behaviour.
  -t, --tmpdir                   Alternate temporary directory (default: ${TMP})
  -a, --assetsdir		 Asset directory

Flags (no parameters):
  -v, --version                  Print version and exit
  -h, --help                     Print this help and exit
  -D, --debug                    Debug mode
"
#  -n, --no-abort-missing-handler Do not abort processing if there isn't a defined handler for a
#                                 file system label (default: abort)
}

function print_version() {
  echo "${PRGNAM} ${VERSION}"
}

# Zero out the free space. This is due to replacing the files (esp if you replace the
# entire 'slackware' tree) on the ext4 file system. When the resulting image is compressed,
# it contains all of the old data since ext4 only marked it as deleted, it wasn't
# actually removed.
function freespace_clean() {
  local fsclean=$1
  pinfo "Zeroing free blocks in ${fsclean##*/}"
  dd if=/dev/zero of=${fsclean}/fs.zero.fill bs=1M > /dev/null 2>&1
  rm -f ${fsclean}/fs.zero.fill
}

function bytes_of_file() {
  stat -c %s -- "$1"
}
function mime_of_file() {
  file -b --mime-type -- "$1" 2>/dev/null || echo ""
}
function is_xz() {
  case "$(mime_of_file "$1")" in application/x-xz) return 0;; esac
  [[ "$1" == *.xz || "$1" == *.lzma || "$1" == *.txz ]] && return 0 || return 1
}
function is_bz2() {
  case "$(mime_of_file "$1")" in application/x-bzip2) return 0;; esac
  [[ "$1" == *.bz2 || "$1" == *.bz ]] && return 0 || return 1
}

# Create a secure temporary directory
# Usage:
#   make_tempdir /path/to/dir   # wipe and recreate
#   make_tempdir                # auto-generate with mktemp
# Create a secure temp directory and ensure enough space for FILE.
# Uses fallocate to test storage availability.
#
# Usage:
#   make_tempdir FILE
#   make_tempdir /path/to/dir FILE
#
# Returns the directory path on success.
# make_tempdir: create a secure temp dir and ensure enough space for work on FILE.
# Usage:
#   make_tempdir FILE
#   make_tempdir /path/to/dir FILE
#
# Required space:
#   compressed? required = uncompressed_size + compressed_size + 100MiB
#   else:        required = file_size + 100MiB
#
# On success: echoes the directory path. On failure: stderr + non-zero.
function make_tempdir() {
  set -Euo pipefail

  local dir file
  case "$#" in
    1) dir=""; file=$1 ;;
    2) dir=$1; file=$2 ;;
    *) printf 'Usage: make_tempdir [dir] file\n' >&2; return 1 ;;
  esac

  [[ -f "$file" ]] || { printf 'Input file not found: %s\n' "$file" >&2; return 1; }

  local sz_compressed sz_uncompressed is_compressed required margin
  sz_compressed=$(bytes_of_file "$file")
  is_compressed=0

  if is_xz "$file"; then
    is_compressed=1
    # Fast path: xz header stats
    local out
    if out=$(xz --robot -l -- "$file" 2>/dev/null); then
      # totals\t…\t…\t…\tUNCOMPRESSED
      sz_uncompressed=$(awk -F '\t' '/^totals/ {print $5; exit}' <<<"$out")
    fi
    if [[ -z "${sz_uncompressed:-}" || "$sz_uncompressed" == "-" ]]; then
      # Stream fallback (CPU only, no disk)
      sz_uncompressed=$(xz -dc -- "$file" | wc -c)
    fi
  elif is_bz2 "$file"; then
    is_compressed=1
    sz_uncompressed=$(bunzip2 -c -- "$file" | wc -c)
  else
    sz_uncompressed=$sz_compressed
  fi

  margin=$((100 * 1024 * 1024))
  if (( is_compressed )); then
    required=$(( sz_uncompressed + sz_compressed + margin ))
  else
    required=$(( sz_uncompressed + margin ))
  fi

  # --- (re)create dir securely
  if [[ -z "${dir:-}" ]]; then
    dir=$(mktemp -d) || { printf 'mktemp failed\n' >&2; return 1; }
  else
    case "$dir" in
      ""|"/") printf 'Refusing to operate on empty or root path\n' >&2; return 1 ;;
    esac
    [[ -d "$dir" ]] && rm -rf -- "$dir"
    mkdir -p -m 700 -- "$dir"
  fi

  # --- hard test with fallocate
  local testfile="$dir/.space_test.$$"
  if ! fallocate -l "$required" -- "$testfile" 2>/dev/null; then
    rm -f -- "$testfile"
    # If we created the dir (no explicit dir arg), remove it to keep things tidy.
    if (( $# == 1 )); then rmdir -- "$dir" 2>/dev/null || true; fi
    printf 'Insufficient space in %s filesystem.\n' "$dir" >&2
    printf 'Required: %s bytes (object: %s, compressed: %s, margin: %s)\n' \
           "$required" "$sz_uncompressed" "$(( is_compressed ? sz_compressed : 0 ))" "$margin" >&2
    return 1
  fi
  rm -f -- "$testfile"

  printf '%s\n' "$dir"
}

function cleanup_tmp() {
  # Wipe the temp space:
  rm -rf $TMPDIR
}

safe_name() {
  # produce a safe directory name from a label (no spaces/odd chars)
  # allows A-Z a-z 0-9 . _ - + ; replace others with '_'
  local in="$1"
  sed 's/[^A-Za-z0-9._+-]/_/g' <<<"$in"
}

# Globals created:
#   - declare -gA parts   # label -> partition device
#   - declare -gA mounts  # label -> mount point
#   - declare -gA fsdata  # metadata (command, image, loop)
#   - declare -g  LOOP_DEV IMG_PATH
#
# Requirements in scope: mime_of_file, is_xz, is_bz2
# Requires: losetup, blkid, mount, xz, bunzip2
# Uses: $TMPDIR (must be set)
# Optional env:
#   MNT_BASE=/mnt/sair        # base mount directory (default)
#   MNT_OPTS=ro               # mount options (default read-only)
function mount_aio_img() {
  set -Euo pipefail
  (( $# == 1 )) || { echo "usage: mount_aio_img <aio-image>" >&2; return 2; }

  local src="$1"
  [[ -f "$src" ]] || { echo "File not found: $src" >&2; return 1; }
  [[ -n "${TMPDIR:-}" ]] || { echo "TMPDIR is not set" >&2; return 1; }

  local MNT_BASE="${TMPDIR}/mnt"
  local MNT_OPTS="rw"

  mkdir -p -- "$TMPDIR" "$MNT_BASE"

  # Outputs (globals)
  declare -g LOOP_DEV=""
  declare -g IMG_PATH=""
  declare -gA parts
  declare -gA mounts
  declare -gA fsdata
  parts=(); mounts=(); fsdata=()

  # Decompress if needed
  if is_xz "$src"; then
    IMG_PATH="${TMPDIR}/$(basename "${src%.*}")"
    echo "Decompressing ${src##*/} ..."
    xz -dc -- "$src" > "$IMG_PATH"
  elif is_bz2 "$src"; then
    IMG_PATH="${TMPDIR}/$(basename "${src%.*}")"
    echo "Decompressing ${src##*/} ..."
    bunzip2 -c -- "$src" > "$IMG_PATH"
  else
    # Copy it into the temporary directory, as we'll be modifying it:
    # We won't be touching the source image, even if it's uncompressed.
    IMG_PATH="${TMPDIR}/$(basename "${src}")"
    echo "Copying ${src##*/} to temporary work space ..."
    cp -fa "$src" ${IMG_PATH}
  fi

  # Setup loop device (with partition scan)
  LOOP_DEV=$(losetup -fP --show -- "$IMG_PATH") || {
    echo "losetup failed for $IMG_PATH" >&2
    return 1
  }

  fsdata[image]="$IMG_PATH"
  fsdata[loop]="$LOOP_DEV"

  # Build label->device mapping by querying each partition under the loop
  local p dev lbl
  for p in "${LOOP_DEV}"p*; do
    [[ -b "$p" ]] || continue
    # Get label for this partition (may be empty)
    lbl=$(blkid -s LABEL -o value -- "$p" 2>/dev/null || true)
    [[ -n "$lbl" ]] || continue
    parts["$lbl"]="$p"
  done

  # Store the command that lists labels (for reference)
  fsdata[command]="blkid -s LABEL -o value ${LOOP_DEV}p*"

  # Prepare mount points and mount
  for lbl in "${!parts[@]}"; do
    local safe lbl_dir
    safe=$(safe_name "$lbl")
    lbl_dir="${MNT_BASE}/${safe}"
    mkdir -p -- "$lbl_dir"

    # Try mounting (read-only by default; override via MNT_OPTS)
    if mount -o "$MNT_OPTS" -- "${parts[$lbl]}" "$lbl_dir"; then
      mounts["$lbl"]="$lbl_dir"
    else
      echo "Warning: failed to mount ${parts[$lbl]} at $lbl_dir" >&2
    fi
  done

  # --- SAFETY CHECK: ensure every fs_handler_<LABEL> has its LABEL mounted ---
  local -a handler_funcs=()
  mapfile -t handler_funcs < <(compgen -A function fs_handler_ || true)

  if ((${#handler_funcs[@]})); then
    local -a missing=()
    local fn label mp
    for fn in "${handler_funcs[@]}"; do
      label="${fn#fs_handler_}"                 # extract LABEL from function name
      # Check presence in mounts[] and that it's actually mounted
      if [[ -z "${mounts[$label]+_}" ]]; then
        missing+=("$label (no mount entry)")
        continue
      fi
      mp="${mounts[$label]}"
      if ! mountpoint -q -- "$mp"; then
        missing+=("$label (not a mountpoint: $mp)")
      fi
    done

    if ((${#missing[@]})); then
      echo "Error: handler functions expect these filesystem labels to be mounted:" >&2
      printf '  - %s\n' "${missing[@]}" >&2
      echo "Known labels/mounts:" >&2
      for lbl in "${!mounts[@]}"; do
        printf '  %s -> %s\n' "$lbl" "${mounts[$lbl]}" >&2
      done
      # umount:
      cleanup_mounts_and_loop
      return 1
    fi
  fi

  # Echo results
  echo "Image: ${fsdata[image]}"
  echo "Loop : ${fsdata[loop]}"
  echo "Mounted partitions:"
  for lbl in "${!mounts[@]}"; do
    echo "  Label: $lbl"
    echo "  Dev  : ${parts[$lbl]}"
    echo "  Mount: ${mounts[$lbl]}"
  done
}

# Cleanup helper: unmount everything we mounted and detach loop
# Discover any left over loopback mounts:
# findmnt -rn -S '^/dev/loop'
function cleanup_mounts_and_loop() {
  set -euo pipefail
  local lbl
  # Change the pwd to enable umounting. If any of the helper functions remain
  # within one of the mount points, umounting will fail.
  cd /
  # Unmount in reverse lexical order (best-effort)
  for lbl in $(printf '%s\n' "${!mounts[@]}" | sort -r); do
    local m="${mounts[$lbl]}"
    if mountpoint -q -- "$m"; then
      # Zero out the free space prior to umounting:
      freespace_clean "$m"
      umount -- "$m" || echo "Warning: failed to umount $m" >&2
     else
     echo "$m has been umounted ahead of time (probably from the helper script)"
    fi
  done
  if [[ -n "${LOOP_DEV:-}" ]]; then
    losetup -d "$LOOP_DEV" || echo "Warning: failed to detach $LOOP_DEV" >&2
    LOOP_DEV=""
  fi
}

# Finalise the destination image. Compress if necessary and move into place:
function finalise_dst_image() {
  set -euo pipefail
  if [[ "$COMPRESS_DSTIMG" == "Yes" ]]; then
    echo "Compressing new image ${DST_IMG} with xz (max compression) ..."
    xz $XZ_OPTS --stdout -- "${IMG_PATH}" > "${DST_IMG}.xz"
   else
    # Move the uncompressed image into place:
    echo "Saving image to ${DST_IMG} ..."
    mv -f -- "${IMG_PATH}" "${DST_IMG}"
  fi
}

####################### End of functions for parent ################################################

####################### Functions for Hardware Model helpers #######################################

function pdebug() {
  printf "DEBUG: ${@}\n"
  read -p "Press ENTER to continue ..." ;}

function pinfo() {
  printf "\t${@}\n" ;}

# Extract the Kernel artifacts from the supplied Slackware Kernel package,
# and create the symlinks required:
function setup_kernel_artifacts() {
  pushd $1 > /dev/null || return 1
  # e.g. $2 = ~/armedslack/experimental/slackwareaarch64-current/vendor-kern-fork/kernel_armv8-6.12.33-aarch64-1_VENDOR.txz
  tar xf $2 \
  --wildcards \
  --strip-components=1 \
    'boot/Image-*' \
    'boot/System.map*' \
    'boot/config-*' \
    'boot/dtb-*' \
    'install/doinst.sh'
# we don't need the initrd since that's the OS version, not the Installer.
#    'boot/initrd-*' \
  # Create symlinks to unversioned artifacts:
  grep -E '^\( cd boot' doinst.sh | awk -F';' '{print $2}' | sed 's?)??g' | bash
  # Wipe any initrd symlinks, since we don't use those for the Installer.
  # The Installer initrd is deployed as an unversioned file.
  rm -f initrd*
  # Don't need the package post install script anymore:
  rm -f doinst.sh
  popd > /dev/null
}

function purge_kernel_artifacts() {
   pushd $1 > /dev/null || return 1
   echo "Purging Kernel artifacts ..."
   # This is actually the Slackware installer, not the OS InitRD.
   rm -rf Image-* System.map* dtb* initrd*.img config*
   popd > /dev/null
}

# Unpack the Slackware Installer into a global temporary directory:
# By default we'll take the latest Installer image, as there's no good reason
# to ever use an older version of the Slackware Installer.
function unpack_slk_installer() {
  [ ! -f $1 ] && { echo "ERROR: Installer image $1 not found" ; return 1; }
  pinfo "Unpacking Slackware Installer initrd ..."
  mkdir -pm755 $TMPDIR/installer-unpacked
  xzcat $1 | cpio --quiet -di -D $TMPDIR/installer-unpacked || { pinfo "ERROR: Installer image not found" ; return 1 ;}
  # Store the date for machines without RTCs or with RTCs that have yet to be set.
  # The reason we set it 30 hrs ahead of now is so that when the
  # installer turns the clock back 1 day (due to fscks and such),
  # resizing of our /boot partition doesn't emit date warnings.
  # This already exists within the Installer - we're just updating it here.
  date +%s -d '+30 hours' > $TMPDIR/installer-unpacked/.installer_build_date
}

# Purge the Kernel modules from within the unpacked Slackware Installer
# and extract the modules from the given Slackware Kernel package:
#
function setup_kernel_mods_installer() {
  [ ! -f $1 ] && { pinfo "ERROR: Kernel package not found" ; exit 1 ; }
  [ ! -d $TMPDIR/installer-unpacked ] && { pinfo "ERROR: Unpacked installer image not found" ; return 1 ;}
  pushd $TMPDIR/installer-unpacked > /dev/null || return 1
  rm -rf lib/modules
  pinfo "Unpacking Kernel modules ..."
  tar xf $1 lib/modules/ -C. || return 1
  popd > /dev/null
}

# Filter the Kernel module list to the standard set.
function setup_kernel_mods_filter() {
  [ ! -d $TMPDIR/installer-unpacked ] && { pinfo "ERROR: Unpacked installer image not found" ; return 1 ;}
  [ ! -f $PORTSRC/source/k/scripts/slim-kmods-initrd ] && { pinfo "ERROR: Kernel module slimming script not found" ; return 1 ;}
  pushd $TMPDIR/installer-unpacked > /dev/null || return 1
  # Flag to instruct slimmer to adjust modules for the Installer rather than the
  # OS InitRD:
  local building_installer=1
  . $PORTSRC/source/k/scripts/slim-kmods-initrd || return 1
  popd > /dev/null
}

# Rebuild the module dependency map:
function setup_kernel_mods_depmod() {
  [ ! -d $TMPDIR/installer-unpacked ] && { pinfo "ERROR: Unpacked installer image not found" ; exit 1 ;}
  pushd $TMPDIR/installer-unpacked > /dev/null || return 1
  pinfo "Rebuilding module dependency map ..."
  depmod -b. $( basename $(ls lib/modules/[0-9]*arm* -d) ) || return 1
  popd > /dev/null
}

# Repack the Installer:
function repack_slk_installer() {
   pushd $TMPDIR/installer-unpacked > /dev/null || return 1
   pinfo "Repacking Installer image ..."
   find . | cpio --quiet -o -H newc | xz --threads $(( $(nproc) -1 )) -vze9f -C crc32 > $1
   popd > /dev/null
}

####################### End of functions for Hardware Model helpers ################################

# Parse command line operators:
LONG_OPTS="config:,src-aio-installer-image:,dst-aio-installer-image:,hardware-model:,asset-dir:,tmpdir:,version,help,compress-dst,debug"
SHORT_OPTS="c:s:d:H:t:a:vhCD"

# Use GNU getopt
PARSED=$(getopt --options="${SHORT_OPTS}" --longoptions="${LONG_OPTS}" --name "$0" -- "$@") || {
  # getopt already printed an error
  exit 2 ;}

eval set -- "${PARSED}"

# Vars to capture required args
SRC_IMG=""
DST_IMG=""
HW_MODEL_CONFIG=""

while true; do
  case "$1" in
    -c|--config)
      HW_MODEL_CONFIG=${SAIRCONFDIR}/$2; shift 2 ;;
    -s|--src-aio-installer-image)
      SRC_IMG=$2; shift 2 ;;
    -d|--dst-aio-installer-image)
      DST_IMG=$2; shift 2 ;;
    -H|--hardware-model)
      HW_MODEL=$2; shift 2 ;;
    -t|--tmpdir)
      TMPDIR=$2; shift 2 ;;
    -a|--asset-dir)
      ASSETDIR=$2; shift 2 ;;
    -C|--compress-dst)
      COMPRESS_DSTIMG=Yes ; shift 1 ;;
    -D|--debug)
      DEBUG=Yes ; shift 1 ;;
    -v|--version)
      print_version; exit 0 ;;
    -h|--help)
      print_usage; exit 0 ;;
    --)
      shift; break ;;
    *)
      echo "Internal parsing error at '$1'." >&2; exit 2 ;;
  esac
done

# Validate required parameters
err=0
if [[ -z "${SRC_IMG}" ]]; then
  echo "Error: --src-aio-installer-image is required." >&2; err=1
fi
if [[ -z "${DST_IMG}" ]]; then
  echo "Error: --dst-aio-installer-image is required." >&2; err=1
fi
if [[ -z "${HW_MODEL_CONFIG}" ]]; then
  echo "Error: --config is required." >&2; err=1
fi
if (( err )); then
  echo >&2
  print_usage >&2
  exit 2
fi

# From here, you have:
#   $SRC_IMG   -> source AiO image
#   $DST_IMG   -> destination output image
#   $HW_MODEL_CONFIG  -> hardware model helper script name
#   $TMPDIR    -> temp directory (default /tmp/)

#
# Basic sanity checks:
[[ -r "$SRC_IMG" ]] || { echo "Error: Source image not readable: $SRC_IMG" >&2; exit 2 ;}
[[ -f "$HW_MODEL_CONFIG" ]] || { echo "Error: Hardware Model helper script ${HW_MODEL_CONFIG} not found" >&2; exit 2 ;}
[ ! -z "${ASSETDIR}" -a ! -d "${ASSETDIR}" ] && { echo "Error: asset directory does not exist" >&2 ; exit 2 ;}

###########################################################################################################
# Main business logic
###########################################################################################################

# Create and test temporary space:
TMPDIR=$( make_tempdir ${TMP} ${SRC_IMG} )
# Create locations within the temporary space:
mkdir -pm700 $TMPDIR/{mnt,installer,extract}

# Load the Hardware Model helper/config script:
. ${HW_MODEL_CONFIG} || exit 1

[ "${DEBUG}" = "Yes" ] && pdebug "Pausing before running HWM initialisation function"

# Call the Hardware Model plugin initialisation routine, if present:
fnexists_call hwm_initialise

[ "${DEBUG}" = "Yes" ] && pdebug "pausing after running HWM initialisation function"

##up to here ##
# Mount the supplied Slackware AiO image:
# -> mounts[], LOOP_DEV, IMG_PATH now populated
mount_aio_img ${SRC_IMG} || exit 1

[ "${DEBUG}" = "Yes" ] && pdebug "pausing before running file system handlers"

# Iterate through the filesystem labels, calling the Hardware Model plugin's handler function
# if present.  Each handler is provided the mount point for the file system it handles.
for installer_fs_label in "${!mounts[@]}"; do
   if fnexists fs_handler_${installer_fs_label} ; then
      echo "Calling handler for file system ${installer_fs_label}"
#      echo "  Label: ${installer_fs_label}"
#      echo "  Dev  : ${parts[$installer_fs_label]}"
#      echo "  Mount: ${mounts[$installer_fs_label]}"
      fs_handler_${installer_fs_label} ${mounts[$installer_fs_label]} || exit 1
      [ "${DEBUG}" = "Yes" ] && pdebug "pausing after running file system handler"
    else
      echo "No handler found for label: ${installer_fs_label}"
   fi
done

[ "${DEBUG}" = "Yes" ] && pdebug "pausing before running HWM finalisation function"

# Call the Hardware Model plugin finalisation routine, if present:
fnexists_call hwm_finalise

[ "${DEBUG}" = "Yes" ] && pdebug "pausing before umounting"

# umount the file systems:
cleanup_mounts_and_loop || exit1

# Compress image if requested, and move the new image into place:
finalise_dst_image

# Wipe the temporary workspace:
[ "${DEBUG}" = "Yes" ] && pdebug "pausing before cleaning up"

cleanup_tmp
###########################################################################################################
