#!/usr/bin/env bash
: '
    This script checks if an image is compatible with Cloud Hypervisor.
    At first, it detects the image type(raw or qcow2),
    partition type whether it is DOS or GPT.
    Then it mounts the image and checks if VIRTIO Configs
    are enabled in the kernel config. In the end, it provides
    a message about the compatibility of the image.
'

usage="$(basename "$0") [-h] -f -w -- program to check Cloud Hypervisor compatible image

where:
    -h  show this help text
    -f  image file location
    -w  directory to be used for temporary files"

function check_command {
    if ! command -v "$1" &>/dev/null; then
        echo "Command $1 could not be found"
        exit 1
    fi
}

function check_if_root {
    if [ "$EUID" -ne 0 ]; then
        echo "Please run as root"
        exit 1
    fi

}

check_if_root
working_dir=""
while getopts ':hf:w:' option; do
    case "$option" in
    h)
        echo "$usage"
        exit
        ;;
    f)
        file_name=$OPTARG
        ;;
    w)
        working_dir=$OPTARG
        ;;
    :)
        printf "missing argument for -%s\n" "$OPTARG" >&2
        echo "$usage" >&2
        exit 1
        ;;
    \?)
        printf "illegal option: -%s\n" "$OPTARG" >&2
        echo "$usage" >&2
        exit 1
        ;;
    esac
done

shift $((OPTIND - 1))

if [ -z "${file_name}" ]; then
    echo "You must provide the image file name"
    exit 1
fi
if [[ ! -f ${file_name} ]]; then
    echo "File ${file_name} does not exist"
    exit 1
fi

file_abs_path=$(readlink -m "${file_name}")
if [[ "${working_dir}" != "" && ! -d "${working_dir}" ]]; then
    echo "Directory ${working_dir} does not exist"
    exit 1
elif [[ "${working_dir}" == "" ]]; then
    working_dir=$(mktemp -d)
    tmp_created=1
else
    working_dir=$(readlink -m "${working_dir}")
fi

filename="${file_name%.*}"
dest_file=${working_dir}/${filename}.raw
image_type=$(qemu-img info "${file_abs_path}" | grep 'file format:' | awk '{ print $3 }')
echo "Image type detected as ${image_type}"

if [[ "${image_type}" == "raw" ]]; then
    dest_file=${file_abs_path}
elif [[ "$image_type" == "qcow2" ]]; then
    if lsmod | grep "nbd" &>/dev/null; then
        echo "Module nbd is loaded!"
    else
        echo "Module nbd is not loaded. Trying to load the module"

        if ! modprobe nbd max_part=8; then
            echo "failed to load nbd module. Exiting"
            exit 1
        fi
    fi
    check_command qemu-img
    dest_file=/dev/nbd0
    qemu-nbd --connect=${dest_file} "${file_abs_path}" --read-only
fi

check_command blkid
#get part info
part_type=$(blkid -o value -s PTTYPE "${dest_file}")

check_command partx
nr_partitions=$(partx -g "${dest_file}" | wc -l)

check_command fdisk
out=$(fdisk -l "${dest_file}" --bytes | grep -i -A "${nr_partitions}" 'Device' | tail -n +2)

IFS='
'
i=0
declare -A lines
for x in $out; do
    lines[$i]=$x
    i=$((i + 1))
done

declare -A partitions
IFS=' '
i=0
ROWS=${#lines[@]}

for line in "${lines[@]}"; do
    j=0
    read -a -r str_arr <<<"$line"
    for val in "${str_arr[@]}"; do
        if [[ "$val" != "*" ]]; then
            partitions[$i, $j]=$val
            j=$((j + 1))
        fi
    done
    i=$((i + 1))
done

COLUMNS=$j
START_ADDRESS_INDEX=1
FILE_SYS_INDEX2=$((COLUMNS - 1))
FILE_SYS_INDEX1=$((COLUMNS - 2))
DEVICE_INDEX=0
# Here we have all the partition info now lets mount and analyze the contents
for ((i = 0; i < ROWS; i++)); do
    if [[ "$part_type" == "gpt" && "${partitions[$i, ${FILE_SYS_INDEX1}]}" == "Linux" && "${partitions[$i, ${FILE_SYS_INDEX2}]}" == "filesystem" ]]; then
        echo "The image has GPT partitions"
        MOUNT_ROW=$i
        break
    elif [[ "$part_type" == "dos" && "${partitions[$i, ${FILE_SYS_INDEX1}]}" == "Linux" && "${partitions[$i, ${FILE_SYS_INDEX2}]}" == "" ]]; then
        echo "The image has DOS partitions"
        MOUNT_ROW=$i
        break
    fi
done

start_address=${partitions[${MOUNT_ROW}, ${START_ADDRESS_INDEX}]}
offset=$((start_address * 512))

MOUNT_DIR=/mnt/clh-img-check/
rm -rf ${MOUNT_DIR}
mkdir ${MOUNT_DIR}
if [[ "${image_type}" == "raw" ]]; then
    mount -o ro,loop,offset=$offset "${dest_file}" ${MOUNT_DIR}
elif [[ "${image_type}" == "qcow2" ]]; then
    mount -o ro "${partitions[${MOUNT_ROW}, ${DEVICE_INDEX}]}" ${MOUNT_DIR}
fi

CONFIG_DIR=${MOUNT_DIR}boot/
if [[ "$part_type" == "dos" ]]; then
    CONFIG_DIR=${MOUNT_DIR}
fi

#check VIRTIO
HAS_VIRTIO=1
for conf_file in "${CONFIG_DIR}"config*; do
    out=$(grep -cE "CONFIG_VIRTIO=y|CONFIG_VIRTIO_BLK=y|CONFIG_VIRTIO_BLK=m" "${conf_file}")
    if [[ "$out" != "2" ]]; then
        echo "VIRTIO not found"
        HAS_VIRTIO=0
    fi
done

#clean up
umount ${MOUNT_DIR}

if [[ "${tmp_created}" == "1" ]]; then
    rm -rf "${working_dir}"
fi

if [[ "${image_type}" == "qcow2" ]]; then
    qemu-nbd --disconnect "${dest_file}" >/dev/null
fi

result=""
if [[ "${part_type}" == "dos" ]]; then
    result="dos mode not supported"
fi
if [[ "${HAS_VIRTIO}" == "0" ]]; then
    if [[ "$result" != "" ]]; then
        result="${result},"
    fi
    result="$result VirtIO module not found in the image"
fi
if [[ "$result" == "" ]]; then
    echo "No incompatibilities found"
else
    echo "$result"
fi
