# -*- mode: sh; indent-tabs-mode: nil; sh-basic-offset: 4; -*-
# vim: et ft=sh sts=4 sw=4

#  SPDX-License-Identifier: LGPL-2.1+
#
#  Copyright © 2019-2021 Collabora Ltd.
#  Copyright © 2019-2021 Valve Corporation.
#
#  This file is part of steamos-customizations.
#
#  steamos-customizations is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public License as
#  published by the Free Software Foundation; either version 2.1 of the License,
#  or (at your option) any later version.

#
# /sys helpers
#
# These functions require nothing apart from /sys being up,
# and basic commands such a basename, dirname, realpath, ...
#

get_device_number() {

    # Get the device number, for example:
    # /dev/sda3 -> 3
    # /dev/nvme0n1p3 -> 3

    local device=$(realpath "$1")
    [ -b "$device" ] || return

    local devname=$(basename "$device")
    local partition="/sys/class/block/$devname/partition"
    if [ -e "$partition" ]; then
        cat "$partition"
    fi
}

get_parent_device() {

    # Get the parent of a block device, for example:
    # /dev/sda3 -> /dev/sda

    local device=$(realpath "$1")
    [ -b "$device" ] || return

    local devname=$(basename "$device")
    local diskname=$(basename "$(realpath "/sys/class/block/$devname/..")")
    local disk="/dev/$diskname"
    [ -b "$disk" ] || return

    echo "$disk"
}

get_slave_devices() {

    # Get the slaves owned by a block device, for example:
    # /dev/dm-0 -> /dev/sda4 /dev/sda6  (separated with newlines)

    local device=$(realpath "$1")
    [ -b "$device" ] || return

    local devname=$(basename "$device")
    [ -d "/sys/class/block/$devname/slaves/" ] || return

    local slavenames=$(ls -1 "/sys/class/block/$devname/slaves/")
    local slaves=$(printf "/dev/%s\n" $slavenames)

    echo "$slaves"
}

#
# sfdisk(8) helpers  --  fdisk package
#
# These functions require sfdisk, hence work at the partition level.
#

get_device_by_partlabel() {

    # Get the block device for a given disk and partition label, for example:
    # /dev/sda, efi-A -> /dev/sda2

    local disk=$(realpath "$1")
    local partlabel=$2

    [ -b "$disk" ] || return
    [ "$partlabel" ] || return

    while read device name; do
        # FIXME return first match
        if [ "$name" == "$partlabel" ]; then echo "$device"; return; fi
    done < <(sfdisk -ql -o device,name "$disk" | tail +2)
}

get_device_by_typeuuid() {

    # Get the block device for a given disk and partition type uuid, for example:
    # /dev/sda, c12a7328-f81f-11d2-ba4b-00a0c93ec93b -> /dev/sda1

    local disk=$(realpath "$1")
    local typeuuid=${2,,}

    [ -b "$disk" ] || return
    [ "$typeuuid" ] || return

    while read device uuid; do
        # FIXME return first match
        if [ "${uuid,,}" == "$typeuuid" ]; then echo "$device"; return; fi
    done < <(sfdisk -ql -o device,type-uuid "$disk" | tail +2)
}

get_device_by_partnumber() {

    # Get the block device for a given disk and partition number, for example:
    # /dev/sda, 3 -> /dev/sda3

    # TODO rework?

    local disk=$(realpath "$1")
    local partnumber=$2

    [ -b "$disk" ] || return
    [ "$partnumber" ] || return

    sfdisk -ql -o device "$disk" | tail +2 | sed -n "${partnumber}p"
}

get_partlabel() {

    # Get the partlabel for a given block device, for example:
    # /dev/sda2 -> efi-A

    local device=$(realpath "$1")
    [ -b "$device" ] || return

    local disk=$(get_parent_device "$device")
    [ -b "$disk" ] || return

    while read dev name; do
        if [ "$dev" == "$device" ]; then echo "$name"; return; fi
    done < <(sfdisk -ql -o device,name "$disk" | tail +2)
}

get_partuuid() {

    # Get the partuuid for a given block device, for example:
    # /dev/sda2 -> 9f1db873-be42-485b-9993-78466a3637c7

    local device=$(realpath "$1")
    [ -b "$device" ] || return

    local disk=$(get_parent_device "$device")
    [ -b "$disk" ] || return

    while read dev uuid; do
        if [ "$dev" == "$device" ]; then echo "${uuid,,}"; return; fi
    done < <(sfdisk -ql -o device,uuid "$disk" | tail +2)
}

