#! /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 $* | grep -Eq 'filesystem|hostcontroller|kms' && _SHOW_MODULES=1
# get module modalias from /sys
_ALIASES="$(find /sys/ -noleaf -name modalias  -exec cat {} + 2>${_NO_LOG})"

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-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-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
    --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
    exit 1
}

[[ -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}" | cut -d '=' -f 2)" ;;
            --kernel_version=*) _KERNEL_VERSION="$(echo "${1}" | cut -d '=' -f 2)" ;;
            --rootdevice=*) _ROOTDEVICE="$(echo "${1}" | cut -d '=' -f 2)" ;;
            --hooks-dir=*) _HOOKS_DIR="$(echo "${1}" | cut -d '=' -f 2)" ;;
        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() {
    _INCLUDE=${1} ; shift
    _EXLUDE=""
    while [[ "${1}" ]]; do
        [[ "${_EXCLUDE}" ]] && _EXCLUDE="${_EXCLUDE}|${1}" || _EXCLUDE="${1}"
        shift
    done
    if [[ "${_EXCLUDE}" ]]; then
        #shellcheck disable=SC2086
        modprobe -i -a --dirname="${_ROOT_DIRECTORY}" --set-version="${_KERNEL_VERSION}" --show-depends ${_ALIASES} 2>${_NO_LOG} |\
        grep -v ^builtin | sed "s|^insmod ${_ROOT_DIRECTORY}/lib/modules/${_KERNEL_VERSION}/kernel||g" |\
        sort -u | grep -E -v "${_EXCLUDE}" | grep "${_INCLUDE}" | sed -ne "s#^/.*/\(.*\)\.ko.*#\1#p"
    else
        #shellcheck disable=SC2086
        modprobe -i -a --dirname="${_ROOT_DIRECTORY}" --set-version="${_KERNEL_VERSION}" --show-depends ${_ALIASES} 2>${_NO_LOG} |\
        grep -v ^builtin | sed "s|^insmod ${_ROOT_DIRECTORY}/lib/modules/${_KERNEL_VERSION}/kernel||g" |\
        sort -u | grep "${_INCLUDE}" | sed -ne "s#^/.*/\(.*\)\.ko.*#\1#p"
    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
    if [[ -f "${_VMLINUZ}" && "${_ARCH}" == "x86_64" ]]; then
        offset="$(od -An -j0x20E -dN2 "${_VMLINUZ}")"
        read -r _KERNEL_VERSION _ < <(dd if="${_VMLINUZ}" bs=1 count=127 skip=$((offset + 0x200)) 2>${_NO_LOG})
    fi
    if [[ -f "${_VMLINUZ}" ]]  &&  [[ "${_ARCH}" == "riscv64" || "${_ARCH}" == "aarch64" ]]; then
        reader="cat"
        # try if the image is gzip compressed
        bytes="$(od -An -t x2 -N2 "${_VMLINUZ}" | tr -dc '[:alnum:]')"
        [[ $bytes == '8b1f' ]] && reader="zcat"
        read -r _ _ _KERNEL_VERSION _ < <($reader "${_VMLINUZ}" | grep -m1 -aoE 'Linux version .(\.[-[:alnum:]]+)+')
    fi
    # fallback if no detectable kernel is installed
    [[ -z "${_KERNEL_VERSION}" ]] && _KERNEL_VERSION="$(uname -r)"
fi

# 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 "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 "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/ mtd/ nvme/ parport/ pinctrl/ platform/ powercap/ sound/ 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-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-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/ mtd/ nvme/ parport/ platform/ powercap/ sound/ 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 | grep -q "${i}" && _FS="${_FS} ${i}"
                        done
                        if echo "${_FS}" | grep -q btrfs || echo "${_FS}" | grep 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}" ;;
        --nvidia-kms)   _KMS="nouveau"
                        _MODULES_INITRAMFS="${_KMS} ${_MODULES_INITRAMFS}" ;;
        --systemd)      _SYSTEMD="1"
    esac
    shift
done
# modules for mkinitcpio.conf
[[ -n "${_SHOW_MODULES}" ]] && _show_array "MODULES" "${_MODULES_INITRAMFS}" | sed -e 's/\  //g' -e 's/\ \ /\ /g' -e 's/ $/)/g'

# 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}" | grep -vwe "disk" -we "part" | tac | sed -e "s#crypt#${_ENCRYPT}#g" -e 's#raid.*[0-9]#mdadm_udev#g' -e 's#lvm#lvm2#g')"
    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}" | sed -e "s/${i}\ //g")
        fi
    done
    _show_array "HOOKS" "${_START_HOOKS}" | sed -e 's/\ \ /\ /g' -e 's/\ $/)/g'
fi
# vim: set ft=sh ts=4 sw=4 et:
