aboutsummaryrefslogtreecommitdiffstats
#!/bin/bash
#
# Copyright 2022, 2023  Eric Hameleers, Eindhoven, NL
# 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.

# -----------------------------------------------------------------------------
#
# This script can perform some specific changes on the USB stick
# containing an ISO of Slackware Live Edition,
# when you boot from that ISO using a multi-boot manager.
# - create a directory structure on the USB partition to add more
#   functionality to the ISO (e.g. load extra addons/optional modules).
# - create an encrypted container file for storing persistence data.
# - create an encrypted container file to mount on /home .
# - write all the above information into a configuration file for the ISO.
#
# -----------------------------------------------------------------------------

# Be careful:
set -e

# Limit the search path:
export PATH="/usr/sbin:/sbin:/usr/bin:/bin"

# Use of force is sometimes needed:
FORCE=0

# Version is obtained from the ISO metadata:
VERSION=""

# The extension for containerfiles accompanying an ISO is '.icc',
# whereas the persistent USB stick created with iso2usb.sh uses '.img'.
DEFEXT=".icc"
CNTEXT="${DEFEXT}"

# Default filesystem for devices/containers:
DEF_FS="ext4"
FSYS="${DEF_FS}"

# Default mount point for a LUKS container if not specified:
DEFMNT="/home"
LUKSMNT=""

# Values for container sizes:
PERSSIZE=""
LUKSSIZE=""
INCSIZE=""
LUKSVOL=""

# Associative array to capture LUKSVOL definitions:
declare -A CONTAINERS=()

# Values obtained from a pre-existing .cfg file:
ISOPERSISTENCE=""
LUKSCNT=""
LIVESLAKROOT=""

# Define ahead of time, so that cleanup knows about them:
IMGDIR=""
ISOMNT=""
CNTDEV=""
CNTMNT=""
EXTENSION=""
LODEV=""
PERSISTENCE=""

# Minimim free space (in MB) we want to have left in any partition
# after we are done.
# The default value can be changed from the environment:
MINFREE=${MINFREE:-10}

# Compressor used on the initrd ("gzip" or "xz --check=crc32");
# Note that the kernel's XZ decompressor does not understand CRC64:
COMPR="xz --check=crc32"

# These tools are required by the script, we will check for their existence:
REQTOOLS="cpio cryptsetup fsck gzip isoinfo lsblk unsquashfs xz zstd"

#
#  -- function definitions --
#

# Clean up in case of failure:
function cleanup() {
  # Clean up by unmounting our loopmounts, deleting tempfiles:
  echo "--- Cleaning up the staging area..."
  # During cleanup, do not abort due to non-zero exit code:
  set +e
  sync

  if [ -n "$CNTDEV" ]; then
    # In case of failure, only most recent LUKS mapped device is still open:
    if mount | grep -q ${CNTDEV} ; then
      umount -f ${CNTDEV}
      cryptsetup luksClose $(basename ${CNTDEV})
      losetup -d ${LODEV}
    fi
  fi
  [ -n "${ISOMNT}" ] && ( umount -f ${ISOMNT} 2>/dev/null; rmdir $ISOMNT 2>/dev/null )
  [ -n "${CNTMNT}" ] && ( umount -f ${CNTMNT} 2>/dev/null; rmdir $CNTMNT 2>/dev/null )
  [ -n "${IMGDIR}" ] && ( rm -rf $IMGDIR )
  set -e
} # End of cleanup()

trap 'echo "*** $0 FAILED at line $LINENO ***"; cleanup; exit 1' ERR INT TERM

# Show the help text for this script:
function showhelp() {
cat <<EOT
#
# Purpose: enhance the functionality when booting a Slackware Live ISO file.
# When supplying pathnames as parameter values below, use full pathnames in
# your local filesystem. The script will figure out where your USB disk
# partition is mounted and will adjust the path names accordingly
# in the USB configuration.
#
# $(basename $0) accepts the following parameters:
#   -d|--directory <path>         Create a liveslak directory structure to store
#                                 additional modules. The parameter value is
#                                 used as the root path below which the
#                                 liveslak/{addons,optional} subdirectories
#                                 will be created.
#   -e|--examples                 Show some common usage examples.
#   -f|--force                    Force execution in some cases where the script
#                                 reports an issue.
#   -h|--help                     This help text.
#   -i|--iso <fullpath>           Full path to your liveslak ISO image.
#   -l|--lukscontainer <fullpath> Full path to encrypted container file to be
#                                 created by this script, and to be mounted
#                                 in the live OS under /home
#                                 (or any other mountpoint you supply).
#                                 (filename needs to end in '${CNTEXT}'!).
#   -p|--persistence <fullpath  > Full path to encrypted persistence container
#                                 file to be created in the filesystem
#                                 (filename extension must be '${CNTEXT}'!).
#   -x|--extend <fullpath>        Full path to existing (encrypted) container
#                                 file that you want to extend in size
#                                 Limitations:
#                                 - container needs to be LUKS encrypted.
#                                 - filename extension needs to be '${CNTEXT}'.
#                                 Supported filesystems inside container:
#                                 - $(resizefs).
#   -F|--filesystem <fs>          Specify filesystem to create when formatting
#                                 devices/containers. Defaults to '${DEF_FS}',
#                                 Choices are $(createfs).
#   -L|--lcsize <size|perc>       Size of LUKS encrypted /home ; value is the
#                                 requested size of the container in kB, MB, GB,
#                                 or as a percentage of free space
#                                 (integer numbers only).
#                                 Examples: '-L 125M', '-L 2G', '-L 20%'.
#   -P|--perssize <size|perc>     Size of persistence container ; value is the
#                                 requested size of the container in kB, MB, GB,
#                                 or as a percentage of free space
#                                 (integer numbers only).
#                                 Examples: '-P 125M', '-P 2G', '-P 20%'.
#   -X|--extendsize <size|perc>   Extend size of existing container; value
#                                 is the requested extension of the container
#                                 in kB, MB, GB, or as percentage of free space
#                                 (integer numbers only).
#                                 Examples: '-X 125M', '-X 2G', '-X 20%'.
# 
EOT
} # End of showhelp()

# Show some common usage examples:
function showexamples() {
cat <<EOT
#
# Some common usage examples for $(basename $0)
# ---------------------------------------------------------------------------
# First, mount your USB partition, for instance
# a Ventoy disk will be mounted for you at /run/media/<user>/Ventoy/.
# Then:
# ---------------------------------------------------------------------------
# Create a 1GB encrypted persistence container:
#   ./$(basename $0) -p /run/media/<user>/Ventoy/myfiles/persistence.icc -P 1G
#
# Create a 4GB encrypted home with btrfs filesystem:
#   ./$(basename $0) -l /run/media/<user>/Ventoy/somedir/lukscontainers.icc -L 4000M -F btrfs -i /run/media/<user>/Ventoy/slackware64-live-current.iso
#
# Increase the size of that encrypted home container with another 2GB:
#   ./$(basename $0) -x /run/media/<user>/Ventoy/somedir/lukscontainers.icc -X 2G -i /run/media/<user>/Ventoy/slackware64-live-current.iso
#
# Create a 10GB encrypted container to be mounted on /data in the Live OS:
#   ./$(basename $0) -l /run/media/<user>/Ventoy/somedir/mydata.icc:/data -L 10G -i /run/media/<user>/Ventoy/slackware64-live-current.iso
#
# Create a liveslak directory structure for adding extra live modules:
#   ./$(basename $0) -d /run/media/<user>/Ventoy/myliveslak  -i /run/media/<user>/Ventoy/slackware64-live-current.iso
#
EOT
} # End of showexamples()

# Create a filesystem on a partition with optional label:
function createfs () {
  MYDEV="${1}"
  MYFS="${2:-'ext4'}"
  MYLABEL="${3}"

  if [ -n "${MYLABEL}" ]; then
    case "${MYFS}" in
    fs2s) MYLABEL="-l ${MYLABEL}" ;;
    *)    MYLABEL="-L ${MYLABEL}" ;;
    esac
  fi

  if [ -z "${MYDEV}" ]; then
    # Without arguments given, reply with list of supported fs'es:
    echo  "btrfs,ext2,ext4,f2fs,jfs,xfs"
    return
  fi
  case "${MYFS}" in
    btrfs) mkfs.btrfs -f -d single -m single ${MYLABEL} ${MYDEV}
           ;;
    ext2)  mkfs.ext2 -F -F ${MYLABEL} ${MYDEV}
           # Tune the ext2 filesystem:
           tune2fs -m 0 -c 0 -i 0 ${MYDEV}
           ;;
    ext4)  mkfs.ext4 -F -F ${MYLABEL} ${MYDEV}
           # Tune the ext4 filesystem:
           tune2fs -m 0 -c 0 -i 0 ${MYDEV}
           ;;
    f2fs)  mkfs.f2fs ${MYLABEL} -f ${MYDEV}
           ;;
    jfs)   mkfs.jfs -q ${MYDEV}
           ;;
    xfs)   mkfs.xfs -f ${MYDEV}
           ;;
    *)     echo "*** Unsupported filesystem '${MYFS}'!"
           cleanup
           exit 1
           ;;
  esac
} # End of createfs()

