#!/bin/bash

# slackupdr_lib / slackupdr functions
#
# Copyright 2019-2022 Stu Miller, Colorado, USA
# 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.
#
# shellcheck disable=SC1090,SC2154
#
# main routine
main () {

  # local variables & arrays
  declare version="3.6.3"
  declare copyright="Copyright 2019-2022 Stu Miller, Colorado, USA"
  declare file dir_lib dir_cache dir_etc="/etc/slackpkg" dir_log="/var/log"
  declare debug=${debug:-1} debug_log="${script_name}_debug.log"

  bash_strict_mode # setup environment
  trap 'bash_trap ERR $?' EXIT # set trap

  # test debug
  if [ "$debug" -eq 0 ]; then
    bash_debug 0 "$dir_log" "$debug_log" # enable debug
    bash_debug 2 # start this function
    bash_debug 4 # start xtrace
  fi # END debug

  file="$dir_etc/$script_name.conf"
  # test conf
  if [ -e "$file" ]; then
    . "$file" # source slackupdr.conf
  else # FAIL conf
    bash_print "$file does not exist\nTerminating script!\n"
    exit
  fi # END conf

  ctrl_getopts "$@" # assign rtnval
  # test no opts
  if [ "$rtnval" -eq 1 ]; then
    file="$dir_etc/slackpkg.conf"
    # test conf
    if [ -e "$file" ]; then
      . "$file" # source slackpkg.conf
    else # FAIL conf
      bash_print "$file does not exist\nTerminating script!\n"
      exit
    fi # END conf
    dir_lib="$WORKDIR"
    dir_cache="$TEMP"

    ctrl_menu_main # start menus
    view_main_exit # main exit message
  fi # END no opts

  # test debug - end xtrace & end this function
  if [ "$debug" -eq 0 ]; then
    bash_debug 5
    bash_debug 3
  fi # END debug
  trap '' EXIT # reset trap

} # END main

# autoslackpkg
ctrl_asp_autorun () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_asp_slackpkg update # slackpkg update
  mdl_asp_slackpkg install # slackpkg install-new
  ctrl_asp_upgrade # slackpkg upgrade-all
  ctrl_asp_clean # slackpkg clean-system
  ctrl_asp_conf # slackpkg new-config

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_asp_autorun

# slackpkg clean-system, exclude kernel pkgs
ctrl_asp_clean () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test blacklist
  if [ -e "$BLKLST" ]; then
    mdl_asp_blklst bkup # backup blacklist
  else # FAIL blacklist
    mdl_dialog_msgbox sm2 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END blacklist

  # test blacklist bkup
  if [ -e "$BLKLST.$script_name" ]; then
    mdl_asp_blklst modify # modify blacklist
  else # FAIL blackist bkup
    mdl_dialog_msgbox sm3 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END blacklist bkup

  mdl_asp_slackpkg clean

  # test blacklist bkup
  if [ -e "$BLKLST.$script_name" ]; then
    mdl_asp_blklst rstr # restore blacklist
  else # FAIL blacklist bkup
    mdl_dialog_msgbox sm3 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END blacklist bkup

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_asp_clean

# slackpkg new-config
ctrl_asp_conf () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_asp_slackpkg conf
  bash_any_key

  mdl_asp_conf # assign conf

  # assign msgbox, pass array elements
  mdl_dialog_msgbox cac1 "$(bash_print "${conf[@]}")"
  view_dialog_msgbox std "${msgbox[@]}"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_asp_conf

# slackpkg upgrade-all, exclude kernel pkgs
ctrl_asp_upgrade () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test blacklist
  if [ -e "$BLKLST" ]; then
    mdl_asp_blklst bkup # backup blacklist
  else # FAIL blacklist
    mdl_dialog_msgbox sm2 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END blacklist

  # test blacklist bkup
  if [ -e "$BLKLST.$script_name" ]; then
    mdl_asp_blklst modify # modify blacklist
  else # FAIL blackist bkup
    mdl_dialog_msgbox sm3 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END blacklist bkup

  mdl_asp_slackpkg upgrade

  # test blacklist bkup
  if [ -e "$BLKLST.$script_name" ]; then
    mdl_asp_blklst rstr # restore blacklist
  else # FAIL blacklist bkup
    mdl_dialog_msgbox sm3 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END blacklist bkup

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_asp_upgrade

# modify & save ELILO_CONF
ctrl_elilo_conf () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # global variables & arrays
  declare -g default_krnl

  mdl_efi_files # assign efi_files, efi_krnl & efi_initrd
  mdl_elilo_default_list # assign krnl & list

  mdl_dialog_radiolist cec1 # assign radiolist
  # assign select
  view_dialog_radiolist notags "${radiolist[@]}" "$count" list

  default_krnl="${krnl[$select]}"
  mdl_elilo_conf_build # assign elilo_conf

  mdl_dialog_yesno cec1 # assign yesno
  view_dialog_yesno nc "${yesno[@]}" # assign confirm

  # test confirm
  [ "$confirm" -eq 1 ] && return
  mdl_elilo_conf_save

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_elilo_conf

# copy kernel files to EFI
ctrl_elilo_copy () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file

  mdl_boot_files # assign boot_files, boot_krnl & boot_initrd
  # test boot_files
  [ "${#boot_files[@]}" -eq 0 ] && return

  mdl_count_list list boot_files # assign count & list
  mdl_dialog_checklist cec1 # assign checklist
  view_dialog_checklist "${checklist[@]}" "$count" list # assign select_arr

  # test select_arr
  [ "${#select_arr[@]}" -eq 0 ] && return

  mdl_boot_files_copy
  # assign msgbox, pass array elements
  mdl_dialog_msgbox cec1 "$(bash_print "${select_arr[@]}")"
  view_dialog_msgbox std "${msgbox[@]}"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_elilo_copy

# mount/unmount EFI, requires m or u argument
ctrl_elilo_mount () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 1 ] && return 1

  case "$1" in
    m) # mount ESP
      mdl_esp_mount m # assign msg
      # test mount
      if grep -qs "$EFI_LOC" /proc/mounts; then
        mdl_esp_mount mna # assign msg
        mdl_dialog_msgbox cem2 "$msg" # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}" # no action
      else # FAIL mount
        mdl_dialog_msgbox cem1 "$msg" # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}" # action taken
      fi # END mount
      return
      ;;
    u) # umount ESP
      mdl_esp_mount u # assign msg
      # test umount
      if grep -qs "$EFI_LOC" /proc/mounts; then
        mdl_dialog_msgbox cem1 "$msg" # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}" # action taken
      else # FAIL umount
        mdl_dialog_msgbox cem2 "$msg" # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}" # no action
      fi # END umount
      return
      ;;
    *) # error exit
      exit
      ;;
  esac

  mdl_dialog_msgbox cem3 # assign msgbox
  view_dialog_msgbox std "${msgbox[@]}" # invalid argument

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_elilo_mount

# remove kernel files from EFI
ctrl_elilo_remove () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file

  mdl_efi_files # assign efi_files, efi_krnl & efi_initrd
  # test efi_krnl
  [ "${#efi_krnl[@]}" -eq 0 ] && return

  mdl_count_list list efi_files # assign count & list
  mdl_dialog_checklist cer1 # assign checklist
  view_dialog_checklist "${checklist[@]}" "$count" list # assign select_arr

  # test select_arr
  [ "${#select_arr[@]}" -eq 0 ] && return

  mdl_efi_files_remove

  # assign msgbox, pass array elements
  mdl_dialog_msgbox cer1 "$(bash_print "${select_arr[@]}")"
  view_dialog_msgbox std "${msgbox[@]}"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_elilo_remove

# get bash options & startup help|section|version
ctrl_getopts () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare OPT OPTIND OPTARG OPTERR
  # global variables & array
  declare -g rtnval=1 # set false, no args

  # 1st char colon - silent error rpt
  # opt char followed by colon - req arg
  while getopts ":hm:v" OPT; do # loop parameters
    case "$OPT" in
      h)
        mdl_dialog_msgbox cg1 "$script_name help" # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
        ;;
      m)
        case "$OPTARG" in
          script)
            mdl_dialog_msgbox cg2 # assign msgbox
            view_dialog_msgbox std "${msgbox[@]}"
            ;;
          conf)
            mdl_dialog_msgbox cg3 # assign msgbox
            view_dialog_msgbox std "${msgbox[@]}"
            ;;
          *) # invoked when opt char w/invalid arg
        # assign msgbox
        mdl_dialog_msgbox cg1 "Valid option -$OPT / Invalid argument $OPTARG"
        view_dialog_msgbox std "${msgbox[@]}"
            ;;
        esac
        ;;
      v)
        mdl_dialog_msgbox cg4 # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
        ;;
      \?) # invoked when opt char missing or invalid
        [ "$OPTIND" -eq 1 ] && break # break loop for normal operation
        if [ "$OPTIND" -eq 2 ]; then
          mdl_dialog_msgbox cg1 "Invalid option -$OPTARG" # assign msgbox
          view_dialog_msgbox std "${msgbox[@]}"
        fi
        ;;
      :) # invoked when opt char w/: is w/o arg
        mdl_dialog_msgbox cg1 "Missing argument for -$OPTARG" # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
        ;;
      *) # error exit
        exit
        ;;
    esac
    clear # clear msgbox
    rtnval=0 # set true, yes args
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_getopts

# build GRUB config
ctrl_grub_conf () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_yesno cgc1 # assign yesno
  view_dialog_yesno std "${yesno[@]}" # assign confirm

  # test confirm
  [ "$confirm" -eq 1 ] && return
  clear # clear confirm
  mdl_grub_conf
  bash_any_key

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_grub_conf

# control GRUB list
ctrl_grub_list () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_grub_list # assign grub_default & grub_menu

  mdl_dialog_msgbox cgl1 "$grub_menu" "$grub_default" # assign msgbox
  view_dialog_msgbox cgl1 "${msgbox[@]}"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_grub_list

# find kernels
ctrl_krnl_find () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare inst avail miss

  mdl_krnl_files # assign pkg_repo, pkg_inst, pkg_avail & pkg_miss
  mdl_krnl_find # assign avail_pkg miss_pkg

  inst="$(bash_print "${pkg_inst[@]}")"
  avail="$(bash_print "${avail_pkg[@]}")"
  miss="$(bash_print "${miss_pkg[@]}")"
  # assign msgbox
  mdl_dialog_msgbox ckf1 "$inst" "$avail" "$miss"

  view_dialog_msgbox std "${msgbox[@]}"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_krnl_find

# build initrd(s)
ctrl_krnl_initrd_build () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test mkird_conf
  if [ ! -e "$MKIRD_CONF" ]; then
    mdl_dialog_msgbox ckib1 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END mkird_conf

  mdl_krnl_files # assign pkg_repo, pkg_inst, pkg_avail & pkg_miss
  mdl_krnl_modules # assign modules

  mdl_count_list list modules # assign count & list
  mdl_dialog_checklist ckib1 # assign checklist
  view_dialog_checklist "${checklist[@]}" "$count" list # assign select_arr

  # test select_arr
  [ "${#select_arr[@]}" -eq 0 ] && return

  clear # clear checklist
  view_mkird_build select_arr
  mdl_mkird_build
  bash_any_key

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_krnl_initrd_build

# remove /boot/initrd(s)
ctrl_krnl_initrd_remove () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file

  mdl_boot_files # assign boot_files, boot_krnl & boot_initrd
  # test boot_initrd
  [ "${#boot_initrd[@]}" -eq 0 ] && return

  mdl_count_list list boot_initrd # assign count & list
  mdl_dialog_checklist ckir1 # assign checklist
  view_dialog_checklist "${checklist[@]}" "$count" list # assign select_arr

  # test select_arr
  [ "${#select_arr[@]}" -eq 0 ] && return

  mdl_boot_initrd_remove
  # assign msgbox, pass array elements
  mdl_dialog_msgbox ckir1 "$(bash_print "${select_arr[@]}")"
  view_dialog_msgbox std "${msgbox[@]}"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_krnl_initrd_remove

# install kernel(s)
ctrl_krnl_install () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file

  mdl_krnl_files # assign pkg_repo, pkg_inst, pkg_avail & pkg_miss

  mdl_count_list list pkg_avail # assign count & list
  mdl_dialog_checklist cki1 # assign checklist
  view_dialog_checklist "${checklist[@]}" "$count" list # assign select_arr

  # test select_arr
  [ "${#select_arr[@]}" -eq 0 ] && return

  clear # clear checklist
  mdl_krnl_dnld # assign md5

  # test md5
  if [ "${#md5[@]}" -ne 0 ]; then
    # assign msgbox, pass array elements
    mdl_dialog_msgbox cki1 "$(bash_print "${md5[@]}")"
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END md5

  mdl_krnl_install # assign cache_txz
  [ "${#cache_txz[@]}" -ne 0 ] && bash_any_key

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_krnl_install

# remove kernel(s)
ctrl_krnl_remove () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file

  mdl_krnl_files # assign pkg_repo, pkg_inst, pkg_avail & pkg_miss

  mdl_count_list list pkg_inst # assign count & list
  mdl_dialog_checklist ckr1 # assign checklist
  view_dialog_checklist "${checklist[@]}" "$count" list # assign select_arr

# test select_arr
  [ "${#select_arr[@]}" -eq 0 ] && return

  clear # clear checklist
  mdl_krnl_remove
  bash_any_key

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_krnl_remove

# modify & save LILO_CONF
ctrl_lilo_conf () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # global variables & arrays
  declare -g default_krnl

  mdl_boot_files # assign boot_files, boot_krnl & boot_initrd
  mdl_lilo_default_list # assign krnl & list

  mdl_dialog_radiolist clc1 # assign radiolist
  # assign select
  view_dialog_radiolist notags "${radiolist[@]}" "$count" list

  default_krnl="${krnl[$select]}"
  mdl_lilo_conf_build # assign lilo_conf

  mdl_dialog_yesno clc1 # assign yesno
  view_dialog_yesno nc "${yesno[@]}" # assign confirm

  # test confirm
  [ "$confirm" -eq 1 ] && return
  mdl_lilo_conf_save

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_lilo_conf

# LILO run
ctrl_lilo_run () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_yesno clr1 # assign yesno
  view_dialog_yesno std "${yesno[@]}" # assign confirm
  # test confirm
  [ "$confirm" -eq 1 ] && return

  clear # clear confirm
  lilo -v
  bash_any_key

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_lilo_run

# control autoslackpkg menu
ctrl_menu_asp () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_menu asp # assign menu_opts & menu_items
  mdl_count_list menu menu_items # assign count & list
  view_dialog_menu "${menu_opts[@]}" list # assign menu & level
  # test exit button
  [ "$level" -eq 1 ] && return

  clear # remove menu
  case "$menu" in
    1) # autoslackpkg autorun 2-6
      ctrl_asp_autorun
      ;;
    2) # slackpkg update
      mdl_asp_slackpkg update
      bash_any_key
      ;;
    3) # slackpkg install-new
      mdl_asp_slackpkg install
      bash_any_key
      ;;
    4) # slackpkg upgrade-all
      ctrl_asp_upgrade
      bash_any_key
      ;;
    5) # slackpkg clean-system
      ctrl_asp_clean
      bash_any_key
      ;;
    6) # slackpkg new-config
      ctrl_asp_conf
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_asp

# control bootloader menu
ctrl_menu_btldr () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare key

  for key in bash_container bash_chroot; do
    "$key" # assign rtnval
    # test rtnval
    if [ "$rtnval" -eq 0 ]; then
      mdl_dialog_msgbox sm1 # assign msgbox
      view_dialog_msgbox std "${msgbox[@]}"
      level=1; return
    fi # END rtnval
  done

  mdl_dialog_menu btldr # assign menu_opts & menu_items
  mdl_count_list menu menu_items # assign count & list
  view_dialog_menu "${menu_opts[@]}" list # assign menu & level
  # test exit button
  [ "$level" -eq 1 ] && return # up

  mdl_btldr_type # assign btldr
  case "$menu" in
    1) # GRUB menu
      # test bootloader
      if [ "$btldr" -ne 7 ]; then
        mdl_dialog_msgbox cmb1 # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
      fi
      while [ "$level" -eq 3 ]; do ctrl_menu_grub; done
      ;;
    2) # ELILO menu
      # test bootloader
      if [ "$btldr" -ne 5 ]; then
        mdl_dialog_msgbox cmb1 # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
      fi
      while [ "$level" -eq 3 ]; do ctrl_menu_elilo; done
      ;;
    3) # LILO menu
      # test bootloader
      if [ "$btldr" -ne 0 ]; then
        mdl_dialog_msgbox cmb1 # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
      fi
      while [ "$level" -eq 3 ]; do ctrl_menu_lilo; done
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_btldr

# control ELILO menu
ctrl_menu_elilo () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_menu elilo # assign menu_opts & menu_items
  mdl_count_list menu menu_items # assign count & list
  view_dialog_menu "${menu_opts[@]}" list # assign menu & level
  # test exit button
  [ "$level" -eq 2 ] && return # up

  # test EFI directory
  if [ -d "$EFI_LOC" ]; then
    mdl_efi_dir # assign efi_dir
  else # FAIL EFI directory
    mdl_dialog_msgbox cme1 "$EFI_LOC"
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END EFI directory

  case "$menu" in
    1) # mount EFI
      ctrl_elilo_mount m
      ;;
    2) # copy initrd(s) to EFI
      # test EFI mounted
      if [ -z "$efi_dir" ]; then
        mdl_dialog_msgbox cme2 # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
        return
      fi
      ctrl_elilo_copy
      ;;
    3) # remove initrd(s) from EFI
      # test EFI mounted
      if [ -z "$efi_dir" ]; then
        mdl_dialog_msgbox cme2 # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
        return
      fi
      ctrl_elilo_remove
      ;;
    4) # update & save ELILO config
      # test EFI mounted
      if [ -z "$efi_dir" ]; then
        mdl_dialog_msgbox cme2 # assign msgbox
        view_dialog_msgbox std "${msgbox[@]}"
        return
      fi
      ctrl_elilo_conf
      ;;
    5) # umount EFI
      ctrl_elilo_mount u
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_elilo

