File functions.sh of Package minimal-initramfs

#!/bin/sh
export PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH

einfo() { echo -ne "\033[1;30m>\033[0;36m>\033[1;36m> \033[0m${*}\n" ;}
ewarn() { echo -ne "\033[1;30m>\033[0;33m>\033[1;33m> \033[0m${*}\n" >&2;}
eerror() { echo -ne "\033[1;30m>\033[0;31m>\033[1;31m> ${*}\033[0m\n" >&2 ;}
die() { eerror "$*"; rescueshell; }

rescueshell() {
    if [ "${console}" ]; then
        console="${console%,*}"
    fi

    if [ "$rescueshell" != 'true' ]; then
        # If we did not forced rescueshell by kernel opt, print additional message.
        ewarn "Dropping to rescueshell because of above error."
    fi

    ewarn "Rescue Shell (busybox's /bin/sh)"
    ewarn "To reboot, press 'control-alt-delete'."
    ewarn "If you wish resume booting process, run 'resume-boot'."
    if [ "$console" ] && [ -c "/dev/${console}" ]; then
        setsid sh -c "exec sh --login </dev/"${console}" >/dev/${console} 2>&1"
    elif command -v cttyhack 1>/dev/null 2>&1; then
        setsid cttyhack sh --login
    elif [ -c '/dev/tty1' ]; then
        setsid sh -c 'exec sh --login </dev/tty1 >/dev/tty1 2>&1'
    else
        sh --login
    fi
    echo
    :> /rescueshell.pid
}

was_shell() {
    [ -f '/rescueshell.pid' ]
    return $?
}

run() {
    if "$@"; then
        echo "Executed: '$@'" >> /init.log
    else
        eerror "'$@' failed."
        echo "Failed: '$@'" >> /init.log
        echo "$@" >> /.ash_history
        rescueshell
    fi
}

run_hooks() {
    # Support for optional code injection via hooks placed into
    # multiple initramfs images.
    if [ -d "/hooks/$1" ]; then
        for i in /hooks/$1/*; do
            [ "$i" = "/hooks/$1/*" ] && break
            einfo "Running '$i' hook ..."
            [ -x "$i" ] && . "$i"
        done
    fi
}

populate_dev_disk_by_label_and_uuid() {
    # Create /dev/disk/by-{uuid,label} symlinks.
    # We could run it with mdev as a trigger on event,
    # but binit uses devtmpfs by default.

    # It is possible that later whatever manages /dev
    # will not probe block device and create symlinks,
    # so we should do it before we move /dev to /newroot/dev

    # Fix for an issue reported under Gentoo's bug #559026.

    local block_device blkid_output LABEL UUID TYPE
    local vars

    einfo "Populating /dev/disk/by-{uuid,label} ..."

    dodir /dev/disk /dev/disk/by-uuid /dev/disk/by-label

    for block_device in /sys/class/block/*; do
        unset blkid_output LABEL UUID TYPE

        block_device="${block_device##*/}"

        blkid_output="$(blkid "/dev/${block_device}")"
        [ "${blkid_output}" ] || continue
        vars="${blkid_output#*:}"
        eval "${vars}"

        [ "${LABEL}" ] && ! [ -e "/dev/disk/by-label/${LABEL}" ] && run ln -s "../../${block_device}" "/dev/disk/by-label/${LABEL}"
        [ "${UUID}" ] && ! [ -e "/dev/disk/by-uuid/${UUID}" ] && run ln -s "../../${block_device}" "/dev/disk/by-uuid/${UUID}"
    done
}

resolve_device() {
    # This function will check if variable at $1 contain LABEL or UUID and then, if LABEL/UUID is valid.
    eval "device=\"\$$1\""
    case "${device}" in
        LABEL\=*|UUID\=*)
            eval "$1=\"$(findfs $device)\""
            if eval "[ -z \"\$$1\" ]"; then
                eerror "Wrong UUID or LABEL."
                rescueshell
            fi
        ;;
        PARTUUID\=*)
            eval "$1=\"$(blkid -t $device -o device)\""
            if eval "[ -z \"\$$1\" ]"; then
                eerror "Wrong UUID or LABEL."
                rescueshell
            fi
        ;;
    esac
}

