#!/bin/bash

# internal parameter, need to save current directory when restart script with root permissions
if [ "$1" == "--workdir" ]; then
    cd "$2" || exit ${LINENO}
    shift 2
    SUDO="sudo "
fi

# internal parameter, need to save config name when restart script with root permissions
if [ "$1" == "--qemoocfg" ]; then
    QEMOOCFG=$2
    shift 2
fi

quote(){
    echo "$*"  |sed -e 's:^:":' -e 's:$:":'
}

QEMOO="$(realpath $0)"
ARGS="$(while [ "$1" ] ; do quote "$1" ; echo -n " "; shift ; done )" # keep qemoo agruments to use with pkexec

PATH="/sbin:/usr/sbin:$PATH"
ACTION='run'    # what to do
IMG='none'      # name of image to boot
QCOW2='none'    # name for qcow2 image to install
SINGLE='none'   # name for standalone cantainer
SIZE='20'       # size for qcow2 image to install
RAM='auto'      # ram for machine
QEMUADD=''      # additional parameters for qemu
EFI_FIRMWARE="/usr/share/OVMF/OVMF_CODE.fd"
PREFIX='_qemoo' # prefix for generating qcow2 file name
SHARE='./'      # share dir
FIRSTPORT='6001'
QEMOOADD=""
CLEANUP_FILE="/tmp/qemoo_cleanup.$$"
ACTION="Start"

METADATA_SIZE=${METADATA_SIZE:-104857600} #100M
INTERNAL_SCRIPT_SIZE=${INTERNAL_SCRIPT_SIZE:-32768} # 32KB
QCOW2_SIZE=${QCOW2_SIZE:-$((SIZE * 1024 * 1024 * 1024))}
SELF_CONTAINER=${SELF_CONTAINER:-0}

### not for config
LIMG='none'
VERBOSE='no'
DAEMON=''
EFI=''
INSTALL=''
EXT_DEVS=''
AMPER=''
SEPCFG='yes'
CONF=''

action(){
    [ "$VERBOSE" == 'yes' ] && echo "==> $@" 1>&2
    DOING="$@"
}

vecho(){
    [ "$VERBOSE" == 'yes' ] && echo "==> $@" 1>&2
}

echo_exit(){
    echo "$1" 1>&2
    if [ -n $2 ] ; then
        echo "Action: $DOING, exit code: $2"
        exit $2
    fi
}

load_conf() {
    [ "$QEMOOCFG" ] || QEMOOCFG="/etc/qemoo.cfg"
    QEMOOCFG="$(realpath "$QEMOOCFG" 2>/dev/null || echo "$QEMOOCFG")"

    if [ -f "$QEMOOCFG" ]; then
        vecho "Using: $QEMOOCFG"
        source "$QEMOOCFG"
    fi
}

validate_environment() {
    QEMU="qemu-system-$(arch)"
    if ! command -V "$QEMU" >/dev/null; then
        echo_exit "$QEMU command not found" ${LINENO}
    fi

    if [[ "$ACTION" == "install"* ]] && ! command -V qemu-img >/dev/null; then
        echo_exit "qemu-img command not found" ${LINENO}
    fi

    if [ -n "$EFI" ] && [ ! -f "$EFI_FIRMWARE" ]; then
        echo_exit "$EFI_FIRMWARE not found" ${LINENO}
    fi

    if [ -n "$TPM2" ] && ! command -V swtpm >/dev/null; then
        echo_exit "swtpm command not found" ${LINENO}
    fi

    if [[ "$ACTION" == "install"* ]] && ! command -V mkfs.ext4 >/dev/null; then
        echo_exit "mkfs.ext4 command not found" ${LINENO}
    fi

    if [ -n "$SPICE" ] && ! command -V ss >/dev/null && ! command -V netstat >/dev/null; then
        echo_exit "ss or netstat command not found (required for port checking)" ${LINENO}
    fi

    for cmd in grep sed awk cut dd stat realpath basename dirname mount losetup ip; do
        if ! command -V "$cmd" >/dev/null; then
            echo_exit "$cmd command not found" ${LINENO}
        fi
    done

    vecho "Environment validation passed" 1>&2
}

cleanup_add() {
    eval echo "'$1'" >> "$CLEANUP_FILE"
}

cleanup_all() {
    if [ -f "$CLEANUP_FILE" ]; then
        [ $VERBOSE == 'yes' ] && sed 's/^/>>> /' "$CLEANUP_FILE"
        source "$CLEANUP_FILE" 2>/dev/null
        rm -f "$CLEANUP_FILE" 2>/dev/null
    fi
}

mkSepCfg(){
    cat  << EOF > "${1}.conf"
ACTION='run'
RAM="$RAM"
ADD="$ADD"
SIZE="$SIZE"
EFI="$LEFI"
TPM2="$TPM2"
PORT="$PORT"
REDIRUSB="$REDIRUSB"
LOSETUP="$LOSETUP"
SPICE="$SPICE"
SHARE="$SHARE"
QEMUADD="$QEMUADD"
EOF
}

getport(){
    start=$1
    while ss -tulpn |grep  -q ':'"$start" ; do
        start=$(( $start + 1 ))
    done
    echo $start
}

run() {
    cmdline="$*"
    if [ "$SHOW" ] ; then
        echo "Qemu cmdline:"
        echo "${SUDO}$cmdline"
    else
        [ $VERBOSE == 'yes' ] && { echo "Qemu cmdline:" ; echo "$cmdline" ; }
        eval "$cmdline"
    fi
}

checkPerms(){
    [ $UID -eq 0 ] && return 0
    granted=$(echo -e "$1\n${2//,/\\n}" | while read a ; do
        [ -z "$a" ] && continue
        if ! [ -w "$a" ] ; then
            echo "no"
            break
        fi
    done)
    [ "$granted" != 'no' ] && return 0
    return 111
}

checkImg(){
    local IMG
    IMG=$1
    if [ "$IMG" == 'none' ] ; then
        echo "No image name" 1>&2
        echo 'error'
        return
    elif ! [ -e "$IMG" ] ; then
        echo "$IMG - not exists" 1>&2
        echo 'error'
        return
    elif echo "$IMG" |grep -q '^/dev/\(cdrom\|sr[0-9]\+\)'; then
        echo "iso"
        return 0
    elif echo "$IMG" |grep -q '^/dev/.\+'; then
        if grep -q "$IMG" /proc/mounts; then
            echo "$IMG is mounted" 1>&2
            echo 'error'
            return
        elif [ -b "$IMG" ]; then
            echo "blockdev"
            return 0
        else
            echo "$IMG - is not block device" 1>&2
            echo 'error'
            return
        fi
    elif  file "$IMG" | grep -q "ISO 9660"; then
        echo "iso"
        return 0
    else
        echo "virt"
        return 0
    fi
}

mk_loop() {
    local image=$1
    local image_loop=$(losetup -f)
    losetup -o $((METADATA_SIZE + INTERNAL_SCRIPT_SIZE)) --sizelimit $(($SIZE * 1024 * 1024 * 1024)) $image_loop "$image"
    [ -b $image_loop ] && echo $image_loop
    cleanup_add "losetup -d $image_loop"
}

mount_meta() {
    local image="$1"
    metadir=${image}.meta
    mkdir -p "$metadir" 1>&2
    if mount -o loop,offset=$INTERNAL_SCRIPT_SIZE,sizelimit=$METADATA_SIZE -t ext4 "$image" "$metadir" ; then
        cleanup_add "umount $metadir"
        cleanup_add "rmdir $metadir"
        echo "$metadir"
    else
        echo "Can't mount with metadata partition" 1>&2
    fi
}

checkFormat(){
    local file="$1"
    set -o pipefail
    local format=$(qemu-img info "$file" 2>/dev/null | grep 'format:' | cut -d' ' -f3)
    set +o pipefail

    # Если qemu-img не смог определить или это raw
    if [ -z "$format" ] || [ "$format" = "raw" ]; then
        # Проверяем наш кастомный формат
        if [ -f "$file" ]; then
            local header=$(dd if="$file" bs=1 count=512 2>/dev/null)
            if [[ "$header" == "#!"* ]] &&
               [[ "$header" == *"QEMOO"* ]] &&
               [[ "$header" == *"SELF_CONTAINER=1"* ]]; then
                echo 'qemoo'
                return
            fi
        fi
    fi
    echo "${format:-unknown}"
}


drive_cmdline(){
    local CDROM=''
    local IMG="$(realpath "$1")"
    local type=$(checkImg "$IMG")
    local IMGFORMAT=$(checkFormat "$IMG")
    [ "$type" == "iso" ] && CDROM=",media=cdrom"
    [ "$type" == 'error' ] && return  1
    if [ "$IMGFORMAT" == 'qemoo' ] ; then
        IMG=$(mk_loop "$IMG")
        IMGFORMAT='qcow2'
    fi
    echo -n "-drive file=$(quote "$IMG"),format=$IMGFORMAT,cache=writeback"
    echo "$CDROM"
}

nameGen(){
    local NAME LAST short_name n basename
    LAST="$(ls -1 ./ | grep -E "${PREFIX}.*qcow2$|${PREFIX}.*qemoo$" | sort -V | tail -n1)"
    NAME="$(basename "$IMG")"
    basename="${NAME%.*}"
    short_name="${basename:0:3}"
    if [ -n "$LAST" ]; then
        n="$(echo "$LAST" | sed "s/${PREFIX}//" | sed 's/_.*\.qcow2//' | sed 's/_.*\.qemoo//')"
        if [[ "$n" =~ ^[0-9]+$ ]]; then
            suffix=$((n + 1))
        fi
    else
        suffix=1
    fi
    echo "${PREFIX}${suffix}_${short_name}.qcow2"
}

tpm2(){
    local IMAGE=$1
    local DIR=$2
    new=$3
    local TPM2DIR
    local pid
    if [[ -z "$new" ]] ; then
        if [ -d "${IMAGE}.tpm2" ] ; then
            TPM2DIR="$(realpath "${IMAGE}.tpm2")"
        elif [ -d "$(realpath ${DIR})/qemoo.tpm2" ] ; then
            TPM2DIR="$(realpath ${DIR})/qemoo.tpm2"
        else
            TPM2DIR="$(realpath "${DIR}/$(basename "$IMAGE").tpm2")"
        fi
    elif [ "$new" == 'single' ] ; then
        TPM2DIR="$(realpath ${DIR})/qemoo.tpm2"
    elif [ "$new" == 'separated' ] ; then
        TPM2DIR="$(realpath "${DIR}/$(basename "$IMAGE").tpm2")"
    fi

    if ! [ $SHOW ] ; then
        mkdir -p "$TPM2DIR"
        swtpm socket --tpmstate dir="${TPM2DIR}" --ctrl type=unixio,path="${TPM2DIR}/swtpm-sock" \
            --log level=20 --tpm2 > "${TPM2DIR}/tpm2.log" 2>&1 &
        pid=$!
        sleep 0.5  #  на всякий случай
        if kill -0 "$pid" 2>/dev/null; then
            cleanup_add "kill $pid"
            echo "${TPM2DIR}"
            return 0
        fi
        return 1
    else
        vecho "TPM2 dir: ${TPM2DIR}"
        echo "${TPM2DIR}"
    fi
}

mkQcow2(){
    if [ -f "$QCOW2" ] ; then
        echo "$QCOW2 already exists" 1>&2
        echo "$QCOW2"
        return 0
    fi
    if run qemu-img create -f qcow2 "${QCOW2}" "${SIZE}G" 1>&2 ; then
        echo "$QCOW2"
    else
        echo 'error'
    fi
}

mkSingle() {
    if [ -f "$SINGLE" ] ; then
        echo "$SINGLE already exists" 1>&2
        echo "$SINGLE"
        return 0
    fi

    vecho "Creating self-executing container: $SINGLE"
    vecho "DATA offset: ${INTERNAL_SCRIPT_SIZE}"

    action SPARSE container creation
    local total_size=$(($INTERNAL_SCRIPT_SIZE + $METADATA_SIZE + $SIZE * 1024 * 1024 * 1024))
    dd if=/dev/zero of="$SINGLE" bs=1 count=0 seek=$total_size 2>/dev/null

    action Concatenate script and a header, set offset values
    local int_script=$(mktemp /tmp/qemoo_XXX)
    cleanup_add "rm -f $int_script"

    cat > "$int_script" << HEADER
#!/bin/bash
# QEMOO Self-executing Container
# magic: QEMOO
QCOW2_OFFSET=$((INTERNAL_SCRIPT_SIZE + METADATA_SIZE))
QCOW2_SIZE=$((SIZE * 1024 * 1024 * 1024))
INTERNAL_SCRIPT_SIZE=$INTERNAL_SCRIPT_SIZE
METADATA_SIZE=$METADATA_SIZE
QCOW2_SIZE=$((SIZE * 1024 * 1024 * 1024))
SELF_CONTAINER=1

# original qemoo
HEADER

    action Adding original qemoo code
    cat "$QEMOO" >> "$int_script"

    # Добавляем поясняющий комментарий
    cat >> "$int_script" << PADDING

# === QEMOO CONTAINER PADDING ===
# This section reserves space for container metadata
# DO NOT MODIFY - container data starts at offset $INTERNAL_SCRIPT_SIZE
exit 0
PADDING

    action "increaze script size to $INTERNAL_SCRIPT_SIZE"
    local current_size=$(stat -c %s "$int_script")
    local padding_needed=$(($INTERNAL_SCRIPT_SIZE - $current_size))

    if [ "$padding_needed" -gt 0 ]; then
        vecho "Adding padding: $padding_needed bytes"
        # Добавляем переносы строк добиваем размер до INTERNAL_SCRIPT_SIZE
        for ((i=0; i<padding_needed; i++)); do
            echo >> "$int_script"
        done
    elif [ "$padding_needed" -lt 0 ]; then
        echo_exit "ERROR: Script too big - $current_size > $INTERNAL_SCRIPT_SIZE" ${LINENO}
    fi

    action "Check internal script size"
    local final_script_size=$(stat -c %s "$int_script")
    if [ "$final_script_size" -ne "$INTERNAL_SCRIPT_SIZE" ]; then
        echo_exit "ERROR: Size mismatch: $final_script_size != $INTERNAL_SCRIPT_SIZE" ${LINENO}
    fi

    action "Write internal script to the sparce container"
    dd if="$int_script" of="$SINGLE" conv=notrunc 2>/dev/null

    action "Creating metadata image"
    local metadata=$(mktemp /tmp/qemoo_XXX)
    cleanup_add "rm -f $metadata"
    dd if=/dev/zero of="$metadata" bs="$METADATA_SIZE" count=1 2>/dev/null
    mkfs.ext4 "$metadata" 1>&2
    dd if="$metadata" of="$SINGLE" bs=1M iflag=fullblock oflag=seek_bytes   seek="$INTERNAL_SCRIPT_SIZE" conv=notrunc 2>/dev/null

    action "Creating qcow2 image"
    local qcow2=$(mktemp /tmp/qemoo_XXX)
    cleanup_add "rm -f $qcow2"
    qemu-img create -f qcow2 "$qcow2" "${SIZE}G" 1>&2
    dd if="$qcow2" of="$SINGLE" bs=1 seek=$((METADATA_SIZE + INTERNAL_SCRIPT_SIZE)) conv=notrunc 2>/dev/null

    # Делаем исполняемым
    chmod +x "$SINGLE"

    # Финальная информация
    local total_size=$(stat -c %s "$SINGLE")
    vecho "Successfully created: $SINGLE"
    vecho "Script section: $final_script_size bytes"
    vecho "Total size: $total_size bytes"
    vecho "Container data starts at: $INTERNAL_SCRIPT_SIZE"
    vecho "File is executable and ready to use"

    # возвращаемое значение
    echo "$SINGLE"
}

