#!/bin/bash

shopt -s nullglob


export LC_CTYPE=en_US.UTF-8
NAME=${DEVNAME#/dev/}
DESC="`echo "${ID_MODEL} (${ID_FS_LABEL})" | iconv -f utf8 -t ascii//TRANSLIT`"
SIZE=$[ $(cat /sys/$DEVPATH/size) * 512 ]
MODE=w
QDB_KEY="/qubes-block-devices/$NAME"

xs_remove() {
    if is_attached /sys$DEVPATH; then
        return 0
    fi

    if qubesdb-read -q "$QDB_KEY/desc" >/dev/null; then
        qubesdb-rm "$QDB_KEY/"
        qubesdb-write /qubes-block-devices ''
    fi
}

is_used() {
    local sys_devpath=$1
    local devname=$(grep ^DEVNAME= $sys_devpath/uevent | cut -f 2 -d =)
    # mounted; or enabled swap
    if lsblk -dnr -o MOUNTPOINT "/dev/$devname" | grep -q .; then
        return 0
    fi
    # part of other device-mapper
    if [ -n "`ls -A $sys_devpath/holders 2> /dev/null`" ]; then
        return 0
    fi
    # open device-mapper device
    if [ -f "$sys_devpath/dm/name" ] && \
            /sbin/dmsetup info "$(cat $sys_devpath/dm/name)" |\
                grep -q "^Open count:.*[1-9]"; then
        return 0
    fi
    return 1
}

refresh_another() {
    # launch this script for other device
    local devpath=$1
    local launch_env=$(udevadm info -q all -x -p "$devpath" \
            | grep ^E: | cut -d ' ' -f 2- | tr ' ' ':')
    env -i PATH=$PATH $launch_env $0
}

is_attached() {
    dev_hex=$(stat -c %t:%T /dev/$(basename $1))
    if [ -z "$dev_hex" -o "$dev_hex" = "0:0" ]; then
        return 1
    fi
    # looking at sysfs is much faster than looking at xenstore
    # this code requires no subprocesses and doesn't hit argument length limitations
    for i in /sys/bus/xen-backend/drivers/vbd/vbd-*/physical_device; do
        read i_dev_hex < "$i"
        if test "$i_dev_hex" == "$dev_hex"; then
            return 0
        fi
    done
    return 1
}

# update info about parent devices, if any:
if [ -f /sys$DEVPATH/partition ]; then
    parent=$(dirname $(readlink -f /sys$DEVPATH))
    refresh_another /$(realpath --relative-to=/sys $parent)
    # if parent device is already attached, skip its partitions
    if is_attached $parent; then
        xs_remove
        exit 0
    fi
fi

# and underlying devices of device-mapper (if any)
for dev in /sys$DEVPATH/slaves/*; do
    refresh_another /$(realpath --relative-to=/sys $dev)
done

# cache slave devices for remove event
if [ -n "$DM_NAME" ]; then
    ls -1 /sys$DEVPATH/slaves/ > /run/qubes/block-slave-cache-$NAME
fi

# then take care of this device:

# udev rules already excluded this device:

if [ -z "$QUBES_EXPORT_BLOCK_DEVICE" ] && [ "$DM_UDEV_DISABLE_DISK_RULES_FLAG" = "1" ]; then
    xs_remove
    exit 0
fi

# device itself is already used
if is_used /sys$DEVPATH; then
    xs_remove
    exit 0
fi

# or one of its partitions is used
# or already attached (prevent attaching both device and its partition(s) at
# the same time)
for part in /sys$DEVPATH/$NAME*; do
    if [ -d $part ]; then
        if is_used $part || is_attached $part; then
            xs_remove
            exit 0
        fi
    fi
done

# or "empty" loop device
if [ "$MAJOR" -eq 7 -a ! -d /sys/$DEVPATH/loop ]; then
    xs_remove
    exit 0
fi

# or unconnected Network Block Device
if [ "$MAJOR" -eq 43 -a ! -e /sys/$DEVPATH/pid ]; then
    xs_remove
    exit 0
fi

# ... and loop devices from excluded directories
if [[ "$NAME" = 'loop'* ]]; then
    backing_file=$(cat /sys/block/${NAME}/loop/backing_file)
    if [ -n "$backing_file" ]; then
        dir_to_check=$(dirname "$backing_file")
        while [ "$dir_to_check" != "/" -a "$dir_to_check" != "." ]; do
            if [ -e "$dir_to_check/.qubes-exclude-block-devices" ]; then
                xs_remove
                exit 0
            fi
            dir_to_check=$(dirname "$dir_to_check")
        done
    fi
fi

# Check if device is read-only
if [ "`cat /sys/$DEVPATH/ro`" -eq 1 ]; then
    MODE=r
fi

# Special case for CD
if [ "$ID_TYPE" = "cd" ]; then
    if [ "$ID_CDROM_MEDIA" != "1" ]; then
        # Hide empty cdrom drive
        xs_remove
        exit 0
    fi
    MODE=r
fi

# Special description for loop devices
if [ -d /sys/$DEVPATH/loop ]; then
    DESC=$(cat /sys/$DEVPATH/loop/backing_file)
fi
# and for device-mapper
if [ -n "$DM_NAME" ]; then
    DESC="$DM_NAME"
fi

if [ -f /sys$DEVPATH/partition ]; then
    parent=$(basename "$(dirname "$DEVPATH")")
elif echo "$DEVPATH" | grep -q '/host'; then
    parent=$(basename "$(sed 's|/host.*$||' <<< "$DEVPATH")")
fi

# The last one is meant to trigger watches
qubesdb-write \
    "$QDB_KEY/desc" "$DESC" \
    "$QDB_KEY/size" "$SIZE" \
    "$QDB_KEY/mode" "$MODE" \
    "$QDB_KEY/parent" "$parent" \
    /qubes-block-devices ''

# Make sure that block backend is loaded
/sbin/modprobe xen-blkback 2> /dev/null || /sbin/modprobe blkbk