process_commandline_options() {
    for i in $(cat /proc/cmdline); do
        case "${i}" in
            initramfsdebug)
                set -x
            ;;
            ro|rw)
                root_rw_ro=$i
            ;;
            *=*)
                # Catch-all for foo=bar.
                # And ignore foo.bar=1 etc.
                case "${i%%=*}" in
                    *'.'*)
                        true
                    ;;
                    *)
                        export "${i%%=*}=${i#*=}"
                    ;;
                esac
            ;;
            *.*)
                # ignore foo.bar
                true
            ;;
            *)
                # Everything that is not foo=bar should be just exported as 'true'
                export "${i}=true"
            :;
        esac
     done
}

use() {
    eval "name=\"\$$1\""
    # Check if $name isn't empty and if $name isn't set to false or zero.
    if [ -n "${name}" ] && [ "${name}" != 'false' ] && [ "${name}" != '0' ]; then
        if [ -n "$2" ]; then
            $2
        else
            return 0
        fi
    else
        return 1
    fi
}

musthave() {
    while [ -n "$1" ]; do
        # We can handle it by use() function, yay!
        if ! use "$1"; then
            eerror "The \"$1\" variable is empty, set to false or zero but shoudn't be."
            local missing_variable='true'
        fi
        shift
    done

    if [ "${missing_variable}" = 'true' ]; then
        rescueshell
    fi
}

dodir() {
    for dir in "$@"; do
        run mkdir -m 700 -p "${dir}"
    done
}

loadkeymap() {
    if [ -f /keymap ]; then
        loadkmap < /keymap
    fi
}

get_majorminor() {
    local device="$1"
    musthave device

    local major_hex="$(stat -L -c '%t' "${device}")"
    local minor_hex="$(stat -L -c '%T' "${device}")"

    musthave major_hex minor_hex

    if [ -n "${major_hex}" ] && [ -n "${minor_hex}" ]; then
        printf '%u:%u\n' "0x${major_hex}" "0x${minor_hex}"
    fi
}

loadmodules() {
    einfo "Loading device modules"
    FILE="/usr/share/mobile-helpers/devicemodules.conf"
    [ -f "$FILE" ] && while read -r module; do
        [ -n "$module" ] && modprobe "$module"
    done < "$FILE"
    einfo "Starting udevd daemon"
    /usr/lib/systemd/systemd-udevd &> /dev/null &
    #udevadm control --reload
    udevadm trigger &> /dev/null
}

CreateLUKS() {
    einfo "First time boot"
    einfo "Configuring Full Disk Encryption"
    SHRINK_MB=64
    MAPPER_NAME="enc_root"
    UNL0KR_INSTALL_MESSAGE="\
                                              INSTALLATION \
                    =========================================================\
                                          FDE CONFIGURATION \
                    =========================================================\
                                    Set you new device password \
                                                                                    "
    local IFS=":"
    for enc_dev in ${mobileroot}; do
        IFS="${default_ifs}"
        resolve_device enc_dev

        if [ ! -b "$enc_dev" ]; then
            eerror "Error: Device '$enc_dev'  is not a valid block device."
            exit 1
            rescueshell
        fi

        # Check that the device is NOT mounted
        if grep -qs "$enc_dev" /proc/mounts; then
            eerror "Error: El dispositivo '$enc_dev' está montado."
            eerror "Debes desmontarlo primero (ej: sudo umount $enc_dev)"
            rescueshell
        fi

        einfo "(STEP 1/5) Checking FS in ${enc_dev}"
        e2fsck -f -y "$enc_dev"
        einfo "(STEP 2/5) Shrinking the file system to make space"
        # Calculate the space to be reduced
        SHRINK_BYTES=$((SHRINK_MB * 1024 * 1024))
        BLOCK_SIZE=$(dumpe2fs -h "$enc_dev" 2>/dev/null | grep 'Block size:' | busybox-static awk '{print $3}')
        CURRENT_BLOCKS=$(dumpe2fs -h "$enc_dev" 2>/dev/null | grep 'Block count:' | busybox-static awk '{print $3}')
        BLOCKS_TO_REMOVE=$((SHRINK_BYTES / BLOCK_SIZE))
        NEW_SIZE_BLOCKS=$((CURRENT_BLOCKS - BLOCKS_TO_REMOVE))
        # Shrink the file system
        resize2fs -p "$enc_dev" $NEW_SIZE_BLOCKS
        einfo "(STEP 3/5) Encrypting the partition (take a coffee)"
        pass=$(unl0kr -m "$UNL0KR_INSTALL_MESSAGE" 2>/dev/null)
        busybox-static printf "%s" "$pass" | cryptsetup reencrypt --new --type luks2 --reduce-device-size ${SHRINK_MB}M --key-file=- --label=ROOT "$enc_dev"
        einfo "(STEP 4/5) Open LUKS container"
        busybox-static printf "%s" "$pass" | cryptsetup luksOpen --key-file=- "$enc_dev" $MAPPER_NAME
        einfo "(STEP 5/5) Expanding filesystem to fill container"
        resize2fs /dev/mapper/$MAPPER_NAME
        cryptsetup close $MAPPER_NAME
    done

}
InitializeLUKS() {
    # Verify that the unl0kr binary exists
    if ! command -v unl0kr 1>/dev/null 2>&1; then
        eerror "El binario unl0kr no se encuentra en la imagen initramfs."
        rescueshell
    fi
    
    # Verify that the cryptsetup binary exists
    if ! command -v cryptsetup 1>/dev/null 2>&1; then
        eerror "El binario cryptsetup no se encuentra en la imagen initramfs. Es necesario para abrir el dispositivo LUKS."
        rescueshell
    fi
    einfo "Starting FDE"
    local enc_num='1'
    local dev_name="enc_root"
    local IFS=":"
    UNL0KR_MESSAGE="=========================================================\
                                              UNLOCK DEVICE \
                    ========================================================= \
                                                                                    "

    for enc_dev in ${mobileroot}; do
        IFS="${default_ifs}"
        if ! [ "${enc_num}" = '1' ]; then
            dev_name="enc_root${enc_num}"
        fi
        resolve_device enc_dev
        einfo "Checking LUKS encrypt on ${enc_dev}"
        if cryptsetup isLuks "${enc_dev}"; then
            einfo "FDE Device Detected"
        else
            CreateLUKS
        fi

        einfo "Unlocking '${enc_dev##*/}' and mapping to '/dev/mapper/${dev_name}'..."
        
        local retries=5
        local count=1
        local unlocked=false
        # Loop to retry unlocking up to 5 times
        while [ $count -le $retries ]; do
            # Try unlocking the device.
            pass=$(unl0kr -m "$UNL0KR_MESSAGE" 2>/dev/null)
            if busybox-static printf "%s" "$pass" | cryptsetup luksOpen --key-file=- "${enc_dev}" "${dev_name}"; then
                unlocked=true
                break
            else
                ewarn "Fallo al desbloquear. Intento $count de $retries."
                count=$((count+1))
                # Wait 2 seconds before the next attempt, if it is not the last one
                [ $count -le $retries ] && sleep 2
            fi
        done
        # If all attempts fail, enter the rescue shell.
        if [ "$unlocked" = "false" ]; then
            eerror "Failed to unlock ${enc_dev} after $retries attempts."
            rescueshell
        fi
        enc_num="$((enc_num+1))"
    done
}

InitializeLVM() {
    einfo "Scaning all disks for volume groups."
    # We have to ensure that cache does not exist so vgchange will run 'vgscan' itself.
    if [ -d '/etc/lvm/cache' ]; then run rm -rf '/etc/lvm/cache'; fi
    run lvm vgchange -a y
}