checkRam() {
    HOSTRAM="$(( $(grep MemTotal /proc/meminfo |awk '{print $2}') / 1000 ))"
    if [ "$RAM" == 'auto' ] ; then
        RAM="$(( "$HOSTRAM" / 2 ))"
        [ "$RAM" -gt 4272 ] && RAM='4272' # (free -g: total 4G)
        echo "$RAM"
    else
        if expr "$RAM" + 1 >/dev/null 2>&1 ; then
            if [ "$RAM" -lt "$HOSTRAM" ] ; then
                echo "$RAM"
            else
                echo "RAM size: $RAM greater then host RAM" 1>&2
                echo 'error'
            fi
        else
            echo "RAM size: $RAM - is not digit" 1>&2
            echo 'error'
        fi
    fi
}

checkUSB(){
    [ -b "$IMG" ] || return
    # shellcheck source=/dev/null
    source <(udevadm info -q property "$IMG" |grep 'ID_' |sed 's/=\(.*\)/="\1"/')
    if [ "$ID_BUS" = "usb" ]; then
        echo "-usb -device usb-host,vendorid=0x${ID_VENDOR_ID},productid=0x${ID_MODEL_ID}"
    fi
}

extract_container_vars() {
    local file="$1"
    local header=$(dd if="$file" bs=1 count=1024 2>/dev/null)
    for var in INTERNAL_SCRIPT_SIZE METADATA_SIZE QCOW2_SIZE QCOW2_OFFSET SELF_CONTAINER; do
        local value=$(echo "$header" | grep "^${var}=" | head -1 | cut -d= -f2)
        if [ -n "$value" ]; then
            eval "$var=\"$value\""
            vecho "$var=$value"
        fi
    done
}

while [ -n "$1" ] ; do
    case "$1" in
    "-i" | "--install" ) ACTION='install' ;; # install from ISO to a qcow2 instead sipmle run
    "-I" | "--install-single" ) ACTION="install-single" ;; # install to a single container
    "-s" | "--size"    ) shift; SIZE=$1 ;; # <par> size for new qcow2 (only with --install)
    "-q" | "--qcow2"   ) shift; QCOW2="$1" ;; # <par> name for new or existing qcow2 (only with --install)
    "-m" | "--mem"     ) shift; _RAM=$1 ;; # <par> size or RAM to guest machine MB
    "-a" | "--add"     ) shift; _ADD="$1" ;; # <par> devices to be added. List separated by comma
    "-p" | "--port"    ) shift; _PORT="$1" ;; # <par> port for spice connection ( use with -d )
    "-c" | "--config"  ) shift ; CONF="$1" ;; # <par> additional config file, if exists has the highest priority, if not exists will be created with current values
    "-L" | "--ll"   | "--lowlevel" ) REDIRUSB='yes' ;; # lowlevel redirect USB drive to guest machine, useful for tokens, modems, etc.
    "-e" | "--efi"  | "--uefi"     ) _EFI="-bios $EFI_FIRMWARE" ;; # efi instead legacy bios
    "-t" | "--tpm2" | "--tpm"      ) _TPM2='yes' ; _EFI="-bios $EFI_FIRMWARE" ;; # enable tpm2 support (efi only)
    "-l" | "--loop" | "--losetup"  ) _LOSETUP='yes' ;; # use ISO and IMG files attaching as loop device
    "-d" | "--daemon" | "--spice"  ) _SPICE='yes' ;; # run as daemon, to connect via remote-viewer
    "-S" | "--show-cmdline"        ) SHOW='yes' ;; # do not run qemu, show qemu cmdline only
    "-v" | "--verbose" ) VERBOSE='yes' ;; # verbose mode
    "-h" | "--help"    ) # this help
    echo "Usage: $0 /path/to/image <parameters>"
    sed -n '/).*;;\s*#.*/s/).*#/ - /p' "$0" |grep -v cat
    echo -e "\n$0 uses the network bridge qemoobr0 or virbr0 if available."
    echo "virbr0 is available if libvirt is installed and the libvirtd service is running,"
    echo "to use qemoobr0, you need to create it manually and allow it in /etc/qemu/bridge.conf"
    exit;;
    "--" )  shift ; _QEMUADD+=" $*" # <par> additional parameters for qemu in the end of cmdline
            break ;;
    *) IMG="$(realpath "$1")" ;;
    esac
    shift
done

if [ "$SELF_CONTAINER" -eq 1 ] ; then
    IMG="$QEMOO"
fi

FNAME=$(basename $IMG)

load_conf
validate_environment

action Check permissions
if ! checkPerms "$IMG" "$ADD"; then
    if [ "$QEMOO" == "/usr/bin/qemoo" ]; then
        # TODO use `pkexec --keep-cwd` since polkit version 121 to keep initial working directory
        eval exec pkexec "$QEMOO" --workdir "$PWD" --qemoocfg "$QEMOOCFG" $ARGS
    else
        echo_exit "Could not open '$IMG': Permission denied" ${LINENO}
    fi
fi

action Check image format
type="$(checkImg "$IMG")"
[ "$type" == 'error' ] && echo_exit "Unknown type: $type" ${LINENO}
IMGFORMAT="$(checkFormat "$IMG")"
if [ "$IMGFORMAT" == 'qemoo' ] ; then
    [ "$SELF_CONTAINER" -eq 0 ] && extract_container_vars "$IMG"
    vecho "Extract qcow2 from qemoo container:"
    vecho "\"tail -c +${QCOW2_OFFSET} $IMG | dd of=image.qcow2 bs=4M\""
    metadir=$(mount_meta $IMG)
    [ "$SEPCFG" == 'yes' ] && source ${metadir}/qemoo.conf
    LIMG=$(mk_loop $IMG)
    IMGFORMAT=qcow2
fi

action Set special config
# apply separate config
if [ "$SEPCFG" == 'yes' ] && [ -f "${IMG}.conf" ] ; then
    echo "Using: ${IMG}.conf"
    source "${IMG}.conf"
fi

# apply specified config
if [ "$CONF" ] ; then
    if [ -f "$CONF" ] ; then
        echo "Using: $CONF"
        source "$CONF"
    else
        mkSepCfg "$IMG"
    fi
fi

action "Apply command line parameters"
EFI="${_EFI:-$EFI}"
TPM2="${_TPM2:-$TPM2}"
LOSETUP="${_LOSETUP:-$LOSETUP}"
SPICE="${_SPICE:-$SPICE}"
PORT="${_PORT:-$PORT}"
RAM="${_RAM:-$RAM}"
QEMUADD="${_QEMUADD:-$QEMUADD}"
ADD="${_ADD:-$ADD}"


[ -b "$LIMG" ] && IMG="$LIMG"

if [ "$ACTION" == 'install' ] ; then
    action Processing install mode
    [ "$QCOW2" == 'none' ] && QCOW2=$(nameGen)
    if ! [ -w "$(dirname $(realpath "$QCOW2" ))" ] ; then
        eval exec pkexec "$QEMOO" --workdir "$PWD" --qemoocfg "$QEMOOCFG" $ARGS
    fi
    qImg="$( realpath "$(mkQcow2)" )"
    [ "$qImg" == 'error' ] && echo_exit "Create qcow2 - failed" ${LINENO}
    if [ "$EFI" ] ; then
        cp -f /usr/share/OVMF/OVMF_VARS.fd "${qImg}.nvram"
        EFI="-drive if=pflash,format=raw,readonly=on,file='$EFI_FIRMWARE' -drive if=pflash,format=raw,file='${qImg}.nvram'"
        LEFI="-drive if=pflash,format=raw,readonly=on,file='$EFI_FIRMWARE' -drive if=pflash,format=raw,file='\$IMG.nvram'"
    fi
    mkSepCfg "$qImg"
