## Casper helper functions, used by casper on boot and by casper-snapshot

if [ "${BUILD_SYSTEM}" = "Debian" ] || [ "${BUILD_SYSTEM}" = "Ubuntu" ]; then
    MP_QUIET="-q"
else
    MP_QUIET=""
fi

if [ ! -x "/bin/fstype" ]; then
    # klibc not in path -> not in initramfs
    export PATH="${PATH}:/usr/lib/klibc/bin"
fi

sys2dev() {
    sysdev=${1#/sys}
    echo "/dev/$(udevadm info -q name -p ${sysdev} 2>/dev/null|| echo ${sysdev##*/})"
}

subdevices() {
    sysblock=$1
    r=""
    # When booting off a "hybrid image" you can mount the whole device
    # or the first partition (which has offset 0). It's generally
    # better to mount the partition if we can so that we can mount
    # other partitions on the device (e.g. the persistence partition),
    # so try those first.
    for dev in "${sysblock}"/* "${sysblock}"; do
        if [ -e "${dev}/dev" ]; then
            r="${r} ${dev}"
        fi
    done
    echo ${r}
}

get_fstype() {
    local FSTYPE
    local FSSIZE
    eval $(fstype < $1)
    if [ "$FSTYPE" != "unknown" ]; then
        echo $FSTYPE
        return 0
    fi
    /sbin/blkid -s TYPE -o value $1 2>/dev/null
}

where_is_mounted() {
    device=$1
    if grep -q "^$device " /proc/mounts; then
        mountpoint="$(grep "^$device " /proc/mounts | awk '{print $2; exit}')"
        grep "^$device " /proc/mounts | read d mountpoint rest
        echo $mountpoint
        return 0
    fi
    return 1
}

what_is_mounted() {
    cat /proc/mounts | awk -v P=$1 '$2 == P { print $1 }'
}

lastline() {
    while read lines ; do
        line=${lines}
    done
    echo "${line}"
}

base_path ()
{
    testpath="${1}"
    mounts="$(awk '{print $2}' /proc/mounts)"
    testpath="$(busybox realpath ${testpath})"

    while true ; do
        if echo "${mounts}" | grep -qs "^${testpath}" ; then
            set -- `echo "${mounts}" | grep "^${testpath}" | lastline`
            echo ${1}
            break
        else
            testpath=`dirname $testpath`
        fi
    done
}

fs_size ()
{
    # Returns used/free fs kbytes + 5% more
    # You could pass a block device as $1 or the mount point as $2

    dev="${1}"
    mountp="${2}"
    used="${3}"

    if [ -z "${mountp}" ]; then
        mountp=$(where_is_mounted "${dev}")
        if [ "$?" -gt 0 ]; then
            mountp="/mnt/tmp_fs_size"
            mkdir -p "${mountp}"
            mount -t $(get_fstype "${dev}") -o ro "${dev}" "${mountp}"
            doumount=1
        fi
    fi

    if [ "${used}" = "used" ]; then
        size=$(du -ks ${mountp} | cut -f1)
        size=$(expr ${size} + ${size} / 20 ) # FIXME: 5% more to be sure
    else
        # free space
        size="$(df -k | grep -s ${mountp} | awk '{print $4}')"
    fi

    if [ -n "${doumount}" ]; then
        umount "${mountp}"
        rmdir "${mountp}"
    fi
    echo "${size}"
}

setup_loop() {
    local fspath=$1
    local module=$2
    local pattern=$3
    local offset=$4

    modprobe ${MP_QUIET} -b "$module"
    udevadm settle

    if [ "$module" = loop ]; then
        if [ ! -e /dev/loop0 ]; then
            # temporary workaround for kernel bug
            for i in 0 1 2 3 4 5 6 7; do
                mknod "/dev/loop$i" b 7 "$i" || true
            done
        fi

        dev="$(losetup -f)"
        if [ "$dev" ]; then
            if [ -n "$offset" ]; then
                losetup -o "$offset" "$dev" "$fspath"
            else
                losetup "$dev" "$fspath"
            fi
            echo "$dev"
            return 0
        else
            panic "No loop devices available"
        fi
    else
        for loopdev in $pattern; do
            if [ "$(cat $loopdev/size)" -eq 0 ]; then
                dev=$(sys2dev "${loopdev}")
                if [ -n "$offset" ]; then
                    losetup -o "$offset" "$dev" "$fspath"
                else
                    losetup "$dev" "$fspath"
                fi
                echo "$dev"
                return 0
            fi
        done
        panic "No loop devices available"
    fi
}

# casper itself no longer uses this function, but it's used by third parties
# in installers: https://bugs.launchpad.net/ubuntu/+source/casper/+bug/1845535
#
# Returns 0 on success
# panics if remount- or bind-mount fails
# returns 1 on failure otherwise
try_mount ()
{
    dev="${1}"
    mountp="${2}"
    opts="${3}"

    if where_is_mounted ${dev} > /dev/null; then
        if [ "${opts}" != "ro" ]; then
            mount -o remount,"${opts}" ${dev} $(where_is_mounted ${dev}) || panic "Remounting failed"
            return 0
        fi
        mount -o bind $(where_is_mounted ${dev}) ${mountp} || panic "Cannot bind-mount"
        return 0
    else
        mount -t $(get_fstype "${dev}") -o "${opts}" "${dev}" "${mountp}"
        ret=$?
        if [ $ret -ne 0 ]; then
            log_warning_msg "Cannot mount ${dev} on ${mountp}"
            return 1
        fi
        return 0
    fi
}

find_cow_device() {
    pers_label="${1}"

    udevadm settle

    if [ -e "/dev/disk/by-label/${pers_label}" ]; then
        echo /dev/disk/by-label/${pers_label}
        return 0
    fi

    cow_backing="/${pers_label}-backing"

    if [ -z "${PERSISTENT_PATH}" ]; then
        pers_fpath=${pers_label}
    else
        pers_fpath=${PERSISTENT_PATH}/${pers_label}
    fi

    for sysblock in $(echo /sys/block/* | tr ' ' '\n' | grep -v loop); do
	# do not ever probe floppies, probing nonexistant ones delays the boot to half an hour and longer
        n=${sysblock##*/}
        if [ "${n#fd}" != "$n" ]; then
            continue
        fi
        for dev in $(subdevices "${sysblock}"); do
            devname=$(sys2dev "${dev}")
            # Do not add any filesystem types here that might be able to
            # mount a journalled filesystem and replay the journal. Doing so
            # will cause data loss when a live CD is booted on a system
            # where filesystems are in use by hibernated operating systems.
            case "$(get_fstype ${devname})" in
                vfat)
                    :;;
                *)
                    continue;;
            esac
            where_mounted=$(where_is_mounted "${devname}")
            if [ $? -eq 0 ]; then
                if [ -e "${where_mounted}/${pers_fpath}" ]; then
                    mount -o remount,rw ${devname} ${where_mounted} || continue
                    echo $(setup_loop "${where_mounted}/${pers_fpath}" "loop" "/sys/block/loop*")
                fi
            else
                mkdir -p "${cow_backing}"
                mount -w "${devname}" "${cow_backing}" || continue
                if [ -e "${cow_backing}/${pers_fpath}" ]; then
                    echo $(setup_loop "${cow_backing}/${pers_fpath}" "loop" "/sys/block/loop*")
                    return 0
                else
                    umount ${cow_backing}
                fi
            fi
        done
    done
}

root_persistence=

# The default persistence label in 20.04+ is 'writable' but if there
# is no filesystem with that label and there is one with the old
# 'casper-rw' label, we use that instead.
#
# We delay checking for the writable/casper-rw partitions for as long
# as possible as it can take a long time for the block devices to show
# up.
root_persistence_label () {
    if [ -z "$root_persistence" ]; then
        if [ ! -e "/dev/disk/by-label/writable" ] && [ -e "/dev/disk/by-label/casper-rw" ]; then
            root_persistence=casper-rw
        else
            root_persistence=writable
        fi
    fi
    echo $root_persistence
}

# find_or_create_persistent_partition $dev looks for a partition with
# the label $(root_persistence_label) (usually writable). If it does
# not find one, and there is at least 100MiB space on $dev, it creates
# one there.
find_or_create_persistent_partition () {
    local DEVICE newpartno maxend start sectorsize size
    DEVICE=$1

    udevadm settle

    if [ -e "/dev/disk/by-label/$(root_persistence_label)" ]; then
        return
    fi
    newpartno=$(sfdisk -l $DEVICE -q | wc -l)
    maxend=$(sfdisk $DEVICE -l -q -o end | tail -n +2 | sort -n | tail -n1)
    start=$(((maxend + 1 + 0xfff) & ~0xfff))
    sectorsize=$(blockdev --getss $DEVICE)
    size=$(blockdev --getsize64 $DEVICE)
    # Do not bother creating a partition less than 100MiB
    if [ $((size - start*sectorsize)) -lt $((100*1024*1024)) ]; then
        return
    fi
    MBR_STASH=/tmp/mbr_stash_$$
    dd if="$DEVICE" bs=1 skip=462 count=48 of="$MBR_STASH"
    echo "start=$start" | sfdisk --no-reread -q $DEVICE -a || return

    # If our partition table is GPT, we have a protective MBR; it is important
    # for some BIOSes that we preserve entries 2-4 in this MBR.  See
    # https://bugs.launchpad.net/ubuntu/+source/casper/+bug/1977644 and the
    # very long https://bugs.launchpad.net/ubuntu/+source/casper/+bug/1922342.
    if sfdisk -d $DEVICE | grep -q 'label: gpt'; then
        dd if="$MBR_STASH" bs=1 seek=462 conv=notrunc count=48 of="$DEVICE"
    fi
    for d in ${DEVICE}$newpartno ${DEVICE}p$newpartno ${DEVICE}-part$newpartno; do
        if [ -e $d ]; then
            mkfs.ext4 -q -L "$(root_persistence_label)" -F $d
            break
        fi
    done
    udevadm trigger
    udevadm settle
}

find_files()
# return the first of $filenames found on vfat and ext2 devices
# FIXME: merge with above function
{
    filenames="${1}"
    for sysblock in $(echo /sys/block/* | tr ' ' '\n' | grep -v loop); do
        for dev in $(subdevices "${sysblock}"); do
            devname=$(sys2dev "${dev}")
            case "$(get_fstype ${devname})" in
                vfat|ext2)
                    :;;
                *)
                    continue;;
            esac
            doumount=
            mntpoint=$(where_is_mounted "${devname}")
            if [ $? -ne 0 ]; then
                doumount=1
                mntpoint="/snap-backing"
                mkdir -p "${mntpoint}"
                mount -o ro "${devname}" "${mntpoint}"
            fi
            for filename in ${filenames}; do
                if [ -e "${mntpoint}/${filename}" ]; then
                    echo "${devname} ${mntpoint} ${filename}"
                    return 0
                fi
            done
            if [ -n "${doumount}" ]; then
                umount "${mntpoint}"
            fi
        done
    done
}

# Helpers originally from lupin-helpers
is_supported_fs(){
    [ -z "${1}" ] && return 1
    case ${1} in
        ext2|ext3|ext4|xfs|jfs|reiserfs|vfat|ntfs|iso9660|btrfs|udf)
            return 0
            ;;
    esac
    return 1
}

wait_for_devs(){
    if [ -e /dev/.initramfs/lupin-waited-for-devs ]; then
        return
    fi
    [ "$quiet" != "y" ] && log_begin_msg "...waiting for devs..."
    udevadm trigger --subsystem-match=block
    udevadm settle
    #TBD, modprobe on demand?
    modprobe ext3
    modprobe ext4
    modprobe reiserfs
    modprobe xfs
    modprobe jfs
    modprobe vfat
    modprobe fuse
    [ "$quiet" != "y" ] && log_end_msg "...devs loaded..."
    touch /dev/.initramfs/lupin-waited-for-devs
}

find_path()
{
    local path="${1}"
    # must match find_path_cleanup
    local default_mountpoint="${2:-/tmpmountpoint}"
    local mountoptions="${3:-ro}"
    local mountpoint=
    local dev devname devfstype
    local trial_number
    FOUNDDEV=
    FOUNDPATH=
    [ -z "${path}" ] && return 1
    wait_for_devs
    mkdir -p "${default_mountpoint}"
    for trial_number in 1 2 3; do
        [ $trial_number -gt 1 ] && sleep 3
        for sysblock in $(echo /sys/block/* | tr ' ' '\n' | grep -v /ram | grep -v /loop | grep -v /fd); do
            for dev in $(subdevices "${sysblock}"); do
                devname=$(sys2dev "${dev}")
                devfstype="$(get_fstype ${devname})"
                if is_supported_fs "${devfstype}" ; then
                                    #if device is already mounted, do not remount
                    if grep -q "^${devname} " /proc/mounts; then
                        mountpoint=$(grep "^${devname} " /proc/mounts|cut -d ' ' -f 2)
                        unmount=false
                    else
                        mountpoint="${default_mountpoint}"
                        try_mount "$devname" "$mountpoint" "$mountoptions" || continue
                        unmount=true
                    fi
                    if [ -e "${mountpoint}${path}" ]; then
                        FOUNDDEV="${devname}"
                        FOUNDPATH="${mountpoint}${path}"
                        return 0
                    fi
                    [ "${unmount}" = "true" ] && umount ${mountpoint} 2> /dev/null || true
                fi
            done
        done
    done
    return 1
}