# control GRUB menu
ctrl_menu_grub () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_menu grub # assign menu_opts & menu_items
  mdl_count_list menu menu_items # assign count & list
  view_dialog_menu "${menu_opts[@]}" list # assign menu & level
  # test exit button
  [ "$level" -eq 2 ] && return # up

  case "$menu" in
    1) # generate GRUB config
      ctrl_grub_conf
      ;;
    2) # list menuentry & saved_entry
      ctrl_grub_list
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_grub

# control kernel menu
ctrl_menu_krnl () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare key

  for key in bash_container bash_chroot; do
    "$key" # assign rtnval
    # test rtnval
    if [ "$rtnval" -eq 0 ]; then
      mdl_dialog_msgbox sm1 # assign msgbox
      view_dialog_msgbox std "${msgbox[@]}"
      level=1; return
    fi # END rtnval
  done

  mdl_dialog_menu krnl # assign menu_opts & menu_items
  mdl_count_list menu menu_items # assign count & list
  view_dialog_menu "${menu_opts[@]}" list # assign menu & level
  # test exit button
  [ "$level" -eq 1 ] && return # up
  case "$menu" in
    1) # find available, installed & missing kernels
      ctrl_krnl_find
      ;;
    2) # install kernel(s)
      ctrl_krnl_install
      ;;
    3) # build initrd(s)
      ctrl_krnl_initrd_build
      ;;
    4) # remove kernel(s)
      ctrl_krnl_remove
      ;;
    5) # remove /boot/initrd(s)
      ctrl_krnl_initrd_remove
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_krnl

# control LILO menu
ctrl_menu_lilo () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_menu lilo # assign menu_opts & menu_items
  mdl_count_list menu menu_items # assign count & list
  view_dialog_menu "${menu_opts[@]}" list # assign menu & level
  # test exit button
  [ "$level" -eq 2 ] && return # up

  case "$menu" in
    1) # LILO config changes
      ctrl_lilo_conf
      ;;
    2) # LILO run
      ctrl_lilo_run
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_lilo

# control main menu
ctrl_menu_main () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare menu level=1

  bash_user # assign user
  while [ "$level" -eq 1 ]; do # loop main menu
    mdl_dialog_menu main # assign menu_opts & menu_items
    mdl_count_list menu menu_items # assign count & list
    view_dialog_menu "${menu_opts[@]}" list # assign menu & level
    # test exit button
    [ "$level" -eq 0 ] && break # out

    case "$menu" in
      1) # autoslackpkg menu
        while [ "$level" -eq 2 ]; do ctrl_menu_asp; done
        ;;
      2) # kernel menu
        while [ "$level" -eq 2 ]; do ctrl_menu_krnl; done
        ;;
      3) # bootloader menu
        while [ "$level" -eq 2 ]; do ctrl_menu_btldr; done
        ;;
      4) # tools menu
        while [ "$level" -eq 2 ]; do ctrl_menu_tools; done
        ;;
      *) # error exit
        exit
        ;;
    esac
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_main

# control tools menu
ctrl_menu_tools () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_menu tools # assign menu_opts & menu_items
  mdl_count_list menu menu_items # assign count & list
  view_dialog_menu "${menu_opts[@]}" list # assign menu & level
  # test exit button
  [ "$level" -eq 1 ] && return # up

  case "$menu" in
    1) # new & orig conf diff
      ctrl_tools_diff
      ;;
    2) # slackpkg install-new
      ctrl_tools_updb
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_menu_tools

# new & orig conf diff
ctrl_tools_diff () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file select

  mdl_asp_conf # assign conf_new, conf_orig & conf_all
  # test conf_all
  if [ "${#conf_all[@]}" -eq 0 ]; then
    mdl_dialog_msgbox ctd1 # assign msgbox
    view_dialog_msgbox std "${msgbox[@]}"
    return
  fi # END conf_all

  mdl_count_list list conf_all # assign count & list
  mdl_dialog_radiolist ctd1 # assign radiolist
  view_dialog_radiolist std "${radiolist[@]}" "$count" list # assign select

  # test select
  if [ -n "$select" ]; then
    mdl_diff_edit 0 "$select" # args run diff/edit

    mdl_dialog_yesno ctd1 # assign yesno
    view_dialog_yesno std "${yesno[@]}" # assign confirm

    # test confirm
    [ "$confirm" -eq 0 ] && mdl_diff_edit 1 "$select" # args run rm config
  fi # END select

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_tools_diff

# updatedb control
ctrl_tools_updb () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  mdl_dialog_infobox ctu1 # assign infobox
  view_dialog_infobox "${infobox[@]}"
  updatedb
  sleep 3

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END ctrl_tools_updb

# slackpkg blacklist kernel
mdl_asp_blklst () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 1 ] && return 1

  # local variables & arrays
  declare krnl

  case "$1" in
    bkup) # bkup orig blacklist
      # copy blacklist
      cp -fa "$BLKLST" "${BLKLST}.$script_name"
      ;;
    modify) # clean & add kernel pkgs on orig blacklist
      # remove comments & blank lines from orig blacklist
      sed -i -e '/^#/d' -e '/^[[:space:]]*$/d' "$BLKLST"
      # append kernel pkg names to orig blacklist
      krnl="kernel-generic*\nkernel-huge*\nkernel-modules*\nkernel-source*\n"
      bash_print "$krnl" >> "$BLKLST"
      ;;
    rstr) # restore bkup blacklist
      # move bkup to orig
      mv -f "${BLKLST}.$script_name" "$BLKLST"
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_asp_blklst

# find new & orig config files
mdl_asp_conf () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare -a conf_new=() conf_orig=()
  # global variables & arrays
  declare -ga conf=() conf_all=()

  # find .new files
  mapfile -t conf_new < <(find /etc -name "*.new")
  # test conf_new
  if [ "${#conf_new[*]}" -ne 0 ]; then
    mapfile -t -O "${#conf_all[@]}" conf_all < <(bash_print "${conf_new[@]}")
  fi # END conf_new

  # find .orig files
  mapfile -t conf_orig < <(find /etc -name "*.orig")
  # test conf_orig
  if [ "${#conf_orig[*]}" -ne 0 ]; then
    mapfile -t -O "${#conf_all[@]}" conf_all < <(bash_print "${conf_orig[@]}")
  fi # END conf_orig

  # append conf array with .new
  conf=('' '\Zu.new\ZU files found:') # subheader
  # test .new files
  if [ "${#conf_new[*]}" -ne 0 ] ; then
    conf+=("${conf_new[@]}") # body
  else # FAIL .new files
    conf+=('None...') # alt body
  fi # END .new files

  # append conf array with .orig
  conf+=('' '\Zu.orig\ZU files found:') # subheader
  # test .orig files
  if [ "${#conf_orig[*]}" -ne 0 ]; then
    conf+=("${conf_orig[@]}") # body
  else # FAIL .orig files
    conf+=('None...') # alt body
  fi # END .orig files

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_asp_conf

# slackpkg commands
mdl_asp_slackpkg () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 1 ] && return 1

  case "$1" in
    update)
      slackpkg update || : # ignore slackpkg exit codes
      ;;
    install)
      slackpkg install-new || : # ignore slackpkg exit codes
      ;;
    upgrade)
      slackpkg -postinst=off upgrade-all || : # ignore slackpkg exit codes
      ;;
    clean)
      slackpkg -onoff=off clean-system || : # ignore slackpkg exit codes
      ;;
    conf)
      slackpkg new-config || : # ignore slackpkg exit codes
      ;;
    *)
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_asp_slackpkg

# determine /boot/initrd|vmlinuz files
mdl_boot_files () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare regex name
  # global variables & arrays
  declare -ga boot_files=() boot_krnl=() boot_initrd=()

  # find boot files
  # find '-regex' - uses EMACS regex
  regex=".*/vmlinuz-generic-.*\\|.*/vmlinuz-huge-.*\\|.*/initrd.*.gz"
  mapfile -t boot_files < <(find /boot -type f -maxdepth 1 -regex "$regex" |
    sort)
  for name in "${boot_files[@]}"; do # loop boot files
    # test krnl|initrd
    if [ -z "${name##*generic*}" ] || [ -z "${name##*huge*}" ]; then
      # build kernel list
      boot_krnl+=("$name")
    elif [ -z "${name##*initrd*}" ]; then
      # build initrd list
      boot_initrd+=("$name")
    fi # END krnl|initrd
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_boot_files

# copy boot files to EFI
mdl_boot_files_copy () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  bash_print "${select_arr[@]}" | xargs cp -p -t "$efi_dir"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_boot_files_copy

# remove boot initrd files
mdl_boot_initrd_remove () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # rm errors on variable whitespace, use xarg instead
  bash_print "${select_arr[@]}" | xargs rm

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_boot_initrd_remove

# determine LILO, EFI_stub, EFI or GRUB or other
mdl_btldr_type () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # global variables & arrays
  declare -g btldr

  # stdout command substitution $(<...) for arithmetic expansion ((..))
  # with right 4 bitwise shift >>
  # 0=LILO, 2=EFI_stub stub5=EFI, 7=GRUB
  btldr=$(($(</proc/sys/kernel/bootloader_type)>>4))

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_btldr_type

# build count & list / menu, checklist & radiolist
mdl_count_list () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 2 ] && return 1

  # local variables & arrays
  declare item
  declare -a array
  declare -n name_ref=$2
  # global variables & arrays
  declare -g count=0
  declare -ga list=()

  case "$1" in
    menu)
      mapfile -t array < <(bash_print "${name_ref[@]}")
      for item in "${array[@]}"; do # loop argument
        (( count++ )) || : # increment count
        list+=("$count" \""$item"\") # build list
      done
      ;;
    list)
      mapfile -t array < <(bash_print "${name_ref[@]}")
      for item in "${array[@]}"; do # loop argument
        list+=(\""$item"\" \"\" off) # build list
        (( count++ )) || : # increment count
      done
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_count_list

# dialog checklist database
mdl_dialog_checklist () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -lt 1 ] && return 1

  # global variables & arrays
  declare -ga checklist

  case "$1" in
    cec1) # ctrl_elilo_copy 1
      checklist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Copy Boot Files Selection"
        "Select /boot files to copy to EFI directory:")
      ;;
    cer1) # ctrl_elilo_remove 1
      checklist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Remove EFI Files Selection"
        "Select EFI files to remove:")
      ;;
    cki1) # ctrl_krnl_install 1
      checklist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "Download Available Kernel(s)"
        "Select kernel(s) to download:")
      ;;
    ckib1) # ctrl_krnl_initrd_build 1
      checklist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Build New Initrd(s)"
        "NOTE: kernel initrd(s) offered based
on kernel-modules installed

Select kernel-modules to build:")
      ;;
    ckir1) # ctrl_krnl_install 1
      checklist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Remove /boot/initrd File(s)"
        "Select /boot/initrd files to remove:")
      ;;
    ckr1) # ctrl_krnl_remove 1
      checklist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "Remove Installed Kernel(s)"
        "Select kernel(s) to remove:")
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_dialog_checklist

# dialog infobox database
mdl_dialog_infobox () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -lt 1 ] && return 1

  # global variables & arrays
  declare -ga infobox

  case "$1" in
    ctu1) # ctrl_tools_updb 1
      infobox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Action Taken"
        "update locate database

this may take some time...
check disk activity light")
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_dialog_infobox

# dialog menu database
mdl_dialog_menu () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare backtitle="$user - $script_name v$version"
  # global variables & arrays
  declare -ga menu_opts menu_items

  # test args
  [ "$#" -lt 1 ] && return 1

  case "$1" in
    main)
      # assign title, cancel-label, text, level_true & level_false
      menu_opts=("$backtitle" "Main Menu" "Exit")
      menu_opts+=("Please choose an option" 2 0)
      menu_items=("autoslackpkg" "kernel menu" "bootloader menu" "tools menu")
      ;;
    asp)
      menu_opts=("$backtitle" "Autoslackpkg Menu" "Main Menu")
      menu_opts+=("Please choose an option" 2 1)
      menu_items=("autoslackpkg : autorun 2-6")
      menu_items+=("slackpkg update")
      menu_items+=("slackpkg install-new")
      menu_items+=("slackpkg upgrade-all")
      menu_items+=("slackpkg clean-system")
      menu_items+=("slackpkg new-config")
      ;;
    krnl)
      menu_opts=("$backtitle" "Kernel Menu" "Main Menu")
      menu_opts+=("Please choose an option" 2 1)
      menu_items=("find kernels")
      menu_items+=("install kernel(s)")
      menu_items+=("build initrd(s)")
      menu_items+=("remove kernel(s)")
      menu_items+=("remove /boot/initrd(s)")
      ;;
    btldr)
      menu_opts=("$backtitle" "Bootloader Menu" "Main Menu")
      menu_opts+=("Please choose an option" 3 1)
      menu_items=("GRUB menu" "ELILO menu" "LILO menu")
      ;;
    grub)
      menu_opts=("$backtitle" "GRUB Menu" "Btldr Menu")
      menu_opts+=("Please choose an option" 3 2)
      menu_items=("build GRUB config" "GRUB menu entries")
      ;;
    elilo)
      menu_opts=("$backtitle" "ELILO Menu" "Btldr Menu")
      menu_opts+=("Please choose an option" 3 2)
      menu_items=("mount ESP")
      menu_items+=("copy kernel files to ESP")
      menu_items+=("remove kernel files from ESP")
      menu_items+=("modify & save $ELILO_CONF")
      menu_items+=("unmount ESP")
      ;;
    lilo)
      menu_opts=("$backtitle" "LILO Menu" "Btldr Menu")
      menu_opts+=("Please choose an option" 3 2)
      menu_items=("modify & save $LILO_CONF" "run lilo")
      ;;
    tools)
      menu_opts=("$backtitle" "Tools Menu" "Main Menu")
      menu_opts+=("Please choose an option" 2 1)
      menu_items=("diff/edit .new & .orig files" "update locate database")
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_dialog_menu

# dialog msgbox database
mdl_dialog_msgbox () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -lt 1 ] && return 1

  # global variables & arrays
  declare -ga msgbox

  case "$1" in
    cac1) # ctrl_asp_conf 1
      # assign backtitle, title & msg_text
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: slackpkg new-config"
        "After searching /etc for .new & .orig files:
$2")
      ;;
    cec1) # ctrl_elilo_copy 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Files Copied"
        "The following files were copied to the EFI directory:
$2")
      ;;
    cem1) # ctrl_elilo_mount 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Action Taken"
        "$2")
      ;;
    cem2) # ctrl_elilo_mount 2
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: No Action"
        "$2")
      ;;
    cem3) # ctrl_elilo_mount 3
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: No Action"
        "ERROR: Invalid argument: $1")
      ;;
    cer1) # ctrl_elilo_remove 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Files removed"
        "The following files were removed:
$2")
      ;;
    cg1) # ctrl_getopts 1
      msgbox=("Script Usage Help"
        "NOTICE: $script_name - usage help"
        "\Zu$2\Zn

Usage: $script_name [-h|-m|-v] [arg]

[] . . . run $script_name normally
[-h] . . script usage help
[-m] . . text version of man page - requires argument
. . . . . . following args: script | conf
[-v] . . release version")
      ;;
    cg2) # ctrl_getopts 2
      msgbox=("man page - text version"
        "NOTICE: /usr/doc/$script_name-$version/$script_name.8.txt"
        "$(cat "/usr/doc/$script_name-$version/$script_name.8.txt")")
      ;;
    cg3) # ctrl_getopts 3
      msgbox=("man page - text version"
        "NOTICE: /usr/doc/$script_name-$version/$script_name.5.txt"
        "$(cat "/usr/doc/$script_name-$version/$script_name.conf.5.txt")")
      ;;
    cg4) # ctrl_getopts 4
      msgbox=("Script Version"
        "NOTICE: $script_name - v$version"
        "$copyright")
      ;;
    cgl1) # ctrl_grub_list 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: GRUB Menu Entries"
        "\ZuLinux Menu Entries:\ZU

$2
\ZuDefault Boot Entry:\ZU

$3")
      ;;
    ckf1) # ctrl_krnl_find 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Kernel Package Status"
        "Summarize kernel package status:

\ZuInstalled Packages:\ZU

$2

\ZuAvailable Repo Packages:\ZU

$3

\ZuNot Installed Repo Packages:\ZU

$4")
      ;;
    ckib1) # ctrl_krnl_initrd_build 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "$MKIRD_CONF error(s)"
        "For help see man $script_name.conf
or $script_name -m conf")
      ;;
    ckir1) # ctrl_krnl_initrd_remove 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Files Removed"
        "The following files were removed:
$2")
      ;;
    cki1) # ctrl_krnl_install 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Files Failed"
        "Downloaded package(s) FAIL md5sum check:
$2")
      ;;
    cmb1) # ctrl_menu_btldr 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Bootloader mismatch"
        "Selected bootloader not active.")
      ;;
    cme1) # ctrl_menu_elilo 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: No Action"
        "$2 does not exist.
Check EFI_LOC variable in /etc/slackpkg/slackupdr.conf")
      ;;
    cme2) # ctrl_menu_elilo 2
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: No Action"
        "ESP not mounted.")
      ;;
    ctd1) # ctrl_tools_diff 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "     NOTICE: diff/edit Config Files      "
        "No /etc .new or .orig files to diff/edit.")
      ;;
    sm1) # shared message 1
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: No Action"
        "Running in container or chroot.")
      ;;
    sm2) # shared message 2
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: slackpkg blacklist not found"
        "manually recreate /etc/slackpkg/blacklist.")
      ;;
    sm3) # shared message 3
      msgbox=("${menu_opts[0]} - ${menu_opts[1]} Function"
      "WARNING: slackpkg blacklist backup not found"
      "manually recreate /etc/slackpkg/blacklist.")
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_dialog_msgbox

# dialog radiolist database
mdl_dialog_radiolist () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -lt 1 ] && return 1

  # global variables & arrays
  declare -ga radiolist

  case "$1" in
    ctd1) # ctrl_tools_diff 1
      radiolist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Diff/Edit File"
        "Select /etc .new or .orig file to diff/edit:")
      ;;
    cec1) # ctrl_elilo_conf 1
      radiolist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Available $efi_dir Kernels"
        "Select $ELILO_BLP default kernel:")
      ;;
    clc1) # ctrl_lilo_conf 1
      radiolist=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Available /boot Kernels"
        "Select $LILO_CONF default kernel:")
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_dialog_radiolist