elif [ "$ACTION" == 'install-single' ] ; then
    action Processing install-single mode
    [ "$SINGLE" == 'none' ] && SINGLE=$(nameGen | sed 's/.qcow2$/.qemoo/')
    if ! [ -w "$(dirname $(realpath "$SINGLE" ))" ] ; then
        eval exec pkexec "$QEMOO" --workdir "$PWD" --qemoocfg "$QEMOOCFG" $ARGS
    fi
    qImg="$( realpath "$(mkSingle)" )"
    [ "$qImg" == 'error' ] && echo_exit "Create qemoo container - failed" ${LINENO}
    metadir=$(mount_meta $qImg)
    if [ "$EFI" ] ; then
        cp -f /usr/share/OVMF/OVMF_VARS.fd "${metadir}/nvram"
        EFI="-drive if=pflash,format=raw,readonly=on,file='$EFI_FIRMWARE' -drive if=pflash,format=raw,file='${metadir}/nvram'"
        LEFI="-drive if=pflash,format=raw,readonly=on,file='$EFI_FIRMWARE' -drive if=pflash,format=raw,file='\${metadir}/nvram'"
    fi
    mkSepCfg "${metadir}/qemoo"
    qImg=$(mk_loop $qImg)
fi

if [ "$LOSETUP" ] && [ "$IMGFORMAT" = "raw" ] && echo "$IMG" |grep -qv '^/dev/'; then
    action Procerssing losetup for IMG
    LOOPDEV="$(losetup -f)"
    if losetup "$LOOPDEV" "$IMG" ; then
        IMG="$LOOPDEV"
        cleanup_add "losetup -d $LOOPDEV"
    else
        echo_exit "losetup error" ${LINENO}
    fi
fi

if ! echo "$QEMUADD" |grep -q -e '-audiodev'; then
        # TODO: pipeware
        action Setup audio
        if pgrep --uid "$UID" 'pulseaudio' >/dev/null ; then
            SOUND='-device ich9-intel-hda -device hda-duplex,audiodev=audio0 -audiodev alsa,id=audio0'
        elif pgrep 'pulseaudio'  >/dev/null  ; then
            # for root
            SOUND="-device ich9-intel-hda -device hda-duplex,audiodev=audio0 -audiodev pa,id=audio0,server=$(find  /run/user/ -type d -name 'pulse' 2>/dev/null |head -n1)/native"
        elif aplay -l 2>/dev/null | grep -q 'card [0-9]' ; then
            SOUND='-device ich9-intel-hda -device hda-duplex,audiodev=audio0 -audiodev alsa,id=audio0'
        else
            SOUND=''
        fi
fi

SHARE=$(realpath $SHARE)

vRam=$(checkRam)
[ "$vRam" == 'error' ] && echo_exit "Calculate RAM size for virt machine- failed" ${LINENO}

MAC="0a:$({ LC_MESSAGES=en fdisk -l "$IMG" 2>/dev/null |grep ident |grep -v 00000000 || stat -c%W "$IMG"; } |
    md5sum |sed 's/\(..\)/\1:/g' |cut -c 1-14)"
pgrep -f mac="$MAC" >/dev/null && MAC="0a:$(head -c64 /dev/urandom | md5sum |sed 's/\(..\)/\1:/g' |cut -c 1-14)"

NETDEVTYPE="user"
if ip link show qemoobr0 &>/dev/null; then
    NETDEVTYPE="bridge,br=qemoobr0"
elif ip addr show virbr0 2>/dev/null | grep -q 'inet '; then
    NETDEVTYPE="bridge,br=virbr0"
fi

DRIVEPARS="file=$(quote $IMG),format=$IMGFORMAT,cache=none"