get_partition_count() {

    # Get the number of partitions on a disk, for example:
    # /dev/sda -> 10

    local disk=$(realpath "$1")
    [ -b "$disk" ] || return

    sfdisk -ql "$disk" | tail +2 | wc -l
}

#
# lsblk(8), blkid(8) helpers  --  util-linux package
#
#    ~ Caveats ~
#
# lsblk(8) is recommended over blkid, however it requires udev to be around,
# otherwise it fails to get some information. When is udev not around, you
# might ask? In containers. blkid(8), OTOH, seems to behave just fine without
# udev.
#
# Hence these functions call lsblk first, and fall back to blkid on failure.
#

lsblk_fstype() {

    # Get the filesystem type for a given block device, for example:
    # /dev/sda6 -> ext4

    local device=$(realpath "$1")
    [ -b "$device" ] || return

    local fstype=

    fstype=$(lsblk --nodeps -no fstype "$device")
    if [ "$fstype" ]; then echo "$fstype"; return; fi

    fstype=$(blkid -o value -s TYPE "$device")
    if [ "$fstype" ]; then echo "$fstype"; return; fi
}

#
# findmnt(8), mountpoint(1) helpers  --  util-linux package
#
# These functions require findmnt, hence (see man page):
# /etc/fstab, /etc/mtab or /proc/self/mountinfo
#
#     ~ Caveats ~
#
# Automatic mountpoints (ie. autofs) must be handled with care. They're not
# triggered by findmnt, so it's up to the caller to mount it beforehand, if
# needed. If we don't decide to mount it, we face inconsistent result, ie:
#
#    $ findmnt --real -no partuuid /efi      # -> no result
#    $ ls >/dev/null /efi                    # trigger mount
#    $ findmnt --real -no partuuid /efi      # try again
#    652313ba-e3f6-4435-9626-e9995cebb4fe

find_mountpoint_for_device() {

    # Get the mountpoint associated with a block device, assuming that the
    # block device is mounted (ie. it won't work for autofs). For example:
    # /dev/sda8 -> /var

    local device=$(realpath "$1")
    [ -b "$device" ] || return

    while read source target; do
        # return first match
        echo "$target"; return
    done < <(findmnt --real -nvo source,target "$device")
}

find_device_for_mountpoint() {

    # Get the block device backing a mountpoint, for example:
    # /var -> /dev/sda8
    # /    -> /dev/dm-0

    local mountpoint=$(realpath "$1")
    mountpoint -q "$mountpoint" || return

    # make sure autofs mountpoints is mounted
    local fstype=$(findmnt -no fstype "$mountpoint")
    if [ "$fstype" == "autofs" ]; then
        ls >/dev/null "$mountpoint"
    fi

    local source=$(findmnt --real -nvo source "$mountpoint")
    [ "$source" ] || return

    realpath "$source"
}

find_device_for_mountpoint_deep() {

    # Similar to find_device_for_mountpoint(), except that in case the device
    # found is a device mapper, we go further and resolve it to the "real"
    # block device.
    #
    # This was intended for dm-verity, and tested with dm-verity. Other kind
    # of device mappers were not tested, and might not work.
    #
    # For example:
    # / -> /dev/sda6

    local mountpoint=$(realpath "$1")
    mountpoint -q "$mountpoint" || return

    local device=$(find_device_for_mountpoint "$mountpoint")
    if [[ "$device" != /dev/dm-* ]]; then echo "$device"; return; fi

    while read slave; do
        # discard slaves whose fstype is DM_*
        if [[ "$(lsblk_fstype "$slave")" == DM_* ]]; then continue; fi
        # return first match
        echo "$slave"; return
    done < <(get_slave_devices "$device")
}

#
# steamos partitions helpers
#

get_partition_set() {

    # Get the partition set for a given partlabel, for example:
    # efi-A -> A
    # var   ->

    local partlabel=$1
    local suffix=

    suffix=${partlabel##*-}
    if [ "$suffix" = "$partlabel" ]; then
        # delimiter was not found, hence partition set is empty
        return
    fi

    echo "$suffix"
}

get_partition_linkname() {

    # Get the partition symlink name for a given partlabel, for example:
    # efi-A -> efi
    # var   -> var

    local partlabel=$1

    echo "${partlabel%-*}"
}

#
# misc helpers
#

roothash_is_valid() {

    # Test whether a roothash is valid (should be a 64 chars hexa string)

    local roothash=$1
    local pattern='^[a-fA-F0-9]{64}$'

    [[ "$roothash" =~ $pattern ]]
}

verity_is_enabled() {

    # Test whether dm-verity is enabled for the rootfs

    grep -q 'roothash=' /proc/cmdline
}