# Resize the filesystem on a block device:
function resizefs() {
  local MYDEV="${1}"

  if [ -z "${MYDEV}" ]; then
    # Without arguments given, reply with list of supported fs'es:
    echo  "btrfs,ext2,ext4,f2fs,jfs,xfs"
    return
  fi

  # Determine the current filesystem for the block device:
  local MYFS=$(lsblk -n -o FSTYPE ${MYDEV})
  if [ -z "${MYFS}" ]; then
    echo "*** Failed to resize filesystem on device '${MYDEV}'!"
    echo "*** No filesystem found."
    cleanup
    exit 1
  fi

  local TMPMNT=$(mktemp -d -p /tmp -t alienres.XXXXXX)
  if [ ! -d $TMPMNT ]; then
    echo "*** Failed to create temporary mount for the filesystem resize!"
    cleanup
    exit 1
  else
    chmod 711 ${TMPMNT}
  fi

  # Mount the block device prior to the resize
  # (btrfs, jfs and xfs do not support offline resize):
  mount -o rw -t ${MYFS} ${MYDEV} ${TMPMNT}

  # Resize the filesystem to occupy the full new device capacity:
  case "${MYFS}" in
    btrfs) btrfs filesystem resize max ${TMPMNT}
           ;;
    ext*)  resize2fs ${MYDEV}
           ;;
    f2fs)  resize.f2fs ${MYDEV}
           ;;
    jfs)   mount -o remount,resize,rw ${TMPMNT}
           ;;
    xfs)   xfs_growfs -d ${TMPMNT}
           ;;
    *)     echo "*** Unsupported filesystem '${MYFS}'!"; exit 1
           ;;
  esac

  if [ ! $? ]; then
    echo "*** Failed to resize '${MYFS}'filesystem on device '${MYDEV}'!"
    cleanup
    exit 1
  else
    # Un-mount the device again:
    sync
    umount ${TMPMNT}
    rmdir ${TMPMNT}
  fi
} # End of checkfs()

# Uncompress the initrd based on the compression algorithm used:
function uncompressfs () {
  if $(file "${1}" | grep -qi ": gzip"); then
    gzip -cd "${1}"
  elif $(file "${1}" | grep -qi ": XZ"); then
    xz -cd "${1}"
  fi
} # End of uncompressfs()

# Read configuration data from the initrd inside the ISO,
# after it has been extracted into a directory:
function read_initrddir() {
  local IMGDIR="$1"
  local INITVARS="$2"
  cd ${IMGDIR}

  # Read the values of liveslak template variables in the init script:
  for TEMPLATEVAR in ${INITVARS} ; do
    eval $(grep "^ *${TEMPLATEVAR}=" ./init |head -1)
  done
} # End of read_initrddir()

# Extract the initrd:
function extract_initrd() {
  local IMGFILE="$1"
  local IMGDIR=$(mktemp -d -p /tmp -t alienimg.XXXXXX)
  if [ ! -d $IMGDIR ]; then
    echo "*** Failed to create temporary extraction directory for the initrd!"
    cleanup
    exit 1
  else
    chmod 711 $IMGDIR
  fi

  cd ${IMGDIR}
    uncompressfs ${IMGFILE} 2>/dev/null \
      | cpio -i -d -m -H newc 2>/dev/null
  echo "$IMGDIR"
} # End of extract_initrd()

# Determine size of a mounted partition (in MB):
function get_part_mb_size() {
  local MYSIZE
  MYSIZE=$(df -P -BM ${1} |tail -n -1 |tr -s '\t' ' ' |cut -d' ' -f2)
  echo "${MYSIZE%M}"
} # End of get_part_mb_size()

# Determine free space of a mounted partition (in MB):
function get_part_mb_free() {
  local MYSIZE
  MYSIZE=$(df -P -BM ${1} |tail -n -1 |tr -s '\t' ' ' |cut -d' ' -f4)
  echo "${MYSIZE%M}"
} # End of get_part_mb_free()

# Determine requested container size in MB (allow for '%|k|K|m|M|g|G' suffix):
function cont_mb() {
  local MYSIZE="$1"
  case "${MYSIZE: -1}" in
     "%") MYSIZE="$(( $PARTFREE * ${MYSIZE%\%} / 100 ))" ;;
     "k") MYSIZE="$(( ${MYSIZE%k} / 1024 ))" ;;
     "K") MYSIZE="$(( ${MYSIZE%K} / 1024 ))" ;;
     "m") MYSIZE="${MYSIZE%m}" ;;
     "M") MYSIZE="${MYSIZE%M}" ;;
     "g") MYSIZE="$(( ${MYSIZE%g} * 1024 ))" ;;
     "G") MYSIZE="$(( ${MYSIZE%G} * 1024 ))" ;;
       *) MYSIZE=-1 ;;
  esac
  echo "$MYSIZE"
} # End of cont_mb()

# Expand existing encrypted container file:
function expand_container() {
  local MYPART="$1" # disk partition
  local MYINC="$2"  # requested increase ('%|k|K|m|M|g|G' suffix)
  local MYFILE="$3" # full path to ${CNTEXT} containerfile
  local MYMAP=""    # Name of the device-mapped file

  # Determine requested container increase in MB:
  MYINC=$(cont_mb ${MYINC})

  # Determine size of the target partition (in MB), and the free space:
  PARTSIZE=$(get_part_mb_size ${MYPART})
  PARTFREE=$(get_part_mb_free ${MYPART})

  if [ $PARTFREE -lt $(( ${MYINC} + ${MINFREE} )) ]; then
    echo "*** Free space on USB partition after file-resizing would be less than ${MINFREE} MB;"
    echo "*** Not resizing the container file!"
    cleanup
    exit 1
  fi

  # Append random bytes to the end of the container file:
  dd if=/dev/urandom of=${MYFILE} bs=1M count=${MYINC} oflag=append conv=notrunc 2>/dev/null

  # Setup a loopback device that we can use with or without cryptsetup:
  LODEV=$(losetup -f)
  losetup $LODEV ${MYFILE}

  if cryptsetup isLuks ${LODEV} ; then
    # Unlock LUKS encrypted container first:
    MYMAP=$(basename ${MYFILE} ${CNTEXT})
    CNTDEV=/dev/mapper/${MYMAP}
    echo "--- Unlocking the LUKS container requires your passphrase..."
    until cryptsetup luksOpen ${LODEV} ${MYMAP} ; do
      echo ">>> Did you type an incorrect passphrases?"
      read -p ">>> Press [ENTER] to try again or Ctrl-C to abort ..." REPLY 
    done
  else
    # The loopmounted block device for the un-encrypted container:
    CNTDEV=${LODEV}
  fi

  # Run fsck so the filesystem is clean before we resize it:
  fsck -fvy ${CNTDEV}
  # Resize the filesystem to occupy the full new size:
  resizefs ${CNTDEV}
  # Just to be safe:
  fsck -fvy ${CNTDEV}

  # Don't forget to clean up after ourselves:
  if cryptsetup isLuks ${LODEV} ; then
    cryptsetup luksClose ${MYMAP}
  fi
  losetup -d ${LODEV} || true

} # End of expand_container()

# Create container file in the empty space of the partition
function create_container() {
  local CNTPART=$1 # partition containing the ISO
  local CNTSIZE=$2 # size of the container file to create
  local CNTFILE=$3 # ${CNTEXT} filename with full path
  local CNTENCR=$4 # 'none' or 'luks'
  local CNTUSED=$5 # 'persistence', '/home' or custom mountpoint
  local MYMAP

  # Create a container file or re-use previously created one:
  if [ -f ${CNTFILE} ]; then
    CNTSIZE=$(( $(du -sk ${CNTFILE} |tr '\t' ' ' |cut -f1 -d' ') / 1024 ))
    echo "--- Keeping existing '${CNTFILE}' (size ${CNTSIZE} MB)."
    return
  fi

  # Determine size of the target partition (in MB), and the free space:
  PARTSIZE=$(get_part_mb_size ${CNTPART})
  PARTFREE=$(get_part_mb_free ${CNTPART})

  if [ $PARTFREE -lt ${MINFREE} ]; then
    echo "*** Free space on USB partition is less than ${MINFREE} MB;"
    echo "*** Not creating a container file!"
    cleanup
    exit 1
  fi

  # Determine requested container size in MB (allow for '%|k|K|m|M|g|G' suffix):
  CNTSIZE=$(cont_mb ${CNTSIZE})

  if [ $CNTSIZE -le 0 ]; then
    echo "*** Container size must be larger than ZERO!"
    echo "*** Check your commandline parameter."
    cleanup
    exit 1
  elif [ $CNTSIZE -ge $PARTFREE ]; then
    echo "*** Not enough free space for container file!"
    echo "*** Check your commandline parameter."
    cleanup
    exit 1
  fi

  echo "--- Creating ${CNTSIZE} MB container file '$(basename =${CNTFILE})' using 'dd if=/dev/urandom', patience please..."
  mkdir -p $(dirname "${CNTFILE}")
  if [ $? ]; then
    # Create a sparse file (not allocating any space yet):
    dd of=${CNTFILE} bs=1M count=0 seek=$CNTSIZE 2>/dev/null
  else
    echo "*** Failed to create directory for the container file!"
    cleanup
    exit 1
  fi

  # Setup a loopback device that we can use with cryptsetup:
  LODEV=$(losetup -f)
  losetup $LODEV ${CNTFILE}
  MYMAP=$(basename ${CNTFILE} ${CNTEXT})
  if [ "${CNTENCR}" = "luks" ]; then
    # Format the loop device with LUKS:
    echo "--- Encrypting the container file with LUKS; takes SOME time..."
    echo "--- enter 'YES' and a passphrase:"
    until cryptsetup -y luksFormat $LODEV ; do
      echo ">>> Did you type two different passphrases?"
      read -p ">>> Press [ENTER] to try again or Ctrl-C to abort ..." REPLY 
    done
    # Unlock the LUKS encrypted container:
    echo "--- Unlocking the LUKS container requires your passphrase again..."
    until cryptsetup luksOpen $LODEV ${MYMAP} ; do
      echo ">>> Did you type an incorrect passphrases?"
      read -p ">>> Press [ENTER] to try again or Ctrl-C to abort ..." REPLY 
    done
    CNTDEV=/dev/mapper/${MYMAP}
    # Now we allocate blocks for the LUKS device. We write encrypted zeroes,
    # so that the file looks randomly filled from the outside.
    # Take care not to write more bytes than the internal size of the container:
    echo "--- Writing ${CNTSIZE} MB of random data to encrypted container; takes LONG time..."
    CNTIS=$(( $(lsblk -b -n -o SIZE  $(readlink -f ${CNTDEV})) / 512))
    dd if=/dev/zero of=${CNTDEV} bs=512 count=${CNTIS} status=progress || true
  else
    # Un-encrypted container files remain sparse.
    CNTDEV=$LODEV
  fi

  # Format the now available block device with a linux fs:
  createfs ${CNTDEV} ${FSYS}

  if [ "${CNTUSED}" == "${DEFMNT}" ]; then
    # Copy the original /home content into the container.
    # NOTE: we only do this for /home, not for any other mountpoint!

    # Create a mount point for the unlocked container:
    CNTMNT=$(mktemp -d -p /var/tmp -t aliencnt.XXXXXX)
    if [ ! -d $CNTMNT ]; then
      echo "*** Failed to create temporary mount point for the LUKS container!"
      cleanup
      exit 1
    else
      chmod 711 $CNTMNT
    fi
    echo "--- Copying '${CNTUSED}' from ISO to container..."
    HOMESRC=$(find ${ISOMNT} -name "0099-slackware_zzzconf*" |tail -1)
    mount ${CNTDEV} ${CNTMNT}
    unsquashfs -n -d ${CNTMNT}/temp ${HOMESRC} ${CNTUSED}
    mv ${CNTMNT}/temp/${CNTUSED}/* ${CNTMNT}/
    rm -rf ${CNTMNT}/temp
    umount ${CNTDEV}
  fi

  # Don't forget to clean up after ourselves:
  if [ "${CNTENCR}" = "luks" ]; then
    cryptsetup luksClose $(basename ${CNTFILE} ${CNTEXT})
  fi
  losetup -d ${LODEV} || true

} # End of create_container()

function read_isoconfig() {
  local MYISO="${1}"
  # Read ISO customization from the .cfg file if it exists:
    if [ -f "${MYISO%.iso}.cfg" ]; then
      for LIVEPARM in \
        BLACKLIST KEYMAP LIVE_HOSTNAME LIVESLAKROOT LOAD LOCALE LUKSVOL \
        NOLOAD ISOPERSISTENCE RUNLEVEL TWEAKS TZ XKB ;
      do
        # Read values from disk only if the variable has not been set yet:
        if [ -z "$(eval echo \$${LIVEPARM})" ]; then
          eval $(grep -w ^${LIVEPARM} ${MYISO%.iso}.cfg)
        fi
      done
    fi
} # End of read_isoconfig()

function write_isoconfig() {
  local MYISO="${1}"
  # Write updated customization into the ISO .cfg:
  echo "# Liveslak ISO configuration file for ${VERSION}" > ${MYISO%.iso}.cfg 2>/dev/null
  echo "# Generated by $(basename $0) on $(date +%Y%m%d_%H%M)" >> ${MYISO%.iso}.cfg 2>/dev/null
  if [ $? -ne 0 ]; then
    echo "***  Media '${USBPART}' read-only, cannot write config file."
  else
    for LIVEPARM in \
      BLACKLIST KEYMAP LIVE_HOSTNAME LIVESLAKROOT LOAD LOCALE LUKSVOL \
      NOLOAD ISOPERSISTENCE RUNLEVEL TWEAKS TZ XKB ;
    do
      if [ -n "$(eval echo \$$LIVEPARM)" ]; then
        echo $LIVEPARM=$(eval echo \$$LIVEPARM) >> ${MYISO%.iso}.cfg
      fi 
    done
  fi
} # End of write_isoconfig()

#
#  -- end of function definitions --
#

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

# Parse the commandline parameters:
if [ -z "$1" ]; then
  showhelp
  exit 1
fi
while [ ! -z "$1" ]; do
  case $1 in
    -d|--directory)
      LIVESLAKROOT="$2"
      [[ ${LIVESLAKROOT::1} != "/" ]] && LIVESLAKROOT="$(pwd)/${LIVESLAKROOT}"
      shift 2
      ;;
    -e|--examples)
      showexamples
      exit
      ;;
    -f|--force)
      FORCE=1
      shift
      ;;
    -h|--help)
      showhelp
      exit
      ;;
    -i|--iso)
      SLISO="$(cd "$(dirname "$2")"; pwd)/$(basename "$2")"
      shift 2
      ;;
    -l|--lukscontainer)
      LUKSMNT="$(echo "$2" |cut -f2 -d:)"
      LUKSCNT="$(echo "$2" |cut -f1 -d:)"
      # If no mountpoint was specified, use the default mountpoint (/home):
      [ "$LUKSMNT" == "$LUKSCNT" ] && LUKSMNT=${DEFMNT}
      LUKSCNT="$(cd "$(dirname "$LUKSCNT")"; pwd)/$(basename "$LUKSCNT")"
      shift 2
      ;;
    -p|--persistence)
      PERSISTENCE="$(cd "$(dirname "$2")"; pwd)/$(basename "$2")"
      shift 2
      ;;
     -x|--extend)
      EXTENSION="$(cd "$(dirname "$2")"; pwd)/$(basename "$2")"
      shift 2
      ;;
    -F|--filesystem)
      FSYS="$2"
      shift 2
      ;;
    -L|--lcsize)
      LUKSSIZE="$2"
      shift 2
      ;;
    -P|--perssize)
      PERSSIZE="$2"
      shift 2
      ;;
    -X|--extendsize)
      INCSIZE="$2"
      shift 2
      ;;
    *)
      echo "*** Unknown parameter '$1'!"
      exit 1
      ;;
  esac
done

#
# Sanity checks:
#

if [ "$(id -u)" != "0" ]; then
  echo "*** You need to be root to run $(basename $0)."
  exit 1
fi

# Add required filesystem tools:
REQTOOLS="${REQTOOLS} mkfs.${FSYS}"

# Are all the required tools present?
PROG_MISSING=""
for PROGN in ${REQTOOLS} ; do
  if ! which $PROGN 1>/dev/null 2>/dev/null ; then
    PROG_MISSING="${PROG_MISSING}--   $PROGN\n"
  fi
done
if [ ! -z "$PROG_MISSING" ] ; then
  echo "--- Required program(s) not found in search path '$PATH'!"
  echo -e ${PROG_MISSING}
  if [ $FORCE -eq 0 ]; then
    echo "--- Exiting."
    exit 1
  fi
fi

if [ -z "${SLISO}" ]; then
  echo "*** You must specify the path to the Live ISO (option '-i')!"
  exit 1
fi

if [ ! -f "$SLISO" ]; then
  echo "*** This is not a useable file: '$SLISO' !"
  exit 1
fi

if [ -z "${LIVESLAKROOT}${LUKSCNT}${PERSISTENCE}${EXTENSION}" ]; then
  echo "*** No action requested!"
  exit 1
fi

if [ -n "${PERSISTENCE}" ]; then
  if [ -z "${PERSSIZE}" ]; then
    echo "*** Persistence filename '${PERSISTENCE}' defined but no filesize provided!"
    echo "*** Not enabling persistence, please use '-P' parameter."
    exit 1
  elif [ "$(basename ${PERSISTENCE} ${CNTEXT})" == "$(basename ${PERSISTENCE})" ]; then
    echo  "*** File '${PERSISTENCE}' does not have an '${CNTEXT}' extension!"
    if [ $FORCE -eq 0 ]; then
      exit 1
    else
      CNTEXT=$(basename ${PERSISTENCE})
      if [ "${CNTEXT}" != "${CNTEXT##*.}" ]; then
        # File has a different extension:
        echo  "--- Accepting '${CNTEXT##*.}' extension for '${PERSISTENCE}'."
        CNTEXT=${CNTEXT##*.}
      else
        # File does not have an extension at all, so we add one:
        echo  "--- Adding '${DEFEXT}' extension to '${PERSISTENCE}'."
        PERSISTENCE="${PERSISTENCE}${DEFEXT}"
      fi
    fi
  fi
fi

if [ -n "${LUKSCNT}" ]; then
  if [ -z "${LUKSSIZE}" ]; then
    echo "*** LUKS container '${LUKSCNT}' defined but no filesize provided!"
    echo "*** Not adding encrypted ${LUKSMNT}, please use '-L' parameter."
    exit 1
  elif [ "$(basename ${LUKSCNT} ${CNTEXT})" == "$(basename ${LUKSCNT})" ]; then
    echo  "*** File '${LUKSCNT}' does not have an '${CNTEXT}' extension!"
    if [ $FORCE -eq 0 ]; then
      exit 1
    else
      CNTEXT=$(basename ${LUKSCNT})
      if [ "${CNTEXT}" != "${CNTEXT##*.}" ]; then
        # File has a different extension:
        echo  "--- Accepting '${CNTEXT##*.}' extension for '${LUKSCNT}'."
        CNTEXT=${CNTEXT##*.}
      else
        # File does not have an extension at all, so we add one:
        echo  "--- Adding '${DEFEXT}' extension to '${LUKSCNT}'."
        LUKSCNT="${LUKSCNT}${DEFEXT}"
      fi
    fi
  fi
fi

if [ -n "${EXTENSION}" ]; then
  if [ -z "${INCSIZE}" ]; then
    echo "*** LUKS container '${EXTENSION}' defined but no extansion size provided!"
    echo "*** Not extending encrypted ${EXTENSION}, please use '-X' parameter."
    exit 1
  elif [ "$(basename ${EXTENSION} ${CNTEXT})" == "$(basename ${EXTENSION})" ]; then
    echo  "*** File '${EXTENSION}' does not have an '${CNTEXT}' extension!"
    if [ $FORCE -eq 0 ]; then
      exit 1
    else
      CNTEXT=$(basename ${EXTENSION})
      if [ "${CNTEXT}" != "${CNTEXT##*.}" ]; then
        # File has a different extension:
        echo  "--- Accepting '${CNTEXT##*.}' extension for '${EXTENSION}'."
        CNTEXT=${CNTEXT##*.}
      else
        # File does not have an extension at all, so we add one:
        echo  "--- Adding '${DEFEXT}' extension to '${EXTENSION}'."
        EXTENSION="${EXTENSION}${DEFEXT}"
      fi
    fi
  fi
fi

# Determine name and mountpoint of the partition containing the ISO:
USBPART=$(cd $(dirname ${SLISO}) ; df . |tail -n -1 |tr -s ' ' |cut -d' ' -f1)
USBMNT=$(cd $(dirname ${SLISO}) ; df . |tail -n -1 |tr -s ' ' |cut -d' ' -f6)

# Determine size of the USB partition (in MB), and the free space:
USBPSIZE=$(get_part_mb_size ${USBMNT})
USBPFREE=$(get_part_mb_free ${USBMNT})

# Report the Slackware Live version:
VERSION=$(isoinfo -d -i "${SLISO}" 2>/dev/null |grep Application |cut -d: -f2-)
echo "--- The ISO on medium '${USBPART}' is '${VERSION}'"

# Try a write to the partition:
if touch ${USBMNT}/.rwtest 2>/dev/null && rm ${USBMNT}/.rwtest 2>/dev/null
then
  echo "--- The medium '${USBPART}' is writable."
else
  echo "--- Trying to remount readonly medium '${USBPART}' as writable..."
  mount -o remount,rw ${USBMNT}
  if [ $? -ne 0 ]; then
    echo "*** Failed to remount '${USBPART}' writable, unable to continue!"
    cleanup
    exit 1
  fi
fi

# Create a mount point for the ISO:
ISOMNT=$(mktemp -d -p /var/tmp -t alieniso.XXXXXX)
if [ ! -d $ISOMNT ]; then
  echo "*** Failed to create temporary mount point for the ISO!"
  cleanup
  exit 1
else
  chmod 711 $ISOMNT
  mount -o loop ${SLISO} ${ISOMNT}
fi

# Collect data from the USB initrd:
IMGDIR=$(extract_initrd ${ISOMNT}/boot/initrd.img)
read_initrddir ${IMGDIR} "DISTRO LIVEMAIN MARKER MEDIALABEL"

# Collect customization parameters for the ISO:
read_isoconfig ${SLISO}

# Determine where in LUKSVOL the /home is defined.
# The LUKSVOL value looks like:
# "/path/to/cntner1.icc:/mountpoint1,[/path/to/cntner2.icc:/mountpoint2,[...]]"
# Break down the LUKSVOL value into container/mountpoint combo's:
if [ -n "$LUKSVOL" ]; then
  _container=""
  _mount=""
  for _luksvol in $(echo $LUKSVOL |tr ',' ' '); do
    _container="$(echo $_luksvol |cut -d: -f1)"
    _mount="$(echo $_luksvol |cut -d: -f2)"
    if [ "$_mount" == "$_container" ]; then
      # No optional mount point specified, so we use the default:
      CONTAINERS["${DEFMNT}"]="$_container"
    else
      CONTAINERS["$_mount"]="$_container"
    fi
  done
fi

# Normalize paths on USB partition (remove mountpoint):
if [ -n "${PERSISTENCE}" ]; then
  PERSISTENCE="${PERSISTENCE#$USBMNT}"
fi
if [ -n "${LUKSCNT}" ]; then
  LUKSCNT="${LUKSCNT#$USBMNT}"
fi
if [ -n "${EXTENSION}" ]; then
  EXTENSION="${EXTENSION#$USBMNT}"
fi

# Should we create a liveslak root directory?
if [ -n "${LIVESLAKROOT}" ]; then
  # The directory may already exist, in which case we obtained its name
  # from the configfile. But creating directory tree is harmless:
  mkdir -p ${LIVESLAKROOT}/${LIVEMAIN}/{addons,optional,core2ram}
  # Normalize the path, removing the mount point:
  LIVESLAKROOT="$(cd "$(dirname "$LIVESLAKROOT")"; pwd)$(basename "$LIVESLAKROOT")"
  LIVESLAKROOT="${LIVESLAKROOT#$USBMNT}"
fi

# Should we create a persistence container?
if [ -n "${PERSISTENCE}" ]; then
  # Create LUKS persistence container file (or re-use it if existing):
  create_container ${USBPART} ${PERSSIZE} ${USBMNT}${PERSISTENCE} luks persistence
  ISOPERSISTENCE="${PERSISTENCE}"
fi

# Should we add a LUKS container to mount at /home or specified other mount?
if [ -n "${LUKSCNT}" ]; then
  if [ -v 'CONTAINERS["${LUKSMNT}"]' ] && [ "${LUKSCNT}" != "${CONTAINERS["${LUKSMNT}"]}" ]; then
    # The configfile specifies a different mount for container:
    echo "*** On-disk configuration defines an existing mountpoint ${LUKSMNT}"
    echo "*** at '${USBMNT}${CONTAINERS["${LUKSMNT}"]}',"
    echo "*** which is different from your '-l ${USBMNT}${LUKSCNT}'."
    if [ $FORCE -eq 0 ]; then
      echo "*** Not adding encrypted container for ${LUKSMNT} , please fix the entry"
      echo "*** in '${SLISO%.iso}.cfg',"
      echo "*** or supply the correct value for the '-l' parameter!"
      cleanup
      exit 1
    else
      echo "--- Accepting new mountpoint '${LUKSMNT}' for encrypted container ${LUKSMNT}"
    fi
  fi
  # Create LUKS container file for the mount point (or re-use it if existing):
  create_container ${USBPART} ${LUKSSIZE} ${USBMNT}${LUKSCNT} luks ${LUKSMNT}
  CONTAINERS["${LUKSMNT}"]="${LUKSCNT}"
fi

# Should we extend the size of a container?
if [ -n "${EXTENSION}" ]; then
  # Expand existing container file:
  expand_container ${USBPART} ${INCSIZE} ${USBMNT}/${EXTENSION}
fi

if [ ${#CONTAINERS[@]} -gt 0 ]; then
  # CONTAINERS array is non-empty; (re-)assemble the LUKSVOL variable.
  # First zap the LUKSVOL value:
  LUKSVOL=""
  # Write the CONTAINERS array back into LUKSVOL in the correct format:
  for _mount in "${!CONTAINERS[@]}"; do
    LUKSVOL="${LUKSVOL}${CONTAINERS[$_mount]}:${_mount},"
  done
  # Remove the trailing ',':
  LUKSVOL="${LUKSVOL::-1}"
fi

# Write customization parameters for the ISO to disk:
write_isoconfig ${SLISO}

# Write ISO version to the liveslak rootdir if that exists:
if [ -d "${USBMNT}/${LIVESLAKROOT}" ]; then
  echo "$VERSION" > ${USBMNT}/${LIVESLAKROOT}/.isoversion
fi

# Unmount/remove stuff:
cleanup

# THE END