#!/usr/bin/env bash QGA_SOCKET="/tmp/qga.sock" setup() { if [ ! -S "$QGA_SOCKET" ]; then echo "QGA socket not found at $QGA_SOCKET" exit 1 fi } teardown() { true } qga_send() { local json_cmd="$1" local tmp_out tmp_out=$(mktemp) { echo "$json_cmd" sleep 0.1 } | socat - UNIX-CONNECT:"$QGA_SOCKET" > "$tmp_out" cat "$tmp_out" rm -f "$tmp_out" } qga_guest_exec() { local cmd="$1" shift local args_json="[]" if [[ "$#" -gt 0 ]]; then args_json=$(printf '%s\n' "$@" | jq -R . | jq -s .) fi local json json=$(jq -n \ --arg path "$cmd" \ --argjson args "$args_json" \ '{ execute: "guest-exec", arguments: { path: $path, arg: $args, "capture-output": true } }') local response response=$(qga_send "$json") echo "guest-exec JSON sent:" >&2 echo "$json" >&2 echo "guest-exec response:" >&2 echo "$response" >&2 local first_line first_line=$(echo "$response" | head -n1) local pid pid=$(echo "$first_line" | jq -r '.return.pid // empty') if [[ ! "$pid" =~ ^[0-9]+$ ]]; then echo "ERROR: Failed to parse valid PID from response." >&2 return 1 fi echo "$pid" } qga_guest_exec_status() { local pid="$1" local json json=$(jq -n --argjson pid "$pid" \ '{execute: "guest-exec-status", arguments: {pid: $pid}}') qga_send "$json" } qga_decode_output() { local b64_data="$1" echo "$b64_data" | base64 -d } qga_run_cmd_and_get_output() { local pid pid=$(qga_guest_exec "$@") if [[ -z "$pid" ]]; then echo "Failed to get PID from guest-exec" >&2 return 1 fi local status_resp local exited="false" local retries=5 while [[ "$exited" != "true" && $retries -gt 0 ]]; do sleep 0.5 status_resp=$(qga_guest_exec_status "$pid") exited=$(echo "$status_resp" | jq -r '.return.exited // empty') retries=$((retries - 1)) done if [[ "$exited" != "true" ]]; then echo "Command did not finish after retries." >&2 return 1 fi local out_data out_data=$(echo "$status_resp" | jq -r '.return."out-data" // empty') local err_data err_data=$(echo "$status_resp" | jq -r '.return."err-data" // empty') if [[ -n "$err_data" ]]; then echo "STDERR:" >&2 qga_decode_output "$err_data" >&2 fi qga_decode_output "$out_data" } qga_read_guest_file() { local file_path="$1" local pid pid=$(qga_guest_exec "/bin/cat" "$file_path") if [[ -z "$pid" ]]; then echo "Failed to get PID to read file" >&2 return 1 fi local status_resp local exited="false" local retries=5 while [[ "$exited" != "true" && $retries -gt 0 ]]; do sleep 0.5 status_resp=$(qga_guest_exec_status "$pid") exited=$(echo "$status_resp" | jq -r '.return.exited // empty') retries=$((retries - 1)) done if [[ "$exited" != "true" ]]; then echo "Failed to read file or command did not finish execution." >&2 return 1 fi local out_data out_data=$(echo "$status_resp" | jq -r '.return."out-data"') local decoded_data decoded_data=$(qga_decode_output "$out_data") if echo "$decoded_data" | base64 --decode &>/dev/null; then echo "$decoded_data" | base64 --decode else echo "$decoded_data" fi }