# dialog yesno database
mdl_dialog_yesno () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -lt 1 ] && return 1

  # global variables & arrays
  declare -ga yesno

  case "$1" in
    ctd1) # ctrl_tools_diff 1
      yesno=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Remove .new or .orig File Confirmation"
        "Remove file: $select?")
      ;;
    cec1) # ctrl_elilo_conf 1
      yesno=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Save $ELILO_CONF to disk"
        "This is the $ELILO_CONF ready to save:
NOTE: Carets represent double quotes.

$(bash_print "${elilo_conf[@]}")

Save $ELILO_CONF to disk?")
      ;;
    cgc1) # ctrl_grub_conf 1
      yesno=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Build GRUB config"
        "Anytime you edit the /etc/default/grub, /etc/grub.d/* files
or if you install|remove either kernels|initrds used by GRUB,
you must apply your changes before they will take effect.

Failing to do so might cause GRUB not to boot Linux!

Run grub-mkconfig?")
      ;;
    clc1) # ctrl_lilo_conf 1
      yesno=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "WARNING: Save $LILO_CONF to disk"
        "This is the $LILO_CONF ready to save:
NOTE: Carets represent double quotes.

$(bash_print "${lilo_conf[@]}")

Save $LILO_CONF to disk?")
      ;;
    clr1) # ctrl_lilo_run 1
      yesno=("${menu_opts[0]} - ${menu_opts[1]} Function"
        "NOTICE: Run LILO Confirmation"
        "Anytime you edit the $LILO_CONF, or if you install|remove
either kernels|initrds used by LILO, you must apply your
changes before they will take effect.

Failing to do so might cause LILO to be unable to boot Linux!

Run LILO?")
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_dialog_yesno

# new & orig config diff/edit
mdl_diff_edit () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -lt 2 ] && return 1

  # test arg
  if [ "$1" -eq 0 ]; then
    $DIFF "${2%.*}" "$2" # parameter expansion
  elif [ "$1" -eq 1 ]; then
    /usr/bin/rm -f "$2" # remove old file
  fi # END arg

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_diff_edit

# assign EFI directory
mdl_efi_dir () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file
  # global variables & arrays
  declare -g efi_dir

  file=$(find "$EFI_LOC" -name "$ELILO_BLP" | head -1)
  efi_dir=${file%/*} # remove filename

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_efi_dir

# determine EFI initrd|vmlinuz files
mdl_efi_files () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare regex name
  # global variables & arrays
  declare -ga efi_files=() efi_krnl=() efi_initrd=()

  # find EFI files
  regex=".*/vmlinuz-generic-.*\\|.*/vmlinuz-huge-.*\\|.*/initrd.*.gz"
  mapfile -t efi_files < <(find "$efi_dir" -maxdepth 1 -regex "$regex" | sort)
  for name in "${efi_files[@]}"; do # loop efi files
    # test krnl|initrd
    if [ -z "${name##*generic*}" ] || [ -z "${name##*huge*}" ]; then
      efi_krnl+=("$name")
    elif [ -z "${name##*initrd*}" ]; then
      efi_initrd+=("$name")
    fi # END krnl|initrd
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_efi_files

# remove EFI files
mdl_efi_files_remove () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # use xarg, rm errors on variable with spaces
  bash_print "${select_arr[@]}" | xargs rm

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_efi_files_remove

# build elilo.conf based on template
mdl_elilo_conf_build () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare line key lhs label match
  declare krnl krnl_base krnl_type krnl_version
  declare initrd initrd_base initrd_version
  declare -a template
  # global variables & arrays
  declare -ga elilo_conf=()

  # convert multi-line variable from script config to array
  # process substitution <(...) appears as redirected file
  # -t remove trailing newline (1st line of template is null)
  mapfile -t template < <(bash_print "$TEMPLATE_ELILO")
  for line in "${template[@]}"; do # loop template
    # skip line (1st line of template is null)
    [ -z "$line" ] && continue

    key=CLONE
    # test clone lines
    if [ -z "${line##*"$key"*}" ]; then # match CLONE
      elilo_conf+=("${line/\#"$key"/}") # remove #CLONE
    fi # END clone lines

    key=DEFAULT
    lhs="default=" # left hand side
    # test default line
    if [ -z "${line##*"$key"*}" ]; then # match default
      # test default_krnl
      if [ -z "${default_krnl##*generic*}" ]; then # match generic
        elilo_conf+=("$lhs${default_krnl/generic-/Gen}") # replace prefix
      elif [ -z "${default_krnl##*huge*}" ]; then # match huge
        elilo_conf+=("$lhs${default_krnl/huge-/Hug}") # replace prefix
      fi # END default_krnl
    fi # END default line
  done

  for krnl in "${efi_krnl[@]}"; do # loop kernels
    krnl_base="${krnl##*/}" # remove path
    krnl_type="${krnl_base#*-}" # remove 1st field
    krnl_type="${krnl_type%%-*}" # remove all fields except 1st
    krnl_version="${krnl_base##*-}" # remove before version
    # test krnl_version
    if [ "$krnl_version" = "smp" ]; then
      krnl_version="${krnl_base#*-}" # remove 1st field
      krnl_version="${krnl_version#*-}" # remove 2nd field
      krnl_version="${krnl_version#*-}" # remove 3rd field
    fi # END krnl_version

    match="" # reset match
    for initrd in "${efi_initrd[@]}"; do # loop efi initrds
      initrd_base="${initrd##*/}" # remove path
      initrd_version="${initrd_base##*-}" # remove all fields except last
      initrd_version="${initrd_version%.*}" # remove extension
      # test initrd_version
      if [ "$initrd_version" = "smp" ]; then
        initrd_version="${initrd_base#*-}" # remove 1st field
        initrd_version="${initrd_version%.*}" # remove extension
      fi # END initrd_version
      # test initrd_version
      [ "$initrd_version" = "$krnl_version" ] && match="$initrd_base"
    done

    # test krnl_type
    if [ "$krnl_type" = "generic" ];then
      key=GENERIC
      label=Gen
    elif [ "$krnl_type" = "huge" ]; then
      key=HUGE
      label=Hug
    fi # END krnl_type

    for line in "${template[@]}"; do # loop template
      # skip line (1st line of template is null)
      [ -z "$line" ] && continue
      # test key match
      if [ -z "${line##*"$key"*}" ]; then
        # test line match
        if [ -z "${line##*image*}" ]; then # match image
          elilo_conf+=("${line/\#"$key"/"$krnl_base"}") # replace image
        elif [ -z "${line##*label*}" ]; then # match label
          elilo_conf+=("${line/\#"$key"/"$label""$krnl_version"}") # replace label
        elif [ -z "${line##*initrd*}" ] && [ -n "$match" ]; then # match initrd
          elilo_conf+=("${line/\#"$key"/"$match"}") # replace initrd
        elif [ -z "${line##*append*}" ]; then # match append
          elilo_conf+=("${line/\#"$key"/}") # replace append
        elif [ -z "${line##*read-only*}" ]; then # match read-only
          elilo_conf+=("${line/\#"$key"/}") # replace read-only
        fi # END line match
      fi # END key match
    done
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_elilo_conf_build

# save elilo.conf
mdl_elilo_conf_save () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  bash_print "${elilo_conf[@]}" > "$efi_dir/$ELILO_CONF"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_elilo_conf_save

# build ELILO default kernel radiolist
mdl_elilo_default_list () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file item status
  # global variables & arrays
  declare -g count=0
  declare -ga list=() krnl=()

  # test EFI kernels exist
  [ "${#efi_krnl[@]}" -eq 0 ] && return
  for file in "${efi_krnl[@]}"; do # loop efi kernels
    item="${file##*/}" # remove path
    item="${item:8}" # remove 1st field
    # test smp-, remove smp-
    [ -z "${item##*smp-*}" ] && item="${item/smp-/}" # remove field
    # test smp, remove smp-
    [ "${item:0:3}" = "smp" ] && item="${item:5}" # remove 1st field
    krnl+=("$item") # build kernel list
    # test count
    if [ "$count" -eq 0 ]; then
      status=on # set status
    else # FAIL first count
      status=off # set status
    fi # END count
    list+=("$count" "$item" "$status") # build list
    (( count++ )) || : # increment count
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_elilo_default_list

# ESG mount commands
# assumes /etc/fstab contains ESP
mdl_esp_mount () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 1 ] && return 1

  # global variables & arrays
  declare -g msg

  case "$1" in
    m) # mount ESP
      msg="$(mount -v "$EFI_LOC" 2>&1 || :)"
      ;;
    mna) # mount ESP, no action
      msg=$(bash_print "$msg" | sed '/ dmesg.*$/d')
      ;;
    u) # umount ESP
      msg="$(umount -v "$EFI_LOC" 2>&1 || :)"
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_esp_mount

# build GRUB config
mdl_grub_conf () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  grub-mkconfig -o "$GRUB_CONF"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_grub_conf

# build GRUB list variables
mdl_grub_list () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare key
  declare -a conf=()
  # global variables & arrays
  declare -g grub_default grub_menu

  # assume previous GRUB selection was from submenu
  # drop submenu name using only 2nd field after '>'
  # take 1st 3 fields using '-' for default krnl name
  grub_default="$(grub-editenv list | cut -d'>' -f2 | cut -d'-' -f1-3)"

  # test grub_default
  if [ -z "$grub_default" ]; then
    grub_default="No default boot entry.\n
modify: /etc/default/grub\n
        GRUB_DEFAULT=saved\n
        GRUB_SAVEDEFAULT=true\n
After modifications: next boot saves default."
  fi # END arg2

  # test grub_conf
  if [ -f "$GRUB_CONF" ]; then
    # find only menuentry & submenu types
    # isolate 1st 2 fields using "'"
    # remove type names & retain tabs
    mapfile -t conf < <(grep -e 'menuentry ' -e 'submenu ' "$GRUB_CONF" |
      cut -d"'" -f1-2 | sed -E -e "s/menuentry '//" -e "s/submenu '//")
  fi # END grub_conf

  # test grub_menu
  if [ "${#conf[@]}" -ne 0 ]; then
    # format grub_menu
    for (( key=0; key<"${#conf[@]}"; key++ )); do
      grub_menu+="> ${conf["$key"]}\n"
    done
  else # FAIL grub_menu
    grub_menu="\"$GRUB_CONF\" not found.\n  see 'man grub-install'\n"
  fi # END grub_menu

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_grub_list

# download kernel(s) to cache
mdl_krnl_dnld () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare mirror mode=0 name path file md5_cache md5_lib
  declare -a wget_opts=()
  # global variables & arrays
  declare -ga md5=()

  mirror="$(grep -v '^#' "$dir_etc/mirrors")" # set mirror
  # test mirror
  if [ "${mirror:0:6}" = "ftp://" ]; then
    mirror="${mirror/\/\//\/}" # replace double w/single slash
    mode=1 # set ftp
  # test mirror file|cdrom
  elif [ "${mirror:0:7}" = "file://" ] || [ "${mirror:0:8}" = "cdrom://" ]; then
    mirror="${mirror#*/}" # remove mode type
    mode=2 # set file|cdrom
  fi # END mirror

  for name in "${select_arr[@]}"; do # loop thru downloads
    path="${pkg_repo[$name]%/*}/" # remove filename
    file="${pkg_repo[$name]}" # full filename
    # test mode
    if [ "$mode" -eq 0 ]; then # http mode
      # drop leading whitespace
      WGET_OPTS=${WGET_OPTS##[[:space:]]}
      # reformat options into array, replace space with newline
      mapfile -t wget_opts < <(printf %b "$WGET_OPTS" | tr "[:space:]" '\n')
      # test file
      if [ ! -e "$dir_cache/$file.asc" ]; then
        wget "${wget_opts[@]}" -P "$dir_cache/$path" "$mirror$file.asc"
      fi # END file
      # test file
      if [ ! -e "$dir_cache/$file" ]; then
        wget "${wget_opts[@]}" -P "$dir_cache/$path" "$mirror$file"
      fi # END file
    elif [ "$mode" -eq 1 ]; then # ftp mode
      # test file
      if [ ! -e "$dir_cache/$file.asc" ]; then
        curl "$mirror$file.asc" -o "$dir_cache/$file.asc"
      fi # END file
      # test file
      if [ ! -e "$dir_cache/$file" ]; then
        curl "$mirror$file" -o "$dir_cache/$file"
      fi # END file
    else # FAIL mode - file|cdrom mode
      # test file
      if [ ! -e "$dir_cache/$file.asc" ]; then
        ln -s "$mirror$file.asc" "$dir_cache/$file.asc"
      fi # END file
      # test file
      if [ ! -e "$dir_cache/$file" ]; then
        ln -s "$mirror$file" "$dir_cache/$file"
      fi # END file
    fi # END mode

    # test checkmd5
    if [ "$CHECKMD5" = "on" ]; then
      # test asc exists
      if [ -e "$dir_cache/$file.asc" ]; then
        md5_cache="$(md5sum "$dir_cache/$file.asc")" # md5sum asc
        md5_cache="${md5_cache%% *}" # remove filename
      fi # END asc exists
      # extract md5sum from slackpkg database
      md5_lib="$(grep -e "$file.asc$" "$dir_lib/CHECKSUMS.md5")" # full MD5 digest
      md5_lib="${md5_lib%% *}" # remove filename
      [ "$md5_cache" != "$md5_lib" ] && md5+=("$file") # add to bad list
      # test tgz exists
      if [ -e "$dir_cache/$file" ]; then
        md5_cache="$(md5sum "$dir_cache/$file")" # md5sum tgz
        md5_cache="${md5_cache%% *}" # remove filename
      fi # END tgz exists
      # extract md5sum from slackpkg database
      md5_lib="$(grep -e "$file$" "$dir_lib/CHECKSUMS.md5")" # full MD5 digest
      md5_lib="${md5_lib%% *}" # remove filename
      [ "$md5_cache" != "$md5_lib" ] && md5+=("$file") # add to bad list
    fi # END checkmd5
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_krnl_dnld

# find kernels available, installed & missing
mdl_krnl_files () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file pkg repo regex krnl name status
  declare -a regex list=()
  # global variables & arrays
  declare -ga pkg_avail=() pkg_inst=() pkg_miss=()
  declare -gA pkg_repo=() # stored in hash order, cannot sort

  # repo kernel packages
  for file in patches slackware testing; do # loop repos, prioritize patches
    repo="$(ls "$dir_lib/$file"*)" # full filename
    repo="${repo%-*}" # remove -filelist.gz
    repo="${repo##*/}" # remove path
    # test skip slackware kernels when patches kernels exist
    [ "$file" = "slackware" ] && [ "${#pkg_repo[@]}" -gt 0 ] && continue
    regex=(kernel-generic kernel-huge kernel-modules kernel-source)
    krnl="$(zgrep -f <(bash_print "${regex[@]}") "$dir_lib/$file"* || :)"
    # test kernel in repo
    if [ -n "$krnl" ]; then
      # found kernels in repo
      mapfile -t list < <(bash_print "$krnl" | cut -d' ' -f1)
      for pkg in "${list[@]}"; do # loop repo packages
        name="${pkg%.*}" # remove extension
        pkg_repo+=(["${name##*/}"]="$repo${pkg#.}") # remove path & dot
      done
      list=() # clear before next loop
    fi # END kernel in repo
  done

  # installed kernel packages
  # find '-regex' - uses EMACS regex
  # declare -a regex - above so use array not variable
  regex=('.*\(nel-gen\|nel-hug\|nel-mod\|nel-sou\).*')
  # find '-L' - follow symbolic links
  mapfile -t list < <(find -L "$dir_log/packages" -regex "${regex[0]}")
  mapfile -t pkg_inst < <(bash_print "${list[@]##*/}" | sort) # remove path

  # available kernel packages
  mapfile -t pkg_avail < <(bash_print "${!pkg_repo[@]}" | sort) # use key

  # missing kernel packages
  for pkg in "${pkg_avail[@]}"; do # loop available packages
    status=1 # 0=found, 1=missing
    for name in "${pkg_inst[@]}"; do # loop installed packages
      [ "$pkg" = "$name" ] && status=0 && break # found pkg
    done
    [ "$status" -eq 1 ] && pkg_miss+=("$pkg") # missing packages
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_krnl_files

# find kernels
mdl_krnl_find () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare pkg
  declare -a concat sorted
  declare -A pkgs
  # global variables & arrays
  declare -ga avail_pkg=() miss_pkg=()

  for pkg in "${pkg_avail[@]}"; do
    pkgs+=(["$pkg"]="${pkg_repo[$pkg]%%/*}") # remove after 1st field
  done
  for pkg in "${!pkgs[@]}"; do
    concat+=("$pkg|[${pkgs[$pkg]}"]) # concatenate with separator
  done
  mapfile -t sorted < <(bash_print "${concat[@]}" | sort)
  for pkg in "${sorted[@]}"; do
    avail_pkg+=("${pkg/|/ }") # replace separator
  done

  pkgs=(); concat=() # reset arrays
  #test pkg_miss
  if [ "${#pkg_miss[@]}" -eq 0 ]; then
    miss_pkg=("All repo packages are installed.")
  else
    for pkg in "${pkg_miss[@]}"; do
      pkgs+=(["$pkg"]="${pkg_repo[$pkg]%%/*}") # remove after 1st field
    done
    for pkg in "${!pkgs[@]}"; do
      concat+=("$pkg|[${pkgs[$pkg]}"]) # concatenate with separator
    done
    mapfile -t sorted < <(bash_print "${concat[@]}" | sort)
    for pkg in "${sorted[@]}"; do
      miss_pkg+=("${pkg/|/ }") # replace separator
    done
  fi # END pkg_miss

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_krnl_find

# install kernel(s) from cache
mdl_krnl_install () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file
  # global variables & arrays
  declare -ga cache_txz

  # find download kernel files
  mapfile -t cache_txz < <(find -L "$dir_cache" -name 'kernel*.txz')
  # test cache_txz files exist
  if [ "${#cache_txz[@]}" -gt 0 ]; then
    for file in "${cache_txz[@]}"; do # loop install kernels
      installpkg "$file" # install kernel cache file
      rm "$file" "$file.asc" # remove kernel .txz & .asc files
    done
  fi # END cache_txz files exist

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_krnl_install

# find installed kernel modules
mdl_krnl_modules () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare pkg file arch
  # global variables & arrays
  declare -ga modules=()

  for pkg in "${pkg_inst[@]}"; do # loop installed packages
    file="${pkg#*-}" # remove 1st field
    file="${file%%-*}" # remove after 2nd field

    arch="" # clear arch
    # test modules
    if [ "$file" = "modules" ]; then # get 3rd field
      arch="${pkg#*-}" # remove 1st field
      arch="${arch#*-}" # remove 2nd field
      arch="${arch%%-*}" # remove after 3rd field
      # test smp
      if [ "$arch" = "smp" ]; then # get 4th field
        arch="${pkg#*-}" # remove 1st field
        arch="${arch#*-}" # remove 2nd field
        arch="${arch#*-}" # remove 3rd field
        arch="${arch%%-*}" # remove after 4th field
#        arch="${arch/_/-}" # replace divider
        arch="$(bash_replace us "$arch")" # replace divider
      fi # END smp
    fi # END modules

    # test arch
    [ -n "$arch" ] && modules+=("$arch") # build modules list
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_krnl_modules

# remove installed kernel files
mdl_krnl_remove () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file

  # test kernel remove
  for file in "${select_arr[@]}"; do # loop remove kernels
    removepkg "$file" # remove selected kernel pkgs
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_krnl_remove

# build lilo.conf based on template
mdl_lilo_conf_build () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare line key lhs label match
  declare krnl krnl_base krnl_type krnl_version
  declare initrd initrd_base initrd_version
  declare -a template
  # global variables & arrays
  declare -ga lilo_conf=()

  # convert multi-line variable from script config to array
  # process substitution <(...) appears as redirected file
  # -t remove trailing newline (1st line of template is null)
  mapfile -t template < <(bash_print "$TEMPLATE_LILO")
  for line in "${template[@]}"; do # loop template
    # skip line (1st line of template is null)
    [ -z "$line" ] && continue

    key=CLONE
    # test clone lines
    if [ -z "${line##*"$key"*}" ]; then # match CLONE
      lilo_conf+=("${line/\#"$key"/}") # remove #CLONE
    fi # END clone lines

    key=DEFAULT
    lhs="default=" # left hand side
    # test default line
    if [ -z "${line##*"$key"*}" ]; then # match default
      # test default_krnl
      if [ -z "${default_krnl##*generic*}" ]; then # match generic
        lilo_conf+=("$lhs${default_krnl/generic-/Gen}") # replace prefix
      elif [ -z "${default_krnl##*huge*}" ]; then # match huge
        lilo_conf+=("$lhs${default_krnl/huge-/Hug}") # replace prefix
      fi # END default_krnl
    fi # END default line
  done

  for krnl in "${boot_krnl[@]}"; do # loop boot kernels
    krnl_base="${krnl##*/}" # remove path
    krnl_type="${krnl_base#*-}" # remove 1st field
    krnl_type="${krnl_type%%-*}" # remove all fields except 1st
    krnl_version="${krnl_base##*-}" # remove before version
    # test krnl_version
    if [ "$krnl_version" = "smp" ]; then
      krnl_version="${krnl_base#*-}" # remove 1st field
      krnl_version="${krnl_version#*-}" # remove 2nd field
      krnl_version="${krnl_version#*-}" # remove 3rd field
    fi # END krnl_version

    match="" # reset match
    for initrd in "${boot_initrd[@]}"; do # loop boot initrds
      initrd_base="${initrd##*/}" # remove path
      initrd_version="${initrd_base##*-}" # remove all fields except last
      initrd_version="${initrd_version%.*}" # remove extension
      # test initrd_version
      if [ "$initrd_version" = "smp" ]; then
        initrd_version="${initrd_base#*-}" # remove 1st field
        initrd_version="${initrd_version%.*}" # remove extension
      fi # END initrd_version
      [ "$initrd_version" = "$krnl_version" ] && match="$initrd_base"
    done

    # test krnl_type
    if [ "$krnl_type" = "generic" ];then
      key=GENERIC
      label=Gen
    elif [ "$krnl_type" = "huge" ]; then
      key=HUGE
      label=Hug
    fi # END krnl_type

    for line in "${template[@]}"; do # loop template
      # skip line (1st line of template is null)
      [ -z "$line" ] && continue
      # test key match
      if [ -z "${line##*"$key"*}" ]; then
        # test line match
        if [ -z "${line##*image*}" ]; then # match image
          lilo_conf+=("${line/\#"$key"/\/boot\/"$krnl_base"}") # replace image
        elif [ -z "${line##*label*}" ]; then # match label
          lilo_conf+=("${line/\#"$key"/"$label""$krnl_version"}") # replace label
        elif [ -z "${line##*initrd*}" ] && [ -n "$match" ]; then # match initrd
          lilo_conf+=("${line/\#"$key"/\/boot\/"$match"}") # replace initrd
        elif [ -z "${line##*read-only*}" ]; then # match read-only
          lilo_conf+=("${line/\#"$key"/}") # replace read-only
        fi # END line match
      fi # END key match
    done
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_lilo_conf_build

# save lilo.conf
mdl_lilo_conf_save () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  bash_print "${lilo_conf[@]}" > "$LILO_CONF"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_lilo_conf_save

# build LILO default kernel radiolist
mdl_lilo_default_list () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare file item status
  # global variables & arrays
  declare -g count=0
  declare -ga list=() krnl

  for file in "${boot_krnl[@]}"; do # loop boot kernels
    item="${file##*/}" # remove path
    item="${item:8}" # remove 1st field
    # test smp-, remove smp-
    [ -z "${item##*smp-*}" ] && item="${item/smp-/}" # remove field
    # test smp, remove smp-
    [ "${item:0:3}" = "smp" ] && item="${item:5}" # remove 1st field
    krnl+=("$item") # build kernel list
    # test count
    if [ "$count" -eq 0 ]; then
      status=on # set status
    else # FAIL first count
      status=off # set status
    fi # END count
    list+=("$count" "$item" "$status") # build list
    (( count++ )) || : # increment count
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_lilo_default_list

# Make initrd(s)
mdl_mkird_build () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # local variables & arrays
  declare krnl

  for krnl in "${select_arr[@]}"; do # loop initrds
    # test smp, replace divider
    [ -z "${krnl##*smp*}" ] && krnl="$(bash_replace us "$krnl")"
    mkinitrd -F -k "$krnl" -o "$MKIRD_LOC/initrd-$krnl.gz" # build initrd
  done

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END mdl_mkird_build

# dialog checklist, requires arguments
view_dialog_checklist () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 5 ] && return 1

  # local variables & arrays
  declare cmd
  declare -n name_ref=$5
  # global variables & arrays
  declare -ga select_arr

  # build dialog command
  cmd="--stdout --no-cancel --separate-output"
  cmd+=" --backtitle \"$1\""
  cmd+=" --title \"$2\""
  cmd+=" --checklist \"\n$3\" 0 0"
  cmd+=" \"$4\" ${name_ref[*]}"

  mapfile -t select_arr < <(dialog --file <(bash_print "$cmd"))

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_dialog_checklist

# dialog infobox, requires arguments
view_dialog_infobox () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 3 ] && return 1

  # local variables & arrays
  declare cmd

  # build dialog command
  cmd="--colors"
  cmd+=" --backtitle \"$1\""
  cmd+=" --title \"$2\""
  cmd+=" --infobox \"\n$3\" 0 0"

  dialog --file <(bash_print "$cmd")

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_dialog_infobox

# dialog menu
view_dialog_menu () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 7 ] && return 1

  # local variables & arrays
  declare cmd
  declare -n name_ref=$7

  # build dialog command
  cmd="--stdout"
  cmd+=" --backtitle \"$1 - $2\""
  cmd+=" --title \"$2 Select\""
  cmd+=" --cancel-label \"$3\""
  cmd+=" --menu \"\n$4\" 0 0 0"
  cmd+=" ${name_ref[*]}"

  menu=$(dialog --file <(bash_print "$cmd")) && level="$5" || level="$6"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_dialog_menu

# dialog msgbox, requires arguments
view_dialog_msgbox () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 4 ] && return 1

  # local variables & arrays
  declare cmd

  case "$1" in
    std) # standard
      # build dialog command
      cmd="--colors"
      cmd+=" --backtitle \"$2\""
      cmd+=" --title \"$3\""
      cmd+=" --msgbox \"\n$4\" 0 0"

      dialog --file <(bash_print "$cmd")
      ;;
    cgl1) # ctrl_grub_list 1
      # build dialog command
      cmd="--no-collapse --colors"
      cmd+=" --backtitle \"$2\""
      cmd+=" --title \"$3\""
      cmd+=" --msgbox \"\n$4\" 25 50"

      dialog --file <(bash_print "$cmd")
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_dialog_msgbox

# dialog radiolist, requires arguments
view_dialog_radiolist () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 6 ] && return 1

  # local variables & arrays
  declare cmd
  declare -n name_ref=$6
  # global variables & arrays
  declare -g select

  case "$1" in
    std) # standard
      # build dialog command
      cmd="--stdout --no-cancel"
      cmd+=" --backtitle \"$2\""
      cmd+=" --title \"$3\""
      cmd+=" --radiolist \"\n$4\" 0 0"
      cmd+=" \"$5\" ${name_ref[*]}"

      select=$(dialog --file <(bash_print "$cmd"))
      ;;
    notags) # no tags
      # build dialog command
      cmd="--stdout --no-cancel --no-tags"
      cmd+=" --backtitle \"$2\""
      cmd+=" --title \"$3\""
      cmd+=" --radiolist \"\n$4\" 0 0"
      cmd+=" \"$5\" ${name_ref[*]}"

      select=$(dialog --file <(bash_print "$cmd"))
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_dialog_radiolist

# dialog yesno, requires arguments
view_dialog_yesno () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 4 ] && return 1

  # local variables & arrays
  declare cmd
  # global variables & arrays
  declare -g confirm

  case "$1" in
    std) # standard
      # build dialog command
      cmd="--stdout --defaultno"
      cmd+=" --backtitle \"$2\""
      cmd+=" --title \"$3\""
      cmd+=" --yesno \"\n$(bash_replace dq "$4")\" 0 0"

      dialog --file <(bash_print "$cmd") && confirm=0 || confirm=1
      ;;
    nc) # no-collapse
      # build dialog command
      cmd="--stdout --defaultno --no-collapse"
      cmd+=" --backtitle \"$2\""
      cmd+=" --title \"$3\""
      cmd+=" --yesno \"\n$(bash_replace dq "$4")\" 0 0"

      dialog --file <(bash_print "$cmd") && confirm=0 || confirm=1
      ;;
    *) # error exit
      exit
      ;;
  esac

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_dialog_yesno

# view main exit
view_main_exit () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  clear # clear menu
  bash_print "\nEnd of $script_name - System Available\n\n"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_main_exit

# view build initrd(s)
view_mkird_build () {
  if [ "$debug" -eq 0 ]; then bash_debug 2; fi # start function

  # test args
  [ "$#" -ne 1 ] && return 1

  # local variables & arrays
  declare -n name_ref=$1

  bash_print "Building the following initrd(s):\n\n${name_ref[*]}\n\n"

  if [ "$debug" -eq 0 ]; then bash_debug 3; fi # end function
} # END view_mkird_build

# pause program for keyboard input
bash_any_key () {

  bash_print "\n"
  # -n1: defines character count
  # -s: hides user input
  # -r: raw string interpretation
  read -n1 -s -r -p "Hit any key..."

} # END bash_any_key

# test for running in chroot
bash_chroot () {

  # global variables & arrays
  declare -g rtnval=0 # set true, yes chroot

  # value 2 is non-chroot/non-container, ignore error code
  if [ "$(stat -c %i /)" -eq 2 ]; then
    rtnval=1 # set false, no chroot
  fi # END stat

} # END bash_chroot

# test for running in container
bash_container () {

  # global variables & arrays
  declare -g rtnval=1 # set false, no container

  # test not empty, null output is false, any output is true
  if [ -n "$(grep "lxc\|docker" /proc/1/cgroup || :)" ]; then
    rtnval=0 # set true, yes container
  fi # END not empty

} # END bash_container

# all debug output goes via fd9 to $debug_log
# 0 - REQUIRED, start debug environment
# 1 - deprecated, end debug environment (script exit does this)
# 2 - start function message
# 3 - end function message
# 4 - start xtrace logging
# 5 - end xtrace logging
# * - user defined logging
bash_debug () {

  # test debug
  if [ "$debug" -eq 0 ]; then

    # test args
    [ "$#" -lt 1 ] && return 1

    case $1 in
      0)
        # create fd & xtrace redirect, send file descriptor to file
        # args: 0, log path & log name
        exec 9> "$2/$3" # open fd 9
        export BASH_XTRACEFD=9 # redirect xtrace with reserved global
        ;;
      1)
        # remove fd & xtrace redirect (script exit does this)
        # args: 1 only
        exec 9>&- # close fd 9
        unset BASH_XTRACEFD # remove reserved global (limited scope)
        ;;
      2)
        # start function debug, test 3rd argument exists
        # args: 2 only
        [ "$#" -lt 1 ] && return 1
        bash_print "\nSTART ${FUNCNAME[1]} - $(caller 1)\n" >&9
        ;;
      3)
        # end function debug, test 2nd argument exists
        # args: 3 only
        [ "$#" -lt 1 ] && return 1
        bash_print "END ${FUNCNAME[1]}\n\n" >&9
        ;;
      4)
        # start xtrace, test 2nd argument exists
        # args: 4 only
        [ "$#" -lt 1 ] && return 1
        set -x # set -o xtrace
        bash_print "START ${FUNCNAME[1]} - xtrace\n" >&9
        ;;
      5)
        # end xtrace, test 2nd argument exists
        # args: 5 only
        [ "$#" -lt 1 ] && return 1
        bash_print "END ${FUNCNAME[1]} - xtrace\n" >&9
        set +x # set +o xtrace
        ;;
      *)
        # generic command, variable arguments
        # no numeric argument
        # example call: bash_debug echo "hello"
        bash_print "$@" >&9
        ;;
    esac
  fi # END debug

} # END bash_debug

# used to debug scripts
# multiple args are variable & array names
# pause script
bash_dump () {

  # test args
  [ "$#" -eq 0 ] && return 1

  # local variables and arrays
  declare arg attr key

  for arg; do # drop 'in' iterates over arguments
    # test arg exists
    if (declare -p "$arg" &>/dev/null); then
      attr=$(declare -p "$arg" | cut -d' ' -f2) # isolate file attributes
      declare -n name_ref="$arg" # local variable, must declare inside loop
      # test attr
      if [ "$attr" = "--" ]; then
        bash_print "\n$arg - variable\n${!arg}\n"
      elif [ "$attr" = "-a" ]; then
        bash_print "\n$arg - indexed array - count:${#name_ref[@]}\n"
        for key in "${!name_ref[@]}"; do
          bash_print "[$key]=${name_ref["$key"]}\n"
        done
      elif [ "$attr" = "-A" ]; then
        bash_print "\n$arg - associative array - count:${#name_ref[@]}\n"
        for key in "${!name_ref[@]}"; do
          bash_print "[$key]=${name_ref["$key"]}\n"
        done
      else # FAIL attr
        bash_print "\n$arg $attr unsupported variable type\n"
      fi # END attr
    else
      bash_print "\n$arg - not variable or array.\n"
    fi # END arg exists
  done
  bash_any_key

} # END bash_dump

# output argument, use for stdout or use command substitution for return value
bash_print () {

  # test args
  [ "$#" -eq 0 ] && return 1

  # test 2nd argument empty, expand '\' characters or separate by newline
  if [ -z "${2:-}" ]; then
    printf '%b' "$1"
  else
    printf '%s\n' "$@"
  fi # END 2nd argument empty

} # END bash_print

# replace - parameter expansion
bash_replace () {

  # test args
  [ "$#" -ne 2 ] && return 1

  # local variables & arrays
  declare replace="$2"

    case $1 in
      dq) # replace all double quotes with caret
        bash_print "${replace//\"/^}"
        ;;
      us) # replace underscore with dash
        bash_print "${replace/_/-}"
        ;;
      *) # error exit
        exit
        ;;
    esac

} # END bash_replace

# Unofficial Bash Strict Mode
bash_strict_mode () {

  # -E / -o errtrace / inherit: functions, command substitutions & subshells
  # -e / -o errexit / exit on error
  # -u / -o nounset / exit on use of undefined variable
  # -o pipefail / exit pipeline on last (rightmost) command with non-zero error
  set -Eeuo pipefail
  # command substitution inherits the value of the errexit
  shopt -s inherit_errexit || :
  # Internal Field Separator / word split on newline & tab (default is space)
  IFS=$'\n\t'

} # END bash_strict_mode

# trap script exit
# requires 'set -e'
# call: trap 'bash_trap ERR $?' EXIT
bash_trap () {

  # test args
  [ "$#" -ne 2 ] && return 1

  # local variables & arrays
  declare key

  bash_print "\n$1 STATUS:$2\n\nBASH COMMAND:\n${BASH_COMMAND}\n
CALL STACK: last on top, 1st on bottom\nline / func / file\n"
  for key in {0..9}; do
    [ "$(caller "$key")" != "" ] && bash_print "$(caller "$key")\n"
  done
  bash_print "\n"

} # END bash_trap

# identify user@hostname
bash_user () {

  # global variables & array
  declare -g user

  user="$(whoami)@$(hostname)"

} # END bash_user

# confirm yes/no, default yes, accept uppercase or lowercase characters
# test rtnval on exit
bash_yesno () {

  # local variables & arrays
  declare input
  # global variables & arrays
  declare -g rtnval=0 # set true

  while :; do
    read -r -p "Are You Sure? [Y/n] " input
    case $input in
      [yY][eE][sS]|[yY]|"")
        break
        ;;
      [nN][oO]|[nN])
        rtnval=1 # set false
        break
        ;;
      *)
        bash_print "\nInvalid input...\n"
        ;;
    esac
  done

} # END bash_yesno