COMMON="$QEMU $EFI $SOUND \
    -cpu host \
    -smp cores=2,threads=1,sockets=1 \
    -machine q35,accel=kvm:tcg,usb=on \
    -m ${vRam}M \
    -overcommit mem-lock=off \
    -vga virtio \
    -device qemu-xhci,id=xhci -device usb-tablet,bus=xhci.0 \
    -rtc base=localtime,clock=host \
    -global kvm-pit.lost_tick_policy=discard \
    -name \"$(basename "$IMG")\" \
    -virtfs local,path='$SHARE',mount_tag=hostdir,security_model=mapped,id=hostdir \
    -netdev $NETDEVTYPE,id=net0 -device virtio-net-pci,netdev=net0,mac='$MAC' \
    -display gtk"

CLIPBOARD="-chardev qemu-vdagent,id=vdagent_channel,name=vdagent,clipboard=on,mouse=on \
    -device virtio-serial-pci \
    -device virtserialport,chardev=vdagent_channel,name=com.redhat.spice.0"


[ "$REDIRUSB" == "yes" ] && usbDev="$(checkUSB)"

if [ "$ACTION" == 'install' ]  || [ "$ACTION" == 'install-single' ] ; then
    INSTALL="-drive file='$qImg',cache=none"
fi

case $type in
    'virt' )
        echo "Virtual machine image: $FNAME"
        cmdline="
        -boot c \
        -drive $DRIVEPARS"
        ;;
    'blockdev')
        echo "Block device: $FNAME"
        cmdline="
        -boot c \
        ${usbDev:--drive $DRIVEPARS}"
        ;;
    'iso')
        echo "ISO: $FNAME"
        cmdline="
        -boot d \
        -drive $DRIVEPARS,media=cdrom"
        ;;
    *)
        echo_exit "unknown type: $type" ${LINENO}
esac

if [ "$TPM2" ] ; then
    action Setup tpm2
    if [ $ACTION == 'install' ] ; then
        TPM2DIR=$(tpm2 "$qImg" ./ separated)
    elif [ $ACTION == 'install-single' ] ; then
        TPM2DIR=$(tpm2 "$qImg" $metadir single)
    else
        TPM2DIR=$(tpm2 "$IMG" /tmp)
    fi
    if [ "$TPM2DIR" ] ; then
        TPM2ADD="-chardev socket,id=chrtpm,path='${TPM2DIR}/swtpm-sock' \
        -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0"
    fi
fi

if [ "$ADD" ] ; then
    EXT_DEVS=$(echo -e "${ADD//,/\\n}" | while read IMG  ; do
    drive_cmdline "$IMG"
    done)
fi

if [ "$SPICE" ] ; then
    action Setup SPICE
    AMPER='&'
    FREEPORT="$PORT"
    [ -z "$PORT" ] && FREEPORT="$(getport $FIRSTPORT)"
    DAEMON="-device virtio-serial -chardev spicevmc,id=vdagent,debug=0,name=vdagent \
            -device virtserialport,chardev=vdagent,name=com.redhat.spice.0 \
            -spice port=$FREEPORT,disable-ticketing=on -vga virtio"
    CLIPBOARD=''
fi

if [ "$SHOW" != 'yes' ] ; then
echo "Host share:
    $(realpath "$SHARE")
Linux guest mount command example:
    mkdir /mnt/hostdir
    mount -t 9p  hostdir /mnt/hostdir
    (add '-o trans=virtio,msize=100000000' if mount command ends with errors)
"
[ "$SPICE" ] && echo "PORT = $FREEPORT"
fi

trap cleanup_all EXIT

run $COMMON $cmdline $INSTALL $EXT_DEVS $DAEMON $CLIPBOARD $QEMUADD $TPM2ADD $AMPER
ppid=$!

# wait for qemu, necessary for cleanup
if [ "$SPICE" ] && [ "$SHOW" != 'yes' ] ; then
    echo "PID = $ppid"
    while kill -0 "$ppid" 2>/dev/null; do
        sleep 1
    done
    vecho "QEMU process $ppid has terminated"
    exit 0
fi
