summaryrefslogblamecommitdiffstats
path: root/upslak.sh
blob: cd095d7eac5ac2007f0dc2865fd105a5701eec3c (plain) (tree)
1
2
3

           
                                                     





































































                                                                               


             






                
               
               







































                                                                              
                                                  










                                                                                                                                                                                                                                                                                                                                                                           
                                                  










































































                                                                                                                                                                                     
                                                                                                                                                                            





















































































































                                                                                                                                 




















                                                                                                                                                                                            








                                                                 
                           










                                                                      
                                                                                                                                                         






































                                                                                        
                             







































































                                                                                         
                                                                               


























































                                                                          
                                             


                                    
                                                            
                                   
                           



                                             




























                                                                             
                                                              


                
                                                            


                 
                                                             


































































                                                                           
                                                


                                            

                                                                                       


































                                                                           
                                                                                                                 










































                                                                             

                                                                          

























                                                                                                                             




                                                                              






























                                                                            
                                   

                          
                                                      





























































































































                                                                                                   
#!/bin/bash
#
# Copyright 2017, 2019  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 the following changes on
# the USB version of on Slackware Live Edition.
# - upgrade the kernel and modules
# - add network support modules for PXE boot (if missing)
# - increase (or decrease) USB wait time during boot
# - replace the Live init script inside the initrd image
# - move current persistence data to a new squashfs module in 'addons'
#
# -----------------------------------------------------------------------------

# Be careful:
set -e

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

# ---------------------------------------------------------------------------
# START possible tasks to be executed by the script:
# ---------------------------------------------------------------------------

# By default do not move persistence data into a new Live module:
CHANGES2SXZ=0

# Replace the live 'init' script with the file $LIVEINIT:
LIVEINIT=""

# Do we need to add network support? This can be enforced through commandline,
# otherwise will be determined by examining the original kernelmodules:
NETSUPPORT=""

# This will be set to '1' if the user wants to restore the backups of the
# previous kernel and modules:
RESTORE=0

# Set default for 'do we update the kernel':
UPKERNEL=0

# Do not change usb wait time by default:
WAIT=-1

# ---------------------------------------------------------------------------
# END possible tasks to be executed by the script:
# ---------------------------------------------------------------------------

# Determine whether the USB stick has a supported kernel configuration
# i.e. one active and optionally one backup kernel plus mmodules:
SUPPORTED=1

# Values obtained from the init script on the USB:
DEF_KBD=""
DEF_LOCALE=""
DEF_TZ=""
DISTRO=""
LIVE_HOSTNAME=""
LIVEMAIN=""
LIVEUID=""
MARKER=""
MEDIALABEL=""
PERSISTENCE=""
CORE2RAMMODS=""
SQ_EXT_AVAIL=""
VERSION=""

# By default we make a backup of your old kernel/modules when adding new ones:
KBACKUP=1

# Does the initrd contain an old kernel that we can restore?
# The 'read_initrd' routing may set this to '0':
KRESTORE=1

# Timeout when scanning for inserted USB device, 30 seconds by default,
# but this default can be changed from outside the script:
SCANWAIT=${SCANWAIT:-30}

# By default do not show file operations in detail:
VERBOSE=0

# Set to '1' if we are to scan for device insertion:
SCAN=0

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

# Variables to store content from an initrd we are going to refresh:
OLDKERNELSIZE=""
OLDKMODDIRSIZE=""
OLDKVER=""
OLDWAIT=""

# Record the version of the new kernel:
KVER=""

# Define ahead of time, so that cleanup knows about them:
IMGDIR=""
KERDIR=""
USBMNT=""
EFIMNT=""

# These tools are required by the script, we will check for their existence:
REQTOOLS="cpio gdisk inotifywait lsblk strings xz"

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

# -- START: Taken verbatim from make_slackware_live.sh -- #
# List of kernel modules required for a live medium to boot properly;
# Lots of HID modules added to support keyboard input for LUKS password entry:
KMODS=${KMODS:-"squashfs:overlay:loop:xhci-pci:ohci-pci:ehci-pci:xhci-hcd:uhci-hcd:ehci-hcd:mmc-core:mmc-block:sdhci:sdhci-pci:sdhci-acpi:usb-storage:hid:usbhid:i2c-hid:hid-generic:hid-apple:hid-cherry:hid-logitech:hid-logitech-dj:hid-logitech-hidpp:hid-lenovo:hid-microsoft:hid_multitouch:jbd:mbcache:ext3:ext4:isofs:fat:nls_cp437:nls_iso8859-1:msdos:vfat:ntfs"}

