#! /bin/bash
# SPDX-License-Identifier: GPL-3.0-or-later
# Detects/lists modules that are exported by /sys
# and gives array output for mkinitcpio.conf config file.
# by Tobias Powalowski <tpowa@archlinux.org>

_ARCH="$(uname -m)"
_NO_LOG="/dev/null"
#shellcheck disable=SC2048,SC2086
echo $* | rg -q 'filesystem|hostcontroller|kms' && _SHOW_MODULES=1
# get module modalias from /sys

usage () {
cat << EOF
${0} detects/lists modules that are exported by /sys
and gives array output for mkinitcpio.conf config file.

General options:
    --kernel_version=      use kernel version (no autodetect)
    --root_directory=      use root directory

Module listing options:
    --show-modules         show all detected modules
    --show-agp             show AGP modules
    --show-acpi            show ACPI modules
    --show-block           show BLOCK DEVICE modules
    --show-bluetooth       show BLUETOOTH modules
    --show-cdrom           show CDROM modules
    --show-cpufreq         show CPUFREQ modules
    --show-crypto          show CRYPTO modules
    --show-dca             show DCA modules
    --show-dma             show DMA modules
    --show-drm             show DRM modules
    --show-edac            show EDAC modules
    --show-events          show EVENTS modules
    --show-hwmon           show HWMON modules
    --show-i2c             show I2C modules
    --show-input           show INPUT modules
    --show-ipmi            show IPMI modules
    --show-irda            show IRDA modules
    --show-kvm             show KVM modules
    --show-media           show MEDIA modules
    --show-mei             show MEI modules
    --show-mfd             show MFD modules
    --show-mmc             show MMC modules
    --show-mtd             show MTD modules
    --show-net             show NETWORK modules
    --show-nvme            show NVME modules
    --show-parport         show PARPORT modules
    --show-pinctrl         show PINCTRL modules
    --show-platform        show PLATFORM modules
    --show-powercap        show POWERCAP modules
    --show-serial          show SERIAL modules
    --show-sound           show SOUND modules
    --show-soundwire       show SOUNDWIRE modules
    --show-spi             show SPI modules
    --show-staging         show STAGING modules
    --show-thermal         show THERMAL modules
    --show-tpm             show TPM modules
    --show-video           show VIDEO modules
    --show-virt            show VIRT modules
    --show-watchdog        show WATCHDOG modules
    --show-other           show OTHER modules

MODULES array options:
    --filesystem           add filesystems
    --hostcontroller       add hostcontrollers
    --ati-kms              add ati kernel mode setting
    --amd-kms              add amd kernel mode setting
    --intel-kms            add intel kernel mode setting
    --intel-xe-kms         add intel xe kernel mode setting
    --nvidia-kms           add nvidia kernel mode setting

HOOKS array options:
    --rootdevice=          show HOOKS array for rootdevice
    --systemd              use systemd hooks on early userspace
    --hooks-dir=           use directory for HOOKS check
EOF
}

[[ -z "${1}" ]] && usage
[[ "${1}" = "--help" ]] && usage
[[ "${1}" = "-h" ]] && usage

# setting parameters
_parameter() {
    while [ -n "${1}" ]; do
        case ${1} in
            --root_directory=*) _ROOT_DIRECTORY="$(echo "${1}" | rg -o '=(.*)' -r '$1')" ;;
            --kernel_version=*) _KERNEL_VERSION="$(echo "${1}" | rg -o '=(.*)' -r '$1')" ;;
            --rootdevice=*) _ROOTDEVICE="$(echo "${1}" | rg -o '=(.*)' -r '$1')" ;;
            --hooks-dir=*) _HOOKS_DIR="$(echo "${1}" | rg -o '=(.*)' -r '$1')" ;;
        esac
        shift
    done
}

_show_list() {
    _CATEGORY=${1} ; shift
    [[ $# -gt 0 ]] || return
    echo -n "${_CATEGORY}: "
    #shellcheck disable=SC2048,SC2086
    for i in $*; do echo -n "${i} "; done
    echo ""
}

_show_array() {
    _ARRAY=${1} ; shift
    [[ $# -gt 0 ]] || return
    echo -n "${_ARRAY}=("
    #shellcheck disable=SC2048,SC2086
    for i in $*; do echo -n "${i}"\ ; done
    echo ""
}

_mods() {
    if [[ -n "${2}" ]]; then
        #shellcheck disable=SC2086
        echo "${_MODULES}" | rg -v "${2}" | rg "${1}" | rg -o ".*/(.*).ko.*" -r '$1' | sort -u
    else
        #shellcheck disable=SC2086
        echo "${_MODULES}" | rg "${1}" | rg -o ".*/(.*).ko.*" -r '$1' | sort -u
    fi
}

_kver() {
    # x86_64: rg -Noazm 1 'ABCDEF\x00+(.*) \(.*@' -r '$1' ${1}
    # aarch64 compressed and uncompressed:
    # rg -Noazm 1 'Linux version (.*) \(.*@' -r '$1' ${1}
    # riscv64: zcat ${1} | rg -Noazm 1 'Linux version (.*) \(.*@' -r '$1'
    if [[ -f "${1}" ]]; then
        rg -Noazm 1 'ABCDEF\x00+(.*) \(.*@' -r '$1' "${1}" ||\
        rg -Noazm 1 'Linux version (.*) \(.*@' -r '$1' "${1}" ||\
        zcat "${1}" | rg -Noazm 1 'Linux version (.*) \(.*@' -r '$1'
    fi
}

#shellcheck disable=SC2048,SC2086
_parameter $*

if [[ -z "${_KERNEL_VERSION}" ]]; then
    # get kernel version from installed kernel
    [[ "${_ARCH}" == "x86_64" || "${_ARCH}" == "riscv64" ]] && _VMLINUZ=${_ROOT_DIRECTORY}/boot/vmlinuz-linux
    [[ "${_ARCH}" == "aarch64" ]] && _VMLINUZ=${_ROOT_DIRECTORY}/boot/Image
    _KERNEL_VERSION=$(_kver "${_VMLINUZ}")
    # fallback if no detectable kernel is installed
    [[ -z "${_KERNEL_VERSION}" ]] && _KERNEL_VERSION="$(uname -r)"
fi

_ALIASES="$(fd 'modalias' /sys/ -X cat)"
#shellcheck disable=SC2086
_MODULES="$(modprobe -i -a --dirname="${_ROOT_DIRECTORY}" --set-version="${_KERNEL_VERSION}" \
            --show-depends ${_ALIASES} 2>${_NO_LOG} | rg -v '^builtin')"

# starting different actions
while [ -n "$*"  ]; do
    #shellcheck disable=SC2046
    case ${1} in
        --show-modules)
            _show_list 'AGP      ' $(_mods 'agp/')
            _show_list 'ACPI     ' $(_mods 'acpi/')
            _show_list 'BLOCK    ' $(_mods 'ata/pata' 'pata_acpi') $(_mods 'ata/ata_piix') \
                                   $(_mods 'virtio/virtio_pci') $(_mods 'scsi/') $(_mods 'message/fusion/') \
                                   $(_mods 'drivers/block/' 'nbd pktcdvd|sx8|floppy') \
                                   $(_mods ata/ 'pata|ata_generic') $(_mods 'drivers/block/sx8') \
                                   $(_mods 'usb/' 'usb/input') $(_mods 'firewire/') $(_mods 'ieee1394/') $(_mods 'nvme.ko')
            _show_list 'BLUETOOTH' $(_mods 'bluetooth/')
            _show_list 'CDROM    ' $(_mods 'cdrom/')
            _show_list 'CPUFREQ  ' $(_mods 'cpufreq/')
            _show_list 'CRYPTO   ' $(_mods 'crypto/')
            _show_list 'DCA      ' $(_mods 'dca/')
            _show_list 'DMA      ' $(_mods 'dma/')
            _show_list 'DRM      ' $(_mods 'drm/')
            _show_list 'EDAC     ' $(_mods 'edac/')
            _show_list 'EVENTS   ' $(_mods 'events/')
            _show_list 'HWMON    ' $(_mods 'hwmon/')
            _show_list 'I2C      ' $(_mods 'i2c/')
            _show_list 'INPUT    ' $(_mods 'input/' 'pcspkr') $(_mods 'hid/') $(_mods 'mac_hid')
            _show_list 'IPMI     ' $(_mods 'ipmi/')
            _show_list 'IRDA     ' $(_mods 'irda/')
            _show_list 'KVM      ' $(_mods 'kvm/')
            _show_list 'MEDIA    ' $(_mods 'media/')
            _show_list 'MEI      ' $(_mods 'mei/')
            _show_list 'MFD      ' $(_mods 'mfd/')
            _show_list 'MMC      ' $(_mods 'mmc/')
            _show_list 'MTD      ' $(_mods 'mtd/')
            _show_list 'NET      ' $(_mods 'net/' 'irda/')
            _show_list 'NVME     ' $(_mods 'nvme/' 'nvme.ko')
            _show_list 'PARPORT  ' $(_mods 'parport/')
            _show_list 'PINCTRL  ' $(_mods 'pinctrl/')
            _show_list 'PLATFORM ' $(_mods 'platform/')
            _show_list 'POWERCAP ' $(_mods 'powercap/')
            _show_list 'SERIAL   ' $(_mods 'serial/')
            _show_list 'SOUND    ' $(_mods 'pcspkr') $(_mods 'sound/')
            _show_list 'SOUNDWIRE' $(_mods 'soundwire/')
            _show_list 'SPI      ' $(_mods 'spi/')
            _show_list 'STAGING  ' $(_mods 'staging/')
            _show_list 'THERMAL  ' $(_mods 'thermal/')
            _show_list 'TPM      ' $(_mods 'tpm/')
            _show_list 'VIDEO    ' $(_mods 'video/')
            _show_list 'VIRT     ' $(_mods 'virt/')
            _show_list 'WATCHDOG ' $(_mods 'watchdog/')
            _show_list 'OTHER    ' $(_mods '.ko' 'agp/|acpi/|scsi/|message/fusion|block/sx8|block/cciss|block/cpqarray|block/DAC960|block/virtio|virtio/virtio_pci|ata/|usb/|ieee1394|bluetooth/|cdrom/|cpufreq/|crypto/|dca/|dma/|edac/|events/|net/|hwmon/|i2c/|input/|ipmi/|irda/|kvm/|mac_hid|media/|mei/|mfd/|mmc/|mtd/|nvme/|parport/|pinctrl/|platform/|powercap/|sound/|soundwire|spi/|thermal/|tpm/|drm/|firewire/|hid/|serial/|staging/|video/|virt/|watchdog/') ;;
        --show-agp)        _show_list 'AGP      ' $(_mods agp/) ;;
        --show-acpi)       _show_list 'ACPI     ' $(_mods acpi/) ;;
        --show-block)      _show_list 'BLOCK    ' $(_mods 'ata/pata' 'pata_acpi') $(_mods 'ata/ata_piix') \
                               $(_mods 'virtio/virtio_pci') $(_mods 'scsi/') $(_mods 'message/fusion/') \
                               $(_mods 'drivers/block/' 'nbd|pktcdvd|sx8|floppy') \
                               $(_mods 'ata/' 'pata|ata_generic') $(_mods 'drivers/block/sx8') \
                               $(_mods 'usb/' 'usb/input') $(_mods 'firewire/') $(_mods 'ieee1394/') $(_mods 'nvme.ko') ;;
        --show-bluetooth) _show_list 'BLUETOOTH' $(_mods 'bluetooth/') ;;
        --show-cdrom)     _show_list 'CDROM    ' $(_mods 'cdrom/') ;;
        --show-cpufreq)   _show_list 'CPUFREQ  ' $(_mods 'cpufreq/') ;;
        --show-crypto)    _show_list 'CRYPTO   ' $(_mods 'crypto/') ;;
        --show-drm)       _show_list 'DRM      ' $(_mods 'drm/') ;;
        --show-dca)       _show_list 'DCA      ' $(_mods 'dca/') ;;
        --show-dma)       _show_list 'DMA      ' $(_mods 'dma/') ;;
        --show-edac)      _show_list 'EDAC     ' $(_mods 'edac/') ;;
        --show-events)    _show_list 'EVENTS   ' $(_mods 'events/') ;;
        --show-hwmon)     _show_list 'HWMON    ' $(_mods 'hwmon/') ;;
        --show-input)     _show_list 'INPUT    ' $(_mods 'input/' 'pcspkr') $(_mods 'hid/') ;;
        --show-ipmi)      _show_list 'IPMI     ' $(_mods 'ipmi/') ;;
        --show-i2c)       _show_list 'I2C      ' $(_mods 'i2c/') ;;
        --show-irda)      _show_list 'IRDA     ' $(_mods 'irda/') ;;
        --show-kvm)       _show_list 'KVM      ' $(_mods 'kvm/') ;;
        --show-media)     _show_list 'MEDIA    ' $(_mods 'media/') ;;
        --show-mei)       _show_list 'MEI      ' $(_mods 'mei/') ;;
        --show-mfd)       _show_list 'MFD      ' $(_mods 'mfd/') ;;
        --show-mmc)       _show_list 'MMC      ' $(_mods 'mmc/') ;;
        --show-mtd)       _show_list 'MTD      ' $(_mods 'mtd/') ;;
        --show-net)       _show_list 'NET      ' $(_mods 'net/' 'irda/') ;;
        --show-nvme)      _show_list 'NVME     ' $(_mods 'nvme/' 'nvme.ko') ;;
        --show-parport)   _show_list 'PARPORT  ' $(_mods 'parport/') ;;
        --show-platform)  _show_list 'PLATFORM ' $(_mods 'platform/') ;;
        --show-powercap)  _show_list 'POWERCAP ' $(_mods 'powercap/') ;;
        --show-serial)    _show_list 'SERIAL   ' $(_mods 'serial/') ;;
        --show-sound)     _show_list 'SOUND    ' $(_mods 'pcspkr') $(_mods 'sound/') ;;
        --show-soundwire) _show_list 'SOUNDWIRE ' $(_mods 'soundwire/') ;;
        --show-spi)       _show_list 'SPI      ' $(_mods 'spi/') ;;
        --show-staging)   _show_list 'STAGING  ' $(_mods 'staging/') ;;
        --show-thermal)   _show_list 'THERMAL  ' $(_mods 'thermal/') ;;
        --show-tpm)       _show_list 'TPM      ' $(_mods 'tpm/') ;;
        --show-video)     _show_list 'VIDEO    ' $(_mods 'video/') ;;
        --show-virt)      _show_list 'VIRT     ' $(_mods 'virt/') ;;
        --show-watchdog)  _show_list 'WATCHDOG ' $(_mods 'watchdog/') ;;
        --show-other)     _show_list 'OTHER    ' $(_mods '.ko' 'agp/|acpi/|scsi/|message/fusion|block/sx8|block/cciss|block/cpqarray|block/DAC960|block/virtio|virtio/virtio_pci|ata/|usb/|ieee1394|bluetooth/|cdrom/|cpufreq/|crypto/|dca/|dma/|edac/|events/|net/|hwmon/|i2c/|input/|ipmi/|irda/|kvm/|mac_hid|media/|mei/|mfd/|mmc/|mtd/|nvme/|parport/|platform/|powercap/|sound/|soundwire/|thermal/|tpm/|drm/|firewire/|hid/|serial/|staging/|video/|virt/|watchdog/') ;;
        --filesystem)   _FILESYSTEM="ext2 ext3 ext4 f2fs nilfs2 bcachefs btrfs xfs jfs vfat"
                        for i in ${_FILESYSTEM}; do
                            lsblk -o FSTYPE | rg -q "${i}" && _FS="${_FS} ${i}"
                        done
                        if echo "${_FS}" | rg -q "btrfs|bcachefs"; then
                            _FS="${_FS} crc32c"
                        fi
                        _MODULES_INITRAMFS="${_MODULES_INITRAMFS} ${_FS}" ;;
        --hostcontroller)   _HOSTCONTROLLER="$(_mods 'virtio/virtio_pci') $(_mods 'ata/pata' 'pata_acpi') \
        $(_mods 'scsi/' '/sg.ko|/st.ko|scsi_mod|sr_mod|sd_mod') $(_mods 'message/fusion/') \
        $(_mods 'drivers/block/' 'virtio_blk|nbd|pktcdvd|sx8|floppy') $(_mods 'ata/' 'pata|ata_generic') \
        $(_mods 'drivers/block/sx8') $(_mods 'xhci-hcd') $(_mods 'ehci-hcd') $(_mods 'uhci-hcd') \
        $(_mods 'ohci-hcd') $(_mods 'virtio_blk') $(_mods 'nvme/') $(_mods 'xhci-pci')"
                 _MODULES_INITRAMFS="${_MODULES_INITRAMFS} ${_HOSTCONTROLLER}" ;;
        --ati-kms)      _KMS="radeon"
                        _MODULES_INITRAMFS="${_KMS} ${_MODULES_INITRAMFS}" ;;
        --amd-kms)      _KMS="amdgpu"
                        _MODULES_INITRAMFS="${_KMS} ${_MODULES_INITRAMFS}" ;;
        --intel-kms)    _KMS="i915"
                        _MODULES_INITRAMFS="${_KMS} ${_MODULES_INITRAMFS}" ;;
        -intel-xe-kms)  _KMS="xe"
                        _MODULES_INITRAMFS="${_KMS} ${_MODULES_INITRAMFS}" ;;
        --nvidia-kms)   _KMS="nouveau"
                        _MODULES_INITRAMFS="${_KMS} ${_MODULES_INITRAMFS}" ;;
        --systemd)      _SYSTEMD="1" ;;
    esac
    shift
done
# modules for mkinitcpio.conf
#shellcheck disable=SC2005
[[ -n "${_SHOW_MODULES}" ]] && echo "$(_show_array "MODULES" "${_MODULES_INITRAMFS}" | sd " +" " " | sd ' $' ')')"

# hooks for mkinitcpio.conf
if [[ -n "${_ROOTDEVICE}" ]]; then
    if [[ ! -b "${_ROOTDEVICE}" ]]; then
        echo "ERROR: ${_ROOTDEVICE} not a valid block device."
        exit 1
    fi
    if [[ -n "${_SYSTEMD}" ]]; then
        _ENCRYPT=sd-encrypt
    else
        _ENCRYPT=encrypt
    fi
    _ROOTCHECK="$(lsblk -rpsno TYPE "${_ROOTDEVICE}" |\
                  rg -vw "disk|part" | tac |\
                  sd 'crypt' "${_ENCRYPT}" |\
                  sd 'raid.*[0-9]' 'mdadm_udev' |\
                  sd 'lvm' 'lvm2')"
    if [[ -z "${_HOOKS_DIR}" ]]; then
        _HOOKS_DIR="${_ROOT_DIRECTORY}/usr/lib/initcpio/install"
    fi
    if [[ ! -d "${_HOOKS_DIR}" ]]; then
        echo "ERROR: ${_HOOKS_DIR} not a valid HOOKS directory."
        exit 1
    fi
    if [[ -n "${_SYSTEMD}" ]]; then
        _START_HOOKS="systemd autodetect microcode modconf kms keyboard sd-vconsole block filesystems fsck ${_ROOTCHECK}"
    else
        _START_HOOKS="base udev autodetect microcode modconf kms keyboard keymap consolefont block filesystems fsck ${_ROOTCHECK}"
    fi
    # remove the ones that don't exist on the system
    for i in ${_START_HOOKS}; do
        if ! [[ -e "${_HOOKS_DIR}/$i" ]]; then
            #shellcheck disable=SC2001
            _START_HOOKS=$(echo "${_START_HOOKS}" | sd "${i} " '')
        fi
    done
    #shellcheck disable=SC2005
    echo "$(_show_array "HOOKS" "${_START_HOOKS}" | sd " +" " " | sd ' $' ')')"
fi
# vim: set ft=sh ts=4 sw=4 et:
