From 25b209593eba866d1448272aba5cffdc71539da0 Mon Sep 17 00:00:00 2001 From: Andrea Righi Date: Wed, 31 Jan 2024 09:36:16 +0100 Subject: [PATCH] virtme-ng: channel the return code of a command to the host Send the return code of the command executed inside the guest to the host. This allows to run commands inside a guest and give users the impression that they are running on the host. Examples: $ vng -r -- true && echo OK || echo ERROR: $? OK $ vng -r -- false && echo OK || echo ERROR: $? ERROR: 1 $ vng -r -- exit 22 && echo OK || echo ERROR: $? ERROR: 22 This feature is particularly useful for automation/CI, since it easily allows to check the return code of a script executed inside virtme-ng as if it was running directly on the host. This fixes issue #48. Signed-off-by: Andrea Righi --- virtme/commands/run.py | 34 ++++++++++++++++++++++++++++++++-- virtme/guest/virtme-init | 13 ++++++++++++- virtme_ng/run.py | 12 ++++++++---- virtme_ng_init | 2 +- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/virtme/commands/run.py b/virtme/commands/run.py index 6cbdeed..9ed64b6 100644 --- a/virtme/commands/run.py +++ b/virtme/commands/run.py @@ -1054,7 +1054,24 @@ def do_it() -> int: ] ) - def do_script(shellcmd: str, show_boot_console=False) -> None: + ret_path = None + + def cleanup_script_retcode(): + os.unlink(ret_path) + + def fetch_script_retcode(): + if ret_path is None: + return None + try: + with open(ret_path, 'r', encoding="utf-8") as file: + number_str = file.read().strip() + if number_str.isdigit(): + return int(number_str) + return None + except FileNotFoundError: + return None + + def do_script(shellcmd: str, ret_path=None, show_boot_console=False) -> None: if args.graphics is None: # Turn off default I/O qemuargs.extend(arch.qemu_nodisplay_args()) @@ -1106,6 +1123,13 @@ def do_script(shellcmd: str, show_boot_console=False) -> None: ["-device", "virtserialport,name=virtme.dev_stderr,chardev=dev_stderr"] ) + # Create a virtio serial device to channel the retcode of the script + # executed in the guest to the host. + if ret_path is not None: + qemuargs.extend(["-chardev", f"file,id=ret,path={ret_path}"]) + qemuargs.extend(["-device", arch.virtio_dev_type("serial")]) + qemuargs.extend(["-device", "virtserialport,name=virtme.ret,chardev=ret"]) + # Scripts shouldn't reboot qemuargs.extend(["-no-reboot"]) @@ -1141,7 +1165,9 @@ def do_script(shellcmd: str, show_boot_console=False) -> None: args.script_sh = args.graphics if args.script_sh is not None: - do_script(args.script_sh, show_boot_console=args.show_boot_console) + _, ret_path = tempfile.mkstemp(prefix="virtme_ret") + atexit.register(cleanup_script_retcode) + do_script(args.script_sh, ret_path=ret_path, show_boot_console=args.show_boot_console) if args.script_exec is not None: do_script( @@ -1305,7 +1331,11 @@ def do_script(shellcmd: str, show_boot_console=False) -> None: if pid: try: pid, status = os.waitpid(pid, 0) + ret = fetch_script_retcode() + if ret is not None: + return ret return status + except KeyboardInterrupt: sys.stderr.write("Interrupted.") sys.exit(1) diff --git a/virtme/guest/virtme-init b/virtme/guest/virtme-init index ee92a72..c9c8adb 100755 --- a/virtme/guest/virtme-init +++ b/virtme/guest/virtme-init @@ -260,6 +260,11 @@ if [[ -n "${user_cmd}" ]]; then /dev/virtio-ports/virtme.stderr \ /dev/virtio-ports/virtme.dev_stdout \ /dev/virtio-ports/virtme.dev_stderr + + if [ -e /dev/virtio-ports/virtme.ret ]; then + chown ${virtme_user} \ + /dev/virtio-ports/virtme.ret + fi fi # Fix /dev/stdout and /dev/stderr. @@ -289,7 +294,13 @@ if [[ -n "${user_cmd}" ]]; then else setsid bash /tmp/.virtme-script /dev/virtio-ports/virtme.stdout 2>/dev/virtio-ports/virtme.stderr fi - log "script returned $?" + ret=$? + log "script returned {$ret}" + + # Channel exit code to the host. + if [ -e /dev/virtio-ports/virtme.ret ]; then + echo ${ret} > /dev/virtio-ports/virtme.ret + fi # Hmm. We should expose the return value somehow. sync diff --git a/virtme_ng/run.py b/virtme_ng/run.py index 4672b38..fd45688 100644 --- a/virtme_ng/run.py +++ b/virtme_ng/run.py @@ -1102,8 +1102,7 @@ def clean(kern_source, args): def run(kern_source, args): """Run the kernel.""" - kern_source.run(args) - return True + return kern_source.run(args) @spinner_decorator(message="🐞 generating memory dump") @@ -1137,12 +1136,17 @@ def do_it() -> int: if not args.skip_config: config(kern_source, args) if args.kconfig: - return + return 0 make(kern_source, args) else: - run(kern_source, args) + try: + run(kern_source, args) + return 0 + except CalledProcessError as exc: + return exc.returncode except CalledProcessError as exc: raise SilentError() from exc + return 0 def main() -> int: diff --git a/virtme_ng_init b/virtme_ng_init index f3a737c..b8cba09 160000 --- a/virtme_ng_init +++ b/virtme_ng_init @@ -1 +1 @@ -Subproject commit f3a737cf5be7ef00fd46d999cc1cf21c6fbe3465 +Subproject commit b8cba09b3cef230cf80aa63fc8dec24f913809c9