# Network kernel modules to include for NFS root support:
NETMODS="kernel/drivers/net kernel/drivers/virtio"

# Network kernel modules to exclude from above list:
NETEXCL="appletalk arcnet bonding can dummy.ko hamradio hippi ifb.ko irda macvlan.ko macvtap.ko pcmcia sb1000.ko team tokenring tun.ko usb veth.ko wan wimax wireless xen-netback.ko"
# -- END: Taken verbatim from make_slackware_live.sh -- #

#
#  -- function definitions --
#

# Clean up in case of failure:
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
  # No longer needed:
  [ -n "${IMGDIR}" ] && ( rm -rf $IMGDIR )
  [ -n "${KERDIR}" ] && ( rm -rf $KERDIR )
  if [ -n "${USBMNT}" ]; then
    if mount |grep -qw ${USBMNT} ; then umount ${USBMNT} ; fi
    rm -rf $USBMNT
  fi
  if [ -n "${EFIMNT}" ]; then
    if mount |grep -qw ${EFIMNT} ; then umount ${EFIMNT} ; fi
    rm -rf $EFIMNT
  fi
  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:
showhelp() {
cat <<EOT
#
# Purpose: to update the content of a Slackware Live USB stick.
#
# $(basename $0) accepts the following parameters:
#   -b|--nobackup              Do not try to backup original kernel and modules.
#   -d|--devices               List removable devices on this computer.
#   -h|--help                  This help.
#   -i|--init <filename>       Replacement init script.
#   -k|--kernel <filename>     The kernel file (or package).
#   -m|--kmoddir <name>        The kernel modules directory (or package).
#   -n|--netsupport            Add network boot support if not yet present.
#   -o|--outdev <filename>     The device name of your USB drive.
#   -p|--persistence           Move persistent data into new Live module.
#   -r|--restore               Restore previous kernel and modules.
#   -s|--scan                  Scan for insertion of new USB device instead of
#                              providing a devicename (using option '-o').
#   -v|--verbose               Show verbose messages.
#   -w|--wait<number>          Add <number> seconds wait time to initialize USB.
#
EOT
} # End of showhelp()

# Scan for insertion of a USB device:
scan_devices() {
  local BD
  # Inotifywatch does not trigger on symlink creation,
  # so we can not watch /sys/block/
  BD=$(inotifywait -q -t ${SCANWAIT} -e create /dev 2>/dev/null |cut -d' ' -f3)
  echo ${BD}
} # End of scan_devices()

# Show a list of removable devices detected on this computer:
show_devices() {
  local MYDATA="${*}"
  if [ -z "${MYDATA}" ]; then
    MYDATA="$(ls --indicator-style=none /sys/block/ |grep -Ev '(ram|loop|dm-)')"
  fi
  echo "# Removable devices detected on this computer:"
  for BD in ${MYDATA} ; do
    if [ $(cat /sys/block/${BD}/removable) -eq 1 ]; then
      echo "# /dev/${BD} : $(cat /sys/block/${BD}/device/vendor 2>/dev/null) $(cat /sys/block/${BD}/device/model 2>/dev/null): $(( $(cat /sys/block/${BD}/size) / 2048)) MB"
    fi
  done
  echo "#"
} # End of show_devices()

# Uncompress the initrd based on the compression algorithm used:
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 ()

