diff --git a/src/host/rootfs/usr/local/bin/netvm b/src/host/rootfs/usr/local/bin/netvm index 9936be7..4f3da67 100755 --- a/src/host/rootfs/usr/local/bin/netvm +++ b/src/host/rootfs/usr/local/bin/netvm @@ -55,38 +55,70 @@ qemu_ga() { fi } -function start(){ - [ ! -f "${LOCKFILE}" ] || { echo "Error: Netvm already running"; exit 1; } - echo "Starting netvm"; - qemu-system-x86_64 -m 512M \ - --machine q35 \ - -nographic \ - -serial none \ - -monitor none \ - -net none \ - -cdrom /guest.img \ - -boot order=d \ - -chardev socket,path=/var/run/netvm_qga.sock,server=on,wait=off,id=qga0 \ - -device virtio-serial \ - -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 & - echo $! > "${LOCKFILE}" -} - -function stop(){ - pkill -F "${LOCKFILE}" && rm "${LOCKFILE}" -} - -function status(){ - qemu_ga guest-get-host-name "{}" | jq -r '."host-name"' - pid=$(qemu_ga guest-exec '{"path": "uptime", "capture-output": true}' | jq -r '.pid') - out=$(qemu_ga guest-exec-status "$(jq -n --argjson pid "$pid" '{pid: $pid }')" \ - | jq -r '."out-data"' \ - | base64 -d \ - ) - echo $out +function cmd_start(){ + [ ! -f "${LOCKFILE}" ] || { echo "Error: Netvm already running"; exit 1; } + echo "Starting netvm"; + qemu-system-x86_64 -m 512M \ + --machine q35 \ + -nographic \ + -serial none \ + -monitor none \ + -net none \ + -cdrom /guest.img \ + -boot order=d \ + -chardev socket,path=/var/run/netvm_qga.sock,server=on,wait=off,id=qga0 \ + -device virtio-serial \ + -device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 & + echo $! > "${LOCKFILE}" } -function run(){ +function cmd_stop(){ + pkill -F "${LOCKFILE}" && rm "${LOCKFILE}" +} + +function cmd_status(){ + qemu_ga guest-get-host-name "{}" | jq -r '."host-name"' + pid=$(qemu_ga guest-exec '{"path": "uptime", "capture-output": true}' | jq -r '.pid') + out=$(qemu_ga guest-exec-status "$(jq -n --argjson pid "$pid" '{pid: $pid }')" \ + | jq -r '."out-data"' \ + | base64 -d \ + ) + echo $out +} + +function cmd_push(){ + local source="${COMMAND[1]}" + local dest="${COMMAND[2]}" + fo_request=$(jq -n --arg dest "$dest" '{"path": $dest, "mode": "w" }') + handle=$(qemu_ga guest-file-open "$fo_request") + bufb64=$(base64 "$source") + count=$(cat "$source" | wc -c) + fw_request=$(jq -n \ + --argjson handle $handle \ + --argjson count $count \ + --arg bufb64 "$bufb64" \ + '{handle: $handle, "buf-b64": $bufb64, count: $count }' \ + ) + qemu_ga guest-file-write "$fw_request" + fh_request=$(jq -n --argjson handle $handle '{handle: $handle}' ) + qemu_ga guest-file-flush "$fh_request" + qemu_ga guest-file-close "$fh_request" +} + +function cmd_pull(){ + local source="${COMMAND[1]}" + local dest="${COMMAND[2]}" + fo_request=$(jq -n --arg source "$source" '{"path": $source}') + handle=$(qemu_ga guest-file-open "$fo_request") + fr_request=$(jq -n \ + --argjson handle $handle \ + '{handle: $handle, count: 48000000 }' \ + ) + out=$(qemu_ga guest-file-read "$fr_request") + echo $out | jq -r '."buf-b64"' | base64 -d > $dest +} + +function cmd_run(){ [ -z "${COMMAND[1]}" ] && { echo "Error: missing command"; exit 1; } [ -f "${LOCKFILE}" ] || { echo "Error: Netvm is not running"; exit 1; } [ -S "${QGA_SOCKET}" ] || { echo "Error: Netvm QGA socket is missing"; exit 1; } @@ -107,25 +139,48 @@ function run(){ "capture-output": true }' \ ) - - pid=$(qemu_ga guest-exec "$request" | jq -r '.pid') - out=$(qemu_ga guest-exec-status "$(jq -n --argjson pid "$pid" '{pid: $pid }')" \ - | jq -r '."out-data"' \ - ) - sleep 1 - echo $out - echo $out | base64 -d + pid=$(qemu_ga guest-exec "$request" | jq -r '.pid') + local exited=false + until [ "$exited" == "true" ]; do \ + out=$(qemu_ga guest-exec-status "$(jq -n --argjson pid "$pid" '{pid: $pid }')" ) + exited=$(echo $out | jq -r '.exited') + if $exited && jq -r 'has("out-data")' >/dev/null < <(echo $out); then + echo "$out" | jq -r '."out-data"' | base64 -d + break + fi + sleep 1 + done } -function help(){ - echo "Valid operations: start, stop, run, help" +cmd_usage() { + cat <<-_EOF + netvm + + Control network vm headlessly via QMP protocol + + Usage: + netvm start + Start headless network vm in the background + netvm stop + Stop headless network vm + netvm status + Get hostname and uptime from running network vm + netvm push + Push a local file to the network VM + netvm pull + Pull a file from the network VM + netvm run "" + Run a command in network vm and get stdout + _EOF } -case "${COMMAND[0]}" in - status) status ;; - start) start ;; - stop) stop ;; - run) run ;; - help) help ;; - *) help ;; +case "$1" in + status) shift; cmd_status $@ ;; + start) shift; cmd_start $@ ;; + stop) shift; cmd_stop $@ ;; + push) shift; cmd_push $@ ;; + pull) shift; cmd_pull $@ ;; + run) shift; cmd_run $@ ;; + help) shift; cmd_usage $@ ;; + *) cmd_usage $@ ;; esac