rereadpt() {
    # Check for partition table on all block devices.
    for i in /dev/*; do
        if [ -b "${i}" ]; then
            blockdev --rereadpt "${i}" >/dev/null 2>&1
        fi
    done
}

register_bcache_devices() {
    # Push all the block devices to register_quiet
    # If its bcache, it will bring it up, if not, it will simply ignore it.
    if ! [ -e /sys/fs/bcache/register_quiet ]; then
        ewarn "There's no bcache interface. Missing kernel driver?"
        return 0
    fi

    for i in $(awk '$4 !~ /^(name$|$)/ { print $4 }' /proc/partitions); do
        if [ -e "/dev/${i}" ]; then
            echo "/dev/${i}" >/sys/fs/bcache/register_quiet
        else
            echo "Looks like there's no '/dev/${i}', but should be."
        fi
    done
}

cleanup() {
    was_shell && rm /rescueshell.pid
}

boot_newroot() {
    init="${init:-/sbin/init}"
    einfo "Switching root to /newroot and executing ${init}."
    if ! [ -x "/newroot/${init}" ]; then die "There is no executable '/newroot/${init}'."; fi
    exec env -i \
        TERM="${TERM:-linux}" \
        PATH="${PATH:-/bin:/sbin:/usr/bin:/usr/sbin}" \
            switch_root /newroot "${init}"
}

emount() {
    # All mounts into one place is good idea.
    local mountparams
    while [ "$#" -gt 0 ]; do
        case $1 in
            '/newroot')
                if busybox-static mountpoint -q '/newroot'; then
                    einfo "/newroot already mounted, skipping..."
                else     
                    einfo "Mounting /newroot..."
                    musthave root
                    if [ -n "${rootfstype}" ]; then 
                        mountparams="${mountparams} -t ${rootfstype}"
                    fi
                    resolve_device root
                    if [ ! -b "/dev/mapper/enc_root" ]; then
                        einfo "Mounting ${mobileroot}"
                        run mount -o "${rootflags:+${rootflags},}${root_rw_ro:-ro}" ${mountparams} "${mobileroot}" '/newroot'
                    else
                        einfo "Mounting FDE"
                        run mount -o "${rootflags:+${rootflags},}${root_rw_ro:-ro}" ${mountparams} "/dev/mapper/enc_root" '/newroot'
                    fi
                fi
            ;;

            '/newroot/usr')
                if [ -f '/newroot/etc/fstab' ]; then
                    while read device mountpoint fstype fsflags _; do
                        if [ "${mountpoint}" = '/usr' ]; then
                            if [ -d '/newroot/usr' ]; then
                                einfo "Mounting /newroot/usr..."
                                run mount -o "${fsflags},${root_rw_ro:-ro}" -t "${fstype}" "${device}" '/newroot/usr'
                            else
                                die "/usr in fstab present but no mountpoint /newroot/usr found."
                            fi
                            break
                        fi
                    done < '/newroot/etc/fstab'
                else
                    ewarn "No /newroot/etc/fstab present."
                    ewarn "Early mouting of /usr will not be done."
                fi
            ;;
     
            '/dev')
                local devmountopts='nosuid,relatime,size=10240k,mode=755'

                if grep -q 'devtmpfs' '/proc/filesystems' && ! use mdev; then
                    einfo "Mounting /dev (devtmpfs)..."
                    run mount -t devtmpfs -o ${devmountopts} devtmpfs /dev
                else
                    einfo "Mounting /dev (mdev over tmpfs)..."
                    run mount -t tmpfs -o ${devmountopts} dev /dev
                    run touch /etc/mdev.conf
                    run echo /sbin/mdev > /proc/sys/kernel/hotplug
                    run mdev -s
                    # Looks like mdev create /dev/pktcdvd as a file when both udev and devtmpfs do it as a dir. 
                    # We will do it 'better' to avoid non-fatal-error while starting udev after switching to /newroot.
                    # TODO: Can mdev.conf handle it?
                    if [ -c '/dev/pktcdvd' ]; then
                        run rm '/dev/pktcdvd'
                        run mkdir '/dev/pktcdvd'
                        run mknod '/dev/pktcdvd/control' c 10 61
                    fi
                fi
            ;;

            '/proc')
                einfo "Mounting /proc..."
                run mount -t proc proc /proc
            ;;

            '/sys')
                einfo "Mounting /sys..."
                run mount -t sysfs sysfs /sys
            ;;

            *)
                eerror "emount() does not understand \"$1\""
            ;;
        esac
        shift
    done
}

eumount() {
    while [ "$#" -gt 0 ]; do
        case "$1" in
            *)
                einfo "Unmounting ${1}..."
                run umount "$1"
            ;;
        esac
        shift
    done
}     

moveDev() {
    einfo "Moving /dev to /newroot/dev..."
    if mountpoint -q /dev/pts; then umount -l /dev/pts; fi
    if use mdev; then run echo '' > /proc/sys/kernel/hotplug; fi
    run mount --move /dev /newroot/dev
}

rootdelay() {
    if [ "${rootdelay}" -gt 0 2>/dev/null ]; then
        einfo "Waiting ${rootdelay}s (rootdelay)"
        run sleep ${rootdelay}
    else
        ewarn "\$rootdelay variable must be numeric and greater than zero. Skipping rootdelay."
    fi
}

# vim: noexpandtab
openSUSE Build Service is sponsored by