#!/bin/bash

add_systemd_unit() {
    # Add a systemd unit file to the initcpio image. Hard dependencies on binaries
    # and other unit files will be discovered and added.
    #   $1: path to rules file (or name of rules file)

    local unit key dep

    unit=$(PATH=/usr/lib/systemd/system:/lib/systemd/system type -P "$1")
    if [[ -z $unit ]]; then
        # complain about not found unit file
        return 1
    fi

    add_file "$unit"

    while IFS='=' read -r key values; do
        read -ra values <<< "$values"

        case $key in
            Requires|OnFailure)
                # only add hard dependencies (not Wants)
                map add_systemd_unit "${values[@]}"
                ;;
            Exec*)
                # do not add binaries unless they are required,
                # strip special executable prefixes
                case ${values[0]} in
                    -*)  ;;
                    !!*) add_binary "${values[0]#!!}" ;;
                    *)   add_binary "${values[0]#[@!:+]}" ;;
                esac
                ;;
        esac

    done <"$unit"

    # preserve reverse soft dependency
    for dep in {/usr,}/lib/systemd/system/*.wants/"${unit##*/}"; do
        if [[ -L $dep ]]; then
            add_symlink "$dep"
        fi
    done

    # add hard dependencies
    if [[ -d $unit.requires ]]; then
        for dep in "$unit".requires/*; do
            add_systemd_unit "${dep##*/}"
        done
    fi
}

add_systemd_drop_in() {
    local unit="$1" dropin_name="$2"

    add_file - "/etc/systemd/system/${unit}.d/${dropin_name}.conf" 644
}

build() {
    local rules unit

    map add_module 'crypto-lzo' 'crypto-lz4'

    add_binary /usr/bin/kmod /usr/bin/modprobe
    add_binary /usr/lib/systemd/systemd /init

    # Generic utility binaries
    map add_binary /usr/bin/mount \
        /usr/bin/sulogin \
        /usr/bin/umount \
        /usr/bin/swapon \
        /usr/bin/swapoff

    # Systemd specific binaries
    map add_binary \
        /usr/bin/journalctl \
        /usr/bin/systemd-tmpfiles \
        /usr/lib/systemd/systemd-executor \
        /usr/lib/systemd/systemd-hibernate-resume \
        /usr/lib/systemd/systemd-shutdown \
        /usr/lib/systemd/systemd-sulogin-shell \
        /usr/lib/systemd/systemd-validatefs \
        /usr/lib/systemd/system-generators/systemd-fstab-generator \
        /usr/lib/systemd/system-generators/systemd-gpt-auto-generator \
        /usr/lib/systemd/system-generators/systemd-hibernate-resume-generator \
        /usr/lib/systemd/system-generators/systemd-debug-generator

    # udev rules
    map add_udev_rule "$rules" \
        50-udev-default.rules \
        60-persistent-storage.rules \
        64-btrfs.rules \
        80-drivers.rules \
        90-image-dissect.rules \
        99-systemd.rules

    # systemd units
    map add_systemd_unit \
        initrd-cleanup.service \
        initrd-fs.target \
        initrd-parse-etc.service \
        initrd-root-fs.target \
        initrd-root-device.target \
        initrd-switch-root.service \
        initrd-switch-root.target \
        initrd-udevadm-cleanup-db.service \
        initrd.target \
        kmod-static-nodes.service \
        local-fs.target \
        local-fs-pre.target \
        paths.target \
        reboot.target \
        slices.target \
        sockets.target \
        swap.target \
        systemd-battery-check.service \
        systemd-bsod.service \
        systemd-fsck@.service \
        systemd-hibernate-resume.service \
        systemd-journald-audit.socket \
        systemd-journald-dev-log.socket \
        systemd-journald.service \
        systemd-modules-load.service \
        systemd-pcrphase-initrd.service \
        systemd-tmpfiles-setup.service \
        systemd-tmpfiles-setup-dev.service \
        systemd-udevd-control.socket \
        systemd-udevd-kernel.socket \
        systemd-udevd.service \
        systemd-udev-trigger.service \
        systemd-validatefs@.service \
        timers.target \
        rescue.target \
        emergency.target \
        breakpoint-pre-basic.service \
        breakpoint-pre-udev.service \
        breakpoint-pre-mount.service \
        breakpoint-pre-switch-root.service

    # add libraries dlopen()ed (hard and optional dependencies):
    #  tss2-*   -> tpm2-util
    #  kmod     -> systemd-shared
    #  qrencode -> bsod
    for LIB in kmod tss2-{esys,rc,mu,tcti-'*'} qrencode; do
        LC_ALL=C.UTF-8 find /usr/lib/ -maxdepth 1 -name "lib${LIB}.so*" | while read -r FILE; do
            if [[ -L "${FILE}" ]]; then
                add_symlink "${FILE}"
            else
                add_binary "${FILE}"
            fi
        done
    done

    add_symlink "/usr/lib/systemd/system/default.target" "initrd.target"
    add_symlink "/usr/lib/systemd/system/ctrl-alt-del.target" "reboot.target"

    printf '%s\n' \
        'passwd: files' \
        'group: files' \
        'shadow: files' \
        | add_file - '/etc/nsswitch.conf' 0644

    printf 'root:x:0:0:root:/root:/bin/sh\n' | add_file - '/etc/passwd' 0644
    printf 'root:*:::::::\n' | add_file - '/etc/shadow' 0400
    getent group root adm audio clock disk input kmem kvm lp optical render sgx storage systemd-journal tty utmp uucp video wheel | awk -F: ' { print $1 ":x:" $3 ":" }' | add_file - '/etc/group' 0644

    add_dir "/etc/modules-load.d"
    (
      # FIXME: this should be a function in mkinitcpio
      # shellcheck disable=SC1091,SC2154 source=mkinitcpio.conf
      . "$_f_config"
      set -f
      printf '%s\n' "${MODULES[@]}" | add_file - '/etc/modules-load.d/MODULES.conf' 644
    )

    [[ -f /etc/fstab.initramfs ]] && add_file "/etc/fstab.initramfs" "/etc/fstab"

    # Add default systemd tmpfiles, and tmpfile to copy stuff from .extra to /run/systemd
    add_file "/usr/lib/tmpfiles.d/systemd.conf"
    add_file "/usr/lib/tmpfiles.d/20-systemd-stub.conf"
}

help() {
    cat <<HELPEOF
This will install a basic systemd setup in your initramfs, and is meant to
replace the 'base', 'usr', 'udev' and 'resume' hooks. Other hooks with runtime
components will need to be ported, and will not work as intended. You also may
wish to still include the 'base' hook (before this hook) to ensure that a
rescue shell exists on your initramfs.
HELPEOF
}

# vim: set ft=sh ts=4 sw=4 et:
