#!/bin/bash
####################################################################################
# File.......: /usr/lib/setup/armedslack-SeTpartitions
# Called from: (Sourced rather than forked) /usr/lib/setup/SeTpartitions
# Purpose....: Detect a Slackware ARM/AArch64 bootable SD card and append it to
#              the /etc/fstab of the new installation.
#              Mount this SD card under /mnt/boot (/mnt being the location that
#              the Slackware installer mounts the new OS's root filesystem).
#              Updates the U-Boot configuration.
#
#              This is to support the boot process where we ship SD card images
#              that provide a bootable U-Boot header and an ext4 file system containing
#              the U-Boot configuration, Linux Kernel and Slackware installer and OS
#              initial RAM disks ('initrd').
# Version....: 1.01
# Date.......: 02-Jan-2022
# Author.....: Stuart Winter <mozes@slackware.com>
###################################################################################
# Change log
# v1.01, 02-Jan-2022
# * Added file system labeling as the default.
# v1.00, 08-Apr-2021
# * Initial version
###################################################################################
#
# Copyright 2021, 2022  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.

# Config files:
ARMEDSLACK_EXTLINUXCONF=extlinux/extlinux.conf
# Experimentation with INCLUDE
#ARMEDSLACKOS_EXTLINUXCONF=extlinux/slkos

# Copy the 'ROOT_DEVICE' variable into one that we can manipulate within this
# script.  This is because this script is sourced from SeTpartitions, and we
# don't want to overwrite the original values if we switch to use file system
# labeling.
ARMEDSLACK_ROOT_DEVICE=${ROOT_DEVICE}

###################################################################################
################### Functions #####################################################
###################################################################################

# Ensure that the device has a single partition, which we will assume means the user
# wrote the image directly to the SD card and made no modifications.
# Takes full device name as input - e.g. /dev/mmcblk1p1
#
#function sanitycheckbootpart() {
#   local dev=${1%%p*} # lop off the partition number
#   local devbase=${dev#/dev/} # lop off /dev/
#   # If 1 partition found, we're good.
#   [ $( grep -c "${devbase}p[0-9]" /proc/partitions 2> /dev/null ) -eq 1 ] && return 0
#   # Else we're not:
#   return 1
#}

# Ensure that the device has no partitions after the one that will house /boot
# Takes full device name as input - e.g. /dev/mmcblk1p1
function sanitycheckbootpart() {
   local devbase=${1%%p*} # lop off the partition number to find the base name of the block device
   # If the last partition on the block device is the same as the one we're supplied with,
   # it indicates that we potentially have room to resize.
   # However, the last partition isn't the one we're looking for, we cannot offer to
   # resize since it'd overlap!
   [ $( fdisk -l ${devbase} | egrep '^/dev/' | tail -n1 | awk '{print $1}' ) = "${1}" ] && return 0
   # There's another partition subsequent to Slackware OS'
   # /boot - resizing isn't possible:
   return 1
}

function offerresizebootsuccess() {
   dialog \
      --backtitle "/boot partition management" \
      --title "RESIZE /boot PARTITION ON SD CARD" --ok-button "OK" \
      --msgbox "\n\nResizing was successful.\n" 9 60
   clear
}

function offerresizebootfail() {
   dialog \
      --backtitle "/boot partition management" \
      --title "RESIZE /boot PARTITION ON SD CARD" --ok-button "OK" \
      --msgbox '\nResizing failed!\n\n
The installation of Slackware cannot continue.
\n\nYou must investigate manually.\n
\nOne option may be to re-write the Installer image to the SD card again and
reboot the installer.\n' 14 79
   clear
}

# Offer the user the option of resizing /boot:
function offerresizebootpart() {
   dialog \
      --backtitle "/boot partition management" \
      --title "RESIZE /boot PARTITION ON SD CARD" --yesno \
"\nBy default Slackware AArch64 supplies the Slackware Installer image that presently
occupies a 1GB partition on the SD card within this machine.\n\n
This SD card will be transformed into the Operating System's /boot partition
as part of the installation process.\n
\n
It is recommended that this SD card is used for nothing more than /boot, however this
is not mandatory, and some users may prefer to create additional partitions subsequently (post installation).
\n
\nDo you want to resize the /boot partition to fill all available space? (recommendation is 'Yes')\n \

\n" 17 77
  return $?
}

# Resize /boot:
# Take full device name including partition number as input.
# e.g. /dev/mmcblk1p1
function resizebootpart(){
   local dev=${1%%p*} # lop off the partition number
   local devpart=${1}

   # Cause the Kernel to become aware of the new size.
   # Unnecessary but I'll keep it in case:
   # echo 1 > /sys/block/mmcblk1/device/rescan
   # [ "${mmcdev:(-2)}" = "p1" ] && echo y

   # Grow the partition to fill all available space:
   echo "EXISTING SIZE: $( /sbin/fdisk -l ${devpart} | grep "Disk ${devpart}" )"
   echo "OPERATION: Growing partition ${devpart}"
   /sbin/e2fsck -yf ${devpart}
   /usr/sbin/parted -s -a opt ${dev} "resizepart ${devpart#*p} 100%"
#   /usr/sbin/parted -s -a opt ${dev} "resizepart 1 100%"
   estat=$?
   if [ $estat -gt 0 ]; then
      read -p "An error occurred.  Press ENTER to continue"
      return $estat
   fi

   # Resize the ext4 filesystem for /boot on partition 1:
   echo "OPERATION: Resizing ${devpart}"
   /sbin/e2fsck -yf ${devpart}
   /sbin/resize2fs ${devpart}
   estat=$?
   if [ $estat -gt 0 ]; then
      read -p "An error occurred.  Press ENTER to continue"
      return $estat
   fi
   echo "NEW SIZE: $( /sbin/fdisk -l ${devpart} | grep "Disk ${devpart}" )"

   # Wait a moment to see the output:
   sleep 6
   return 0
}

# umount everything that has been configured by /usr/lib/setup/SeTpartitions
# If we merge this upstream, we can dispense with this.
# We cannot umount just $T_PX because the operator may have chosen to mount
# other file systems, which which are now mounted under $T_PX (/mnt within the
# Slackware Installer).
# This prevents us from umounting to conduct labeling operations.
function umount_all_tpx() {
  local blkdev
  # Reverse the order here, since we cannot umount /mnt first as any other
  # mount points are mounted under it.
  # Since the lines in SeTnative are added in the order in which they are mounted,
  # we'll assume we can umount in the reverse order.
  # Not the best assumption, granted, but it'll do until labeling can be upstreamed
  # behind a feature flag.
  egrep '^/' ${TMP}/SeTnative | awk '{print $1}' | tac | while read blkdev ; do
    umount ${blkdev} || { echo "Failed umount ${blkdev}" ; exit 1 ;}
  done
}

# Re-mount everything that has been configured by /usr/lib/setup/SeTpartitions
# These are re-mounted in the same order as they were configured, since the
# mount points build atop one another (with '/mnt' as the base):
function remount_all_tpx() {
  local mntlint
  grep '^/' ${TMP}/SeTnative | awk -v tpx=$T_PX '{print $1,tpx$2,"-t "$3'} | while read mntline ; do
    mount $mntline || { echo "Failed to mount $mntline" ; exit 1 ;}
  done
}

###################################################################################

# In Slackware x86 /etc/fstab and the Boot Loader directly reference the block device.
# This works because the OS partitions typically reside on storage buses that are static
# (e.g. SATA, IDE, SCSI), where as on ARM the primary storage is often on a hot-plug bus
# such as USB.  This lends itself to inconsistent numbering across boots, which may cause
# the machine not to boot.
if [ ! -f /.no-labeling ]; then
   # This label is used within the Boot Loader (U-Boot's extlinux.conf) and the OS' /etc/fstab:
   ARMEDSLACK_ROOT_DEVICE="LABEL=SLKroot"
   ( # Determine the labeling command required for the file system type:
     case ${ROOT_SYS_TYPE} in
        ext2|ext3|ext4) labcmd="e2label ${ROOT_DEVICE} SLKroot" ;;
        reiserfs) labcmd="reiserfstune --label SLKroot ${ROOT_DEVICE}" ;;
        btrfs)labcmd="btrfs filesystem label ${ROOT_DEVICE} SLKroot" ;;
        f2fs) labcmd="f2fs_label ${T_PX} SLKroot"
              # f2fs_label requires that the f2fs file system is mounted:
              labnoumount=yes ;;
        jfs) labcmd="jfs_tune -L SLKroot ${ROOT_DEVICE}" ;;
        xfs) labcmd="xfs_admin -L SLKroot ${ROOT_DEVICE}" ;;
     esac

     # Some file systems cannot label when the FS is mounted.
     # We'll umount by default as the safest choice, and remain mounted
     # by exception:
     #[ -z "${labnoumount}" ] && { umount ${T_PX} || exit 1 ;} # ${T_PX} = OS mount point, /mnt
     [ -z "${labnoumount}" ] && { umount_all_tpx || exit 1 ;}
     # Label the file system:
     eval ${labcmd} || { echo "Failed to label root device" ; exit 1 ; }
     # Re-mount the root FS if we umounted it above:
     #[ -z "${labnoumount}" ] && { mount $ROOT_DEVICE $T_PX -t $ROOT_SYS_TYPE 1> $REDIR 2> $REDIR || exit 1 ;}
     [ -z "${labnoumount}" ] && { remount_all_tpx || exit 1 ;}
     # Switch the OS' /etc/fstab to use a label for the root fs:
     #sed -i 's?^'"${ROOT_DEVICE}"'?'"${ARMEDSLACK_ROOT_DEVICE}"'?g' ${TMP}/SeTnative || exit 1
     # Fix the spacing:
     sed -i 's?^'"${ROOT_DEVICE} "'?'"${ARMEDSLACK_ROOT_DEVICE}"'?g' ${TMP}/SeTnative || { echo "Failed to switch to labeling in ${TMP}/SeTnative" ; exit 1 ;}
     # Ensure that there's a single file system labeled 'SLKroot'
     # This may occur if the user had a file system from a previous installation
     # which they didn't format, and didn't select as their root file system.
     if [ $( lsblk -o label -rin | grep SLKroot -c ) -gt 1 ]; then
        echo "ERROR: More than one file system label found for 'SLKroot'"
        echo "       You must re-label the file system which you did not"
        echo "       select as your root file system."
        exit 1
     fi
   ) || { read -p "ERROR: Failed labeling. This needs to be resolved manually." ; exit 1 ;}
fi

# Scan the MMC block devices for the filesystem label 'SLKboot'
# If there is >1, we'll pick the last.
for mmcdev in /dev/mmcblk* ; do
   { ( /sbin/e2label ${mmcdev} 2>/dev/null | grep -Eq '^SLKboot$' ) && ARMEDSLACK_MMCBOOTPART=${mmcdev} ;}
done

# If a Slackware ARM Installer and OS /boot partition was found, we offer the user the option to
# grow it, then proceed to configure the boot loader.
if [ ! -z "${ARMEDSLACK_MMCBOOTPART}" ]; then

   # By default Slackware AArch64's installer image contains a 1GB single partition.
   # Offer to resize it if seems like the 'stock' image, and handle failures.
   sanitycheckbootpart ${ARMEDSLACK_MMCBOOTPART} && {
      offerresizebootpart && {
         # There's no possibility to retry here.
         clear
         resizebootpart ${ARMEDSLACK_MMCBOOTPART}
         estat=$?
         if [ $estat -gt 0 ]; then
           offerresizebootfail ; exit 1
          else
           offerresizebootsuccess
         fi
       }
   }

cat << EOF >> ${TMP}/SeTnative
# This is the SD card that contains an ext4 file system, housing the
# Linux Kernel and Slackware OS InitRD (Operating System Initial RAM Disk).
$( printf "%-16s %-16s %-11s %-16s %-3s %s\n" LABEL=SLKboot /boot ext4 errors=remount-ro 0 1 )
EOF

   clear
   echo "Detected Slackware bootable SD card: ${ARMEDSLACK_MMCBOOTPART}"
   echo "Attempting to mount ..." # to ${T_PX}/boot"
   # Mount it:
   mkdir -pm755 ${T_PX}/boot
   mount -v ${ARMEDSLACK_MMCBOOTPART} ${T_PX}/boot
   if [ $? -gt 0 ]; then
      # Perhaps turn this into a dialog? not that it helps ;-)
      echo "ERROR: Unable to mount.  This will cause a boot failure."
      echo "       You need to fix this."
      # Abort the installation:
      exit 1
    else
      echo "Configuring U-Boot (${T_PX}/boot/extlinux/extlinux.conf)"
      # If the reached the end of the installer, elected not to erase the
      # installer, and rebooted into the installer to start over, we'll
      # have two copies of the OS boot config.  Let's handle that:
      # There should be two markers, but we'll remove the stanza if there's >0
      if [ $( grep -c '^##SLKOS' ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF} 2>/dev/null ) -gt 0 ]; then
         echo "...removing existing OS boot stanza (probably from a previous failed installation)"
         sed -i '/^##SLKOS/,/^##SLKOS/d' ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF}
      fi
      # Add the Slackware OS boot configuration to extlinux.conf:
      # This was an experiment using the 'INCLUDE' feature, but I found it buggy
      # specifically that it included a single menu entry twice.
      #echo 'INCLUDE slkos' >> ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF}
      # Set the device that contains the root filesystem, and its filesystem type:
      #sed -i 's?%ROOTDEV%?'"${ARMEDSLACK_ROOT_DEVICE}"'?g' ${T_PX}/boot/${ARMEDSLACKOS_EXTLINUXCONF}
      #sed -i 's?%ROOTFSTYPE%?'"${ROOT_SYS_TYPE}"'?g' ${T_PX}/boot/${ARMEDSLACKOS_EXTLINUXCONF}
      # If the user wants to enable the await root device feature, they can
      # set a flag from the shell ( $ touch /.enableawaitrootdev ) and we'll add it:
      if [ -f /.enableawaitrootdev ]; then
         echo "...enabling awaitrootdev OS InitRD feature"
         sed -e 's?APPEND ?APPEND awaitrootdev ?g' ${T_PX}/boot/extlinux/slkos.sample >> ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF}
       else
         # Add the default OS boot stanza:
         cat ${T_PX}/boot/extlinux/slkos.sample >> ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF}
      fi
      # This remains, otherwise if you reboot into the installer again from this
      # SD image, this file won't exist and the machine won't configure U-Boot for
      # booting the OS.
      #rm -f ${T_PX}/boot/extlinux/slkos.sample

      # This assumes a single OS installation:
      echo "...setting root filesystem"
      sed -i 's?%ROOTDEV%?'"${ARMEDSLACK_ROOT_DEVICE}"'?g' ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF}
      sed -i 's?%ROOTFSTYPE%?'"${ROOT_SYS_TYPE}"'?g' ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF}
      # We ship the SD cards with the default boot option as
      # the installer.  Let's switch it to the OS:
      echo "...setting the default boot option to Slackware OS"
      sed -i 's?^DEFAULT Installer?DEFAULT OS?g' ${T_PX}/boot/${ARMEDSLACK_EXTLINUXCONF}

      # As we're in this code path, it means we're on a Slackware AArch64 standard
      # installation, and we know that the installer is within /boot.
      # As the installer has a large footprint and is of limited utility once
      # the installation is complete, we'll suggest to the user to remove it from /boot.
      # We're going to make use of the post-installation package configuration system
      # (that you see when configuring networking/gpm, etc.).
      # Ordinarily these scripts remain on the OS within /var/lib/pkgtools/setup but
      # since this shouldn't be run outside of the installer, we clean it up from
      # within /usr/lib/setup/SeTconfig
      mkdir -pm755 ${T_PX}/var/lib/pkgtools/setup
      ln -fs /usr/lib/setup/armedslack-removeinstaller ${T_PX}/var/lib/pkgtools/setup/setup.armedslack-tmp-removeinstaller
      ln -fs /usr/lib/setup/armedslack-bootloader-flash ${T_PX}/var/lib/pkgtools/setup/setup.armedslack-tmp-bootloader-flash
      ln -fs /usr/lib/setup/armedslack-setconsole ${T_PX}/var/lib/pkgtools/setup/setup.armedslack-tmp-armedslack-setconsole
      ln -fs /usr/lib/setup/armedslack-hwm-os-configure ${T_PX}/var/lib/pkgtools/setup/setup.armedslack-tmp-hwm-os-configure
      # If there are any ARMedslack development scripts to configure the OS
      # as a build machine, add them so that they're run upon completion of the
      # installation.
      [ -d /armedslack-dev-installer-post.src ] && \
         cp -fa /armedslack-dev-installer-post.src/setup.armedslack-tmp* \
         ${T_PX}/var/lib/pkgtools/setup/
   fi
fi

# Search for 'SLKhwm_bw' partition.
# This contains the Hardware Model's initial boot loader and other
# boot assets such as Firmware.
# Hardware Models such as the RK3399 have the Boot Loader within
# flash, so don't have a 'SLKhwm_bw' partition.
# An example that does is the Raspberry Pi4.
#
for mmcdev in /dev/mmcblk* ; do
   { ( e2label ${mmcdev} 2>/dev/null | tail -n1 | rev \
       | awk '{print $1}' | rev | tr -d "'" | \
       grep -Eq '^SLKhwm_bw$' ) && ARMEDSLACK_HWMBW_PART=${mmcdev} ;}
done

# If we found the 'SLKhwm_bw' partition, add it to the OS' /etc/fstab
# and mount it within the Installer.
#
# This isn't required to be mounted at install time,
# but it's handy for the user if they perform any post OS installation
# tasks from within the Installer prior to booting the OS.
#
[ ! -z "${ARMEDSLACK_HWMBW_PART}" ] && {
   # This will be mounted within the OS as /boot/platform/hwm_bw
   # Add it to the OS' /etc/fstab:
cat << EOF >> ${TMP}/SeTnative
# Hardware Model's initial Boot Loader assets:
$(  printf "%-16s %-16s %-6s %-16s %-3s %s\n" LABEL=SLKhwm_bw /boot/platform/hwm_bw vfat errors=remount-ro 0 1 )
EOF

   echo "Detected Hardware Model Bootware partition: ${ARMEDSLACK_HWMBW_PART}"
   echo "Attempting to mount ..."
   # Mount it:
   # If it fails to mount, it's not an immediate issue (although it'll cause
   # errors when the OS boots), so we'll not stall progress.
   mkdir -pm755 ${T_PX}/boot/platform/hwm_bw
   mount -v ${ARMEDSLACK_HWMBW_PART} ${T_PX}/boot/platform/hwm_bw ;}

# Wait a few seconds, otherwise the user sees some text flash on the screen
# momentarily which may cause concern, as often that's what happens to
# error messages.
sleep 4
clear