# Collect the kernel modules we need for the liveslak initrd.
# When calling this function, the old module tree must already
# have been renamed to ${OLDKVER}.prev
collect_kmods() {
  local IMGDIR="$1"

  # Borrow (and mangle) code from Slackware's mkinitrd
  # to convert the KMODS variable into a collection of modules:
  # Sanitize the modules list first, before any further processing.
  # The awk command eliminates doubles without changing the order:
  KMODS=$(echo $KMODS |tr -s ':' '\n' |awk '!x[$0]++' |tr '\n' ':')
  KMODS=$(echo ${KMODS%:}) # Weed out a trailing ':'

  # Count number of modules
  # This INDEX number gives us an easy way to find individual
  # modules and their arguments, as well as tells us how many
  # times to run through the list
  if ! echo $KMODS | grep ':' > /dev/null ; then  # only 1 module specified
    INDEX=1
  else
    # Trim excess ':' which will screw this routine:
    KMODS=$(echo $KMODS | tr -s ':')
    INDEX=1
    while [ ! "$(echo "$KMODS" | cut -f $INDEX -d ':' )" = "" ]; do
      INDEX=$(expr $INDEX + 1)
    done
    INDEX=$(expr $INDEX - 1)      # Don't include the null value
  fi

  mkdir -p $IMGDIR/lib/modules/${KVER}

  # Wrap everything in a while loop
  i=0
  while [ $i -ne $INDEX ]; do
    i=$(( $i + 1 ))
  
    # FULL_MOD is the module plus any arguments (if any)
    # MODULE is the module name
    # ARGS is any optional arguments to be passed to the kernel
    FULL_MOD="$(echo "$KMODS" | cut -d ':' -f $i)"
    MODULE="$(echo "$FULL_MOD" | cut -d ' ' -f 1 )"
    # Test for arguments
    if echo "$FULL_MOD" | grep ' ' > /dev/null; then
      ARGS=" $(echo "$FULL_MOD" | cut -d ' ' -f 2- )"
    else
      unset ARGS
    fi

    # Get MODULE deps and prepare modprobe lines
    modprobe --dirname ${KMODDIR%%/lib/modules/${KVER}} --set-version $KVER --show-depends --ignore-install $MODULE 2>/dev/null \
      | grep "^insmod " | cut -f 2 -d ' ' | while read SRCMOD; do

      if ! grep -Eq " $(basename $SRCMOD .ko)(\.| |$)" $IMGDIR/load_kernel_modules 2>/dev/null ; then
        LINE="$(echo "modprobe -v $(basename ${SRCMOD%%.gz} .ko)" )"

        # Test to see if arguments should be passed
        # Over-ride the previously defined LINE variable if so
        if [ "$(basename $SRCMOD .ko)" = "$MODULE" ]; then
          # SRCMOD and MODULE are same, ARGS can be passed
          LINE="$LINE$ARGS"
        fi

      fi

      if ! grep -qx "$LINE" $IMGDIR/load_kernel_modules ; then
        echo "$LINE" >> $IMGDIR/load_kernel_modules
      fi

      # Try to add the module to the initrd-tree.  This should be done
      # even if it exists there already as we may have changed compilers
      # or otherwise caused the modules in the initrd-tree to need
      # replacement.
      cd ${KMODDIR}
        # Need to strip ${KMODDIR} from the start of ${SRCMOD}:
        cp -a --parents  $(echo $SRCMOD |sed 's|'${KMODDIR}'/|./|' ) $IMGDIR/lib/modules/${KVER}/ 2>/dev/null
        COPYSTAT=$?
      cd - 1>/dev/null
      if [ $COPYSTAT -eq 0 ]; then
        if [ $VERBOSE -eq 1 ]; then
          echo "OK: $SRCMOD added."
        fi
        # If a module needs firmware, copy that too
        modinfo -F firmware "$SRCMOD" | sed 's/^/\/lib\/firmware\//' |
        while read SRCFW; do
          if cp -a --parents "$SRCFW" $IMGDIR 2>/dev/null; then
            if [ $VERBOSE -eq 1 ]; then
              echo "OK: $SRCFW added."
            fi
          else
            echo "*** WARNING:  Could not find firmware \"$SRCFW\""
          fi
        done
      else
        echo "*** WARNING:  Could not find module \"$SRCMOD\""
      fi
      unset COPYSTAT

    done
  done
  # End of Slackware mkinitrd code.

  # Do we have to add network support?
  if [ $NETSUPPORT -eq 1 ]; then
    # The initrd already contains dhcpcd so we just need to add kmods:
    for NETMODPATH in ${NETMODS} ; do 
      cd ${KMODDIR}
        mkdir -p ${IMGDIR}/lib/modules/${KVER}
        cp -a --parents ${NETMODPATH} ${IMGDIR}/lib/modules/${KVER}/
      cd - 1>/dev/null
      # Prune the ones we do not need:
      for KNETRM in ${NETEXCL} ; do
        find ${IMGDIR}/lib/modules/${KVER}/${NETMODPATH} \
          -name $KNETRM -depth -exec rm -rf {} \;
      done
      # Add any dependency modules:
      for MODULE in $(find ${IMGDIR}/lib/modules/${KVER}/${NETMODPATH} -type f -exec basename {} .ko \;) ; do
        modprobe --dirname ${KMODDIR%%/lib/modules/${KVER}} --set-version $KVER --show-depends --ignore-install $MODULE 2>/dev/null |grep "^insmod " |cut -f 2 -d ' ' |while read SRCMOD; do
          if [ "$(basename $SRCMOD .ko)" != "$MODULE" ]; then
            cd ${KMODDIR}
              # Need to strip ${KMODDIR} from the start of ${SRCMOD}:
              cp -a --parents $(echo $SRCMOD |sed 's|'${KMODDIR}'/|./|' ) \
                ${IMGDIR}/lib/modules/${KVER}/
            cd - 1>/dev/null
          fi
        done
      done
    done
  fi
  # We added extra modules to the initrd, so we run depmod again:
  if [ $VERBOSE -eq 1 ]; then
    chroot ${IMGDIR} depmod $KVER
  else
    chroot ${IMGDIR} depmod $KVER 2>/dev/null
  fi
} # End of collect_kmods ()

# Read configuration data from old initrd:
read_initrd() {
  local IMGDIR="$1"

  cd ${IMGDIR}

  # Retrieve the currently defined USB wait time:
  OLDWAIT=$(cat ./wait-for-root)

  # Read the values of liveslak template variables in the init script:
  for TEMPLATEVAR in DEF_KBD DEF_LOCALE DEF_TZ DISTRO LIVE_HOSTNAME LIVEMAIN LIVEUID MARKER MEDIALABEL PERSISTENCE CORE2RAMMODS SQ_EXT_AVAIL VERSION ; do
    eval $(grep "^ *${TEMPLATEVAR}=" ./init |head -1)
  done

  if [ $RESTORE -eq 1 ]; then
    # Add '||true' because grep's exit code '1' may abort the script:
    PREVMODDIR=$(find ./lib/modules -type d -mindepth 1 -maxdepth 1 |grep .prev || true)
    if [ -n "${PREVMODDIR}" ] ; then
      KRESTORE=1
    else
      echo "--- No backed-up kernel modules detected in '${IMGFILE}'."
      KRESTORE=0
    fi
  fi
  if [ $UPKERNEL -eq 1 ]; then
    OLDMODDIR=$(find ./lib/modules -type d -mindepth 1 -maxdepth 1 |grep -v .prev)
    if [ $(echo ${OLDMODDIR} |wc -w) -gt 1 ] ; then
      echo "*** Multiple kernelmodule trees detected in '${IMGFILE}'."
      SUPPORTED=0
    else
      OLDKVER=$(basename "${OLDMODDIR}")
      OLDKMODDIRSIZE=$(du -sm "${OLDMODDIR}" |tr '\t' ' ' |cut -d' ' -f1)
      # Find out if the old kernel contains network support.
      # Use presence of 'devlink.ko' in the old tree to determine this,
      # but allow for a pre-set override value based on commandline preference:
      if [ -f ${OLDMODDIR}/kernel/net/core/devlink.ko ]; then
        NETSUPPORT=${NETSUPPORT:-1}
      else
        NETSUPPORT=${NETSUPPORT:-0}
      fi
    fi
  fi
} # End read_initrd()

# Extract the initrd:
extract_initrd() {
  local IMGFILE="$1"

  cd ${IMGDIR}
    uncompressfs ${IMGFILE} \
      | cpio -i -d -m -H newc
} # End of extract_initrd()
    
# Modify the extracted initrd and re-pack it:
update_initrd() {
  local IMGFILE="$1"
  local NEED_RECOMP=0

  cd ${IMGDIR}
    if [ ${WAIT} -ge 0 ]; then
      if [ $WAIT != $OLDWAIT ]; then
        echo "--- Updating 'waitforroot' time from '$OLDWAIT' to '$WAIT'"
        echo ${WAIT} > wait-for-root
        NEED_RECOMP=1
      fi
    fi

    if [ $UPKERNEL -eq 1 ]; then
      OLDMODDIR=$(find ./lib/modules -type d -mindepth 1 -maxdepth 1 |grep -v .prev)
      rm -rf ./lib/modules/*.prev
      if [ $KBACKUP -eq 1 ]; then
        # We make one backup:
        if [ $VERBOSE -eq 1 ]; then
          echo "--- Making backup of kernel modules"
        fi
        mv -i ${OLDMODDIR} ${OLDMODDIR}.prev
      else
        echo "--- No room for backing up old kernel modules"
        rm -rf ${OLDMODDIR}
      fi
      # Add modules for the new kernel:
      echo "--- Adding new kernel modules"
      collect_kmods ${IMGDIR}
      NEED_RECOMP=1
    elif [ $RESTORE -eq 1 -a $KRESTORE -eq 1 ]; then
      # Restore previous kernel module tree.
      # The 'read_initrd' routine will already have checked that we have
      # one active and one .prev modules tree:
      OLDMODDIR=$(find ./lib/modules -type d -mindepth 1 -maxdepth 1 |grep .prev || true)
      NEWMODDIR=$(find ./lib/modules -type d -mindepth 1 -maxdepth 1 |grep -v .prev)
      echo "--- Restoring old kernel modules"
      rm -rf ${NEWMODDIR}
      mv ${OLDMODDIR} ${OLDMODDIR%.prev}
      NEED_RECOMP=1
    fi

    if [ -n "${LIVEINIT}" ]; then
      echo "--- Replacing live init script"
      cp ./init ./init.prev
      if grep -q "@LIVEMAIN@" ${LIVEINIT} ; then
        # The provided init is a liveinit template, and we need
        # to substitute the placeholders with actual values:
        parse_template ${LIVEINIT} $(pwd)/init
      else
        cat ${LIVEINIT} > ./init
      fi
      NEED_RECOMP=1
    fi

    if [ ${NEED_RECOMP} -eq 1 ]; then
      echo "--- Compressing the initrd image again"
      chmod 0755 ${IMGDIR}
      find . |cpio -o -H newc |$COMPR > ${IMGFILE}
    fi
  cd - 1>/dev/null  # End of 'cd ${IMGDIR}'
} # End of update_initrd()

# Accept either a kernelimage or a packagename,
# and return the path to a kernelimage:
getpath_kernelimg () {
  local MYDATA="${*}"
  [ -z "${MYDATA}" ] && echo ""

  if [ -n "$(file "${MYDATA}" |grep -E 'x86 boot (executable|sector)')" ]; then
    # We have a kernel image:
    echo "${MYDATA}"
  else
    # We assume a Slackware package:
    # Extract the generic kernel from the package and return its filename:
    tar --wildcards -C ${KERDIR} -xf ${MYDATA} boot/vmlinuz-generic-*
    echo "$(ls --indicator-style=none ${KERDIR}/boot/vmlinuz-generic-*)"
  fi
} # End of getpath_kernelimg

# Accept either a directory containing module tree, or a packagename,
# and return the path to a module tree:
getpath_kernelmods () {
  local MYDATA="${*}"
  [ -z "${MYDATA}" ] && echo ""

  if [ -d "${MYDATA}" ]; then
    # We have directory, assume it contains the  kernel modules:
    echo "${MYDATA}"
  else
    # We assume a Slackware package:
    # Extract the kernel modules from the package and return the path:
    tar -C ${KERDIR} -xf ${MYDATA} lib/modules
    cd ${KERDIR}/lib/modules/*
    pwd
  fi
} # End of getpath_kernelmods

# Determine size of a mounted partition (in MB):
get_part_mb_size() {
  local MYSIZE
  MYSIZE=$(df -P -BM ${1} |tail -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):
get_part_mb_free() {
  local MYSIZE
  MYSIZE=$(df -P -BM ${1} |tail -1 |tr -s '\t' ' ' |cut -d' ' -f4)
  echo "${MYSIZE%M}"
} # End of get_part_mb_free

parse_template() {
  # Parse a liveslak template file and substitute the placeholders.
  local INFILE="$1"
  local OUTFILE="$2"

  # We expect these variables to be set before calling this function.
  # But, we do provide default values.
  DISTRO=${DISTRO:-slackware}
  VERSION=${VERSION:-1337}

  cat ${INFILE} | sed \
    -e "s/@LIVEMAIN@/${LIVEMAIN:-liveslak}/g" \
    -e "s/@MARKER@/${MARKER:-LIVESLAK}/g" \
    -e "s/@MEDIALABEL@/${MEDIALABEL:-LIVESLAK}/g" \
    -e "s/@PERSISTENCE@/${PERSISTENCE:-persistence}/g" \
    -e "s/@DARKSTAR@/${LIVE_HOSTNAME:-darkstar}/g" \
    -e "s/@LIVEUID@/${LIVEUID:-live}/g" \
    -e "s/@LIVEUIDNR@/${LIVEUIDNR:-1000}/g" \
    -e "s/@DISTRO@/$DISTRO/g" \
    -e "s/@CDISTRO@/${DISTRO^}/g" \
    -e "s/@UDISTRO@/${DISTRO^^}/g" \
    -e "s/@CORE2RAMMODS@/${CORE2RAMMODS:-"min noxbase"}/g" \
    -e "s/@VERSION@/${VERSION}/g" \
    -e "s/@KVER@/$KVER/g" \
    -e "s/@SQ_EXT_AVAIL@/${SQ_EXT_AVAIL}/g" \
    -e "s,@DEF_KBD@,${DEF_KBD},g" \
    -e "s,@DEF_LOCALE@,${DEF_LOCALE},g" \
    -e "s,@DEF_TZ@,${DEF_TZ},g" \
    > ${OUTFILE}
} # End of parse_template()

#
#  -- end of function definitions --
#

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

# Parse the commandline parameters:
if [ -z "$1" ]; then
  showhelp
  exit 1
fi
while [ ! -z "$1" ]; do
  case $1 in
    -b|--nobackup)
      KBACKUP=0
      shift
      ;;
    -d|--devices)
      show_devices
      exit
      ;;
    -h|--help)
      showhelp
      exit
      ;;
    -i|--init)
      LIVEINIT="$(cd "$(dirname "$2")"; pwd)/$(basename "$2")"
      shift 2
      ;;
    -k|--kernel)
      KERNEL="$(cd "$(dirname "$2")"; pwd)/$(basename "$2")"
      shift 2
      ;;
    -m|--kmoddir)
      KMODDIR="$(cd "$(dirname "$2")"; pwd)/$(basename "$2")"
      shift 2
      ;;
    -n|--netsupport)
      NETSUPPORT=1
      shift
      ;;
    -o|--outdev)
      TARGET="$2"
      shift 2
      ;;
    -p|--persistence)
      CHANGES2SXZ=1
      shift
      ;;
    -r|--restore)
      RESTORE=1
      shift
      ;;
    -s|--scan)
      SCAN=1
      shift
      ;;
    -v|--verbose)
      VERBOSE=1
      shift
      ;;
    -w|--wait)
      WAIT="$2"
      shift 2
      ;;
    *)
      echo "*** Unknown parameter '$1'!"
      exit 1
      ;;
  esac
done

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

#
# More sanity checks:
#

# Either provide a block device, or else scan for a block device:
if [ -z "$TARGET" ]; then
  if [ $SCAN -eq 1 ]; then
    echo "-- Waiting ${SCANWAIT} seconds for a USB stick to be inserted..."
    TARGET=$(scan_devices)
    if [ -z "$TARGET" ]; then
      echo "*** No new USB device detected during $SCANWAIT seconds scan."
      exit 1
    else
      TARGET="/dev/${TARGET}"
    fi
  else
    echo "*** You must specify the Live USB devicename (option '-o')!"
    exit 1
  fi
elif [ $SCAN -eq 1 ]; then
  echo "*** You can not use options '-o' and '-s' at the same time!"
  exit 1
fi

if [ ! -e /sys/block/$(basename $TARGET) ]; then
  echo "*** Not a block device: '$TARGET' !"
  show_devices
  exit 1
elif lsblk -l $TARGET |grep -w $(basename $TARGET) |grep -wq part ; then
  echo "*** You need to point to the storage device itself, not a partition ($TARGET)!"
  show_devices
  exit 1
fi

if [ -z "$KERNEL" -a -z "$KMODDIR" ]; then
  # We don't need to update the kernel/modules:
  UPKERNEL=0
else
  if [ $RESTORE -eq 1 ]; then
    echo "*** You can not use options '-k'/'-m' and '-r' at the same time!"
    exit 1
  fi
  # If we get here, we have one or both '-k' and '-m'.
  # Sanitize the input values of '-k' and '-m':
  if [ -z "$KERDIR" ]; then
    # Create a temporary extraction directory:
    mkdir -p /mnt
    KERDIR=$(mktemp -d -p /mnt -t alienker.XXXXXX)
    if [ ! -d $KERDIR ]; then
      echo "*** Failed to create temporary extraction dir for the kernel!"
      cleanup
      exit 1
    fi
  fi
  KERNEL="$(getpath_kernelimg ${KERNEL})"
  KMODDIR="$(getpath_kernelmods ${KMODDIR})"

  if [ ! -f "${KERNEL}" -o ! -d "${KMODDIR}" ]; then
    echo "*** You need to provide the path to a kernel imagefile (-k),"
    echo "*** as well as the directory containing the kernel modules (-m)!"
    cleanup
    exit 1
  else
    # Determine the new kernel version from a module,
    # rather than from a directory- or filenames:
    KVER=$(strings $(find ${KMODDIR}/kernel/ -name "*.ko*" |head -1) |grep ^vermagic |cut -d= -f2 |cut -d' ' -f1)
    if [ -z "${KVER}" ]; then
      echo "*** Could not determine kernel version from the module directory"
      echo "*** (querying module kernel/fs/overlayfs/overlay.ko)!"
      cleanup
      exit 1
    fi
    UPKERNEL=1
  fi
fi

if [ -n "${LIVEINIT}" -a ! -f "${LIVEINIT}" ]; then
  echo "*** The replacement init script '${LIVEINIT}' is not a file!'"
  cleanup
  exit 1
fi

if [ $CHANGES2SXZ -eq 1 ]; then
  # We need to create a module, so add squashfs to the required tools:
  REQTOOLS="${REQTOOLS} mksquashfs"
fi

# 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}
  echo "-- Exiting."
  cleanup
  exit 1
fi

# We are refreshing the Live content.
# Confirm refresh:
cat <<EOT
#
# We are going to update the Live OS on this device.
# ---------------------------------------------------------------------------
# Target is - '$TARGET':
# Vendor : $(cat /sys/block/$(basename $TARGET)/device/vendor 2>/dev/null)
# Model  : $(cat /sys/block/$(basename $TARGET)/device/model 2>/dev/null)
# Size   : $(( $(cat /sys/block/$(basename $TARGET)/size) / 2048)) MB
# ---------------------------------------------------------------------------
#
# FDISK OUTPUT:
EOT

echo q |gdisk -l $TARGET 2>/dev/null | \
  while read LINE ; do echo "# $LINE" ; done

# If the user just used the scan option (-s) and did not select a task,
# we will exit the script gracefully now:
if [[ $WAIT -lt 0 && $UPKERNEL -ne 1 && $RESTORE -ne 1 && $NETSUPPORT -ne 1 && $LIVEINIT = ""  && $CHANGES2SXZ -ne 1 ]]; then
  cleanup
  exit 0
else
  # We have one or more tasks to execute, allow user to back out:
  cat <<EOT
***                                                       ***
*** If this is the wrong drive, then press CONTROL-C now! ***
***                                                       ***

EOT
  read -p "Or press ENTER to continue: " JUNK
fi

# OK... the user was sure about the drive...
# Determine the three partition names independently of storage architecture:
TARGETP1=$(fdisk -l $TARGET |grep ^$TARGET |cut -d' ' -f1 |grep -E '[^0-9]1$')
TARGETP2=$(fdisk -l $TARGET |grep ^$TARGET |cut -d' ' -f1 |grep -E '[^0-9]2$')
TARGETP3=$(fdisk -l $TARGET |grep ^$TARGET |cut -d' ' -f1 |grep -E '[^0-9]3$')

# Create a temporary extraction directory for the initrd:
mkdir -p /mnt
IMGDIR=$(mktemp -d -p /mnt -t alienimg.XXXXXX)
if [ ! -d $IMGDIR ]; then
  echo "*** Failed to create temporary extraction directory for the initrd!"
  cleanup
  exit 1
fi
chmod 711 $IMGDIR

# Create temporary mount point for the USB device:
mkdir -p /mnt
# USB mounts:
USBMNT=$(mktemp -d -p /mnt -t alienusb.XXXXXX)
if [ ! -d $USBMNT ]; then
  echo "*** Failed to create a temporary mount point for the USB device!"
  cleanup
  exit 1
else
  chmod 711 $USBMNT
fi
EFIMNT=$(mktemp -d -p /mnt -t alienefi.XXXXXX)
if [ ! -d $EFIMNT ]; then
  echo "*** Failed to create a temporary mount point for the USB device!"
  cleanup
  exit 1
else
  chmod 711 $EFIMNT
fi

# Mount the Linux partition:
mount -t auto ${TARGETP3} ${USBMNT}

# Mount the EFI partition:
mount -t vfat -o shortname=mixed ${TARGETP2} ${EFIMNT}

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

# Determine size of the EFI partition (in MB), and the free space:
EFIPSIZE=$(get_part_mb_size ${EFIMNT})
EFIPFREE=$(get_part_mb_free ${EFIMNT})

# Record the Slackware Live version:
OLDVERSION="$(cat ${USBMNT}/.isoversion 2>/dev/null)"
echo "-- The medium '${TARGET}' contains '${OLDVERSION}'"

# Find out if the USB contains an EFI bootloader and use it:
if [ ! -f ${EFIMNT}/EFI/BOOT/boot*.efi ]; then
  EFIBOOT=0
  echo "-- Note: UEFI boot file 'bootx64.efi' or 'bootia32.efi' not found on ISO."
  echo "-- UEFI boot will not be supported"
else
  EFIBOOT=1
fi

# Record the size of the running kernel:
if  [ -f "${USBMNT}/boot/vmlinuz*" ]; then
  KIMG="$(find ${USBMNT}/boot/ -type f -name \"vmlinuz*\" |grep -v prev)"
else
  # Default liveslak kernelname:
  KIMG="${USBMNT}/boot/generic"
fi
OLDKERNELSIZE=$(du -sm "${KIMG}" |tr '\t' ' ' |cut -d' ' -f1)

# Collect data from the USB initrd:
extract_initrd ${USBMNT}/boot/initrd.img
read_initrd ${IMGDIR}

# The read_initrd routine will set SUPPORTED to '0'
# if it finds a non-standard configuration for kernel & modules:
if [ $KBACKUP -eq 1 ]; then
  if [ $SUPPORTED -ne 1 ]; then
    echo "*** ${TARGET} has an unsupported kernel configuration."
    echo "*** Exiting now."
    cleanup
    exit 1
  else
    # If free space is low, require '-b' to skip make a backup (unsafe).
    if [ $(( $USBPFREE - $OLDKMODDIRSIZE - $OLDKERNELSIZE )) -lt $MINFREE ]; then
      KBACKUP=-1
    fi
    if [ $EFIBOOT -eq 1 -a $(( $EFIPFREE - $OLDKMODDIRSIZE - $OLDKERNELSIZE )) -lt $MINFREE ]; then
      KBACKUP=-1
    fi
    if [ $KBACKUP -eq -1  ]; then
      echo "*** Not enough free space for a backup of old kernel and modules."
      echo "*** If you want to update your kerel anyway (without backup) then"
      echo "*** you have to add the parameter '-b' to the commandline."
      cleanup
      exit 1
    fi
  fi
fi

# Update the initrd with regard to USB wait time, liveinit, kernel:
update_initrd ${USBMNT}/boot/initrd.img

# Take care of the kernel in the Linux partition:
if [ $UPKERNEL -eq 1 ]; then
  if [ $KBACKUP -eq 1 ]; then
    # We always make one backup with the suffix ".prev":
    if [ $VERBOSE -eq 1 ]; then
      echo "-- Backing up ${KIMG} to ${USBMNT}/boot/$(basename \"${KIMG}\").prev"
    fi
    mv "${KIMG}" ${USBMNT}/boot/$(basename "${KIMG}").prev
  else
    rm -rf "${KIMG}"
  fi
  # And we name our new kernel exactly as the old one:
  if [ $VERBOSE -eq 1 ]; then
    echo "-- Copying \"${KERNEL}\" to ${USBMNT}/boot/$(basename \"${KIMG}\")"
  fi
  cp "${KERNEL}" ${USBMNT}/boot/$(basename "${KIMG}")
elif [ $RESTORE -eq 1 -a $KRESTORE -eq 1 ]; then
  if [ $VERBOSE -eq 1 ]; then
    echo "-- Restoring ${USBMNT}/boot/$(basename \"${KIMG}\").prev to ${KIMG}"
  fi
  rm -f "${KIMG}"
  mv ${USBMNT}/boot/$(basename "${KIMG}").prev "${KIMG}"
fi

if [ $EFIBOOT -eq 1 ]; then
  # Refresh the kernel/initrd on the EFI partition:
  if [ $VERBOSE -eq 1 ]; then
    rsync -rlptD  --delete -v ${USBMNT}/boot/* ${EFIMNT}/boot/
  else
    rsync -rlptD  --delete ${USBMNT}/boot/* ${EFIMNT}/boot/
  fi
  sync
fi

if [ $CHANGES2SXZ -eq 1 ]; then
  if [ ! -d /mnt/live/changes ]; then
    echo "*** No directory '/mnt/live/changes' exists!"
    echo "*** This script must be executed when running ${DISTRO^} Live Edition"
  else
    # We need to be able to write to the partition:
    mount -o remount,rw ${USBMNT}
    # Tell init to wipe the original persistence data at next boot:
    touch /mnt/live/changes/.wipe 2>/dev/null || true
    if [ ! -f /mnt/live/changes/.wipe ]; then
      echo "*** Unable to create file '/mnt/live/changes/.wipe'!"
      echo "*** Are you sure you are running ${DISTRO^} Live Edition?"
    else
      # Squash the persistence data into a Live .sxz module:
      LIVE_MOD_SYS=$(dirname $(find ${USBMNT} -name "0099-${DISTRO}_zzzconf*.sxz" |head -1))
      LIVE_MOD_ADD=$(dirname ${LIVE_MOD_SYS})/addons
      MODNAME="0100-${DISTRO}_customchanges-$(date +%Y%m%d%H%M%S).sxz"
      echo "-- Moving current persistence data into addons module '${MODNAME}'"
      mksquashfs /mnt/live/changes ${LIVE_MOD_ADD}/${MODNAME} -noappend -comp xz -b 1M -e .wipe
    fi
  fi
fi

# Unmount/remove stuff:
cleanup

# THE END