From b335acea5000908199e26eb432b0463774f5707f Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 2 Dec 2024 15:53:39 +0100 Subject: [PATCH 1/3] vsock: connect: generate helper script Directly connecting to the remote shell using socat launching 'bash' remotely doesn't give a nice user experience: env vars like $HOME are not propagated, the terminal size is set to 80x24, the command that is typed is not visible, the directory is not the one picked with --cwd, etc. To give a better user experience, the '--vsock-connect' option is now creating a script that is setting up the missing pieces. This script is executed when the VM is launched with '--vsock' without any arguments. Note that it is required to pass info from the host, when launching '--vsock-connect', to the VM, e.g. for the terminal size. This is done via the script that is executed once connected to the vsock inside the VM. The VM needs access to this script, and the host needs to be able to modify it. Such script is then created in /tmp/virtme-vsock/.sh on both the host and the VM. If the whole filesystem is mounted (default), this file will be accessible from the VM. If not, a mount point will be added to access the parent directory from the VM. Signed-off-by: Matthieu Baerts (NGI0) --- README.md | 18 ------------ virtme/commands/run.py | 59 ++++++++++++++++++++++++++++++++++++---- virtme/guest/virtme-init | 4 +++ virtme_ng/run.py | 6 ++-- virtme_ng_init | 2 +- 5 files changed, 63 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a4a7215..b08a701 100644 --- a/README.md +++ b/README.md @@ -354,24 +354,6 @@ Examples $ vng --vsock-connect ``` - - Connect to a remote shell with proper dimensions, env vars, and using 'Fish': -``` - # Start the vng instance with vsock support: - $ vng --vsock "${PWD}/console.sh" - - # In a separate terminal run the following commands: - $ read -r rows columns <<< "$(stty size)" - $ cat <<-EOF > console.sh - #! /bin/bash - stty rows ${rows} columns ${columns} - cd "\${virtme_chdir}" - HOME=${HOME} - fish # use use zsh, tmux, byobu, screen, etc. - EOF - $ chmod +x console.sh - $ vng --vsock-connect -``` - - Run virtme-ng inside a docker container: ``` $ docker run -it --privileged ubuntu:23.10 /bin/bash diff --git a/virtme/commands/run.py b/virtme/commands/run.py index 0b2cbd1..1bb8239 100644 --- a/virtme/commands/run.py +++ b/virtme/commands/run.py @@ -133,13 +133,16 @@ def make_parser() -> argparse.ArgumentParser: g.add_argument( "--vsock", action="store", - default=None, - help="Enable a VSock to communicate from the host to the device and " - + "execute the specified command.", + nargs="?", + metavar="COMMAND", + const="", + help="Enable a VSock to communicate from the host to the device. " + + "An argument can be optionally specified to start a different command.", ) g.add_argument( "--vsock-cid", action="store", + metavar="CID", type=int, default=3, help="CID for the VSock.", @@ -866,7 +869,27 @@ def is_subpath(path, potential_parent): def do_it() -> int: args = _ARGPARSER.parse_args() + vsock_script_path = os.path.join(tempfile.gettempdir(), "virtme-vsock", + f"{args.vsock_cid}.sh") + if args.vsock_connect: + # Note: we could accept arguments passed to --vsock-connect to run a + # specific command, and return, without requiring an interactive tty. + try: + (cols, rows) = os.get_terminal_size() + except OSError: + cols, rows = (80, 24) + + with open(vsock_script_path, 'w', encoding="utf-8") as file: + print(( + '#! /bin/bash\n' + f'stty rows {rows} cols {cols} iutf8 echo\n' + 'HOME=$(getent passwd ${virtme_user:-root} | cut -d: -f6)\n' + 'cd ${virtme_chdir:+"${virtme_chdir}"}\n' + 'exec su ${virtme_user:-root}' + ), file=file) + os.chmod(vsock_script_path, 0o755) + tty = os.ttyname(sys.stdin.fileno()) command = ['socat', f'file:{tty},raw,echo=0', f'VSOCK-CONNECT:{args.vsock_cid}:1024'] @@ -1409,8 +1432,34 @@ def get_net_mac(index): ] ) - if args.vsock: - kernelargs.extend([f"virtme.vsockexec=`{args.vsock}`"]) + def cleanup_vsock_script(): + os.unlink(vsock_script_path) + + if args.vsock is not None: + if os.path.exists(vsock_script_path): + arg_fail("vsock: '%s' file exists: " % vsock_script_path + + "another VM is running with the same --vsock-cid? " + + "If not, remove this file.") + + # create an empty file that can be populated later on + vsock_script_dir = os.path.dirname(vsock_script_path) + os.makedirs(vsock_script_dir, exist_ok=True) + open(vsock_script_path, 'w', encoding="utf-8").close() + atexit.register(cleanup_vsock_script) + + if args.vsock: + vsock_exec = args.vsock + else: + vsock_exec = vsock_script_path + if args.root != "/": + virtfs_config = VirtFSConfig( + path=vsock_script_dir, + mount_tag="virtme.vsockmount", + ) + export_virtfs(qemu, arch, qemuargs, virtfs_config) + kernelargs.append("virtme_vsockmount=%s" % vsock_script_dir) + + kernelargs.extend([f"virtme.vsockexec=`{vsock_exec}`"]) qemuargs.extend(["-device", "vhost-vsock-pci,guest-cid=%d" % args.vsock_cid]) if args.pwd: diff --git a/virtme/guest/virtme-init b/virtme/guest/virtme-init index 2681ad7..45eeaf2 100755 --- a/virtme/guest/virtme-init +++ b/virtme/guest/virtme-init @@ -277,6 +277,10 @@ fi vsock_exec=$(sed -ne "s/.*virtme.vsockexec=\`\(.*\)\`.*/\1/p" /proc/cmdline) if [[ -n "${vsock_exec}" ]]; then + if [[ -n "${virtme_vsockmount}" ]]; then + mkdir -p "${virtme_vsockmount}" + mount -t 9p -o version=9p2000.L,trans=virtio,access=any "virtme.vsockmount" "${virtme_vsockmount}" + fi socat "VSOCK-LISTEN:1024,reuseaddr,fork" \ "EXEC:\"${vsock_exec}\",pty,stderr,setsid,sigint,sane,echo=0" & fi diff --git a/virtme_ng/run.py b/virtme_ng/run.py index 48744a8..2c5aa7f 100644 --- a/virtme_ng/run.py +++ b/virtme_ng/run.py @@ -362,15 +362,17 @@ def make_parser(): parser.add_argument( "--vsock", action="store", - const="bash -i", nargs="?", + metavar="COMMAND", + const="", help="Enable a VSock to communicate from the host to the device. " - + "An argument can be optionally specified to start a different shell.", + + "An argument can be optionally specified to start a different command.", ) parser.add_argument( "--vsock-cid", action="store", + metavar="CID", type=int, help="CID for the VSock.", ) diff --git a/virtme_ng_init b/virtme_ng_init index c43ba1c..fe8484d 160000 --- a/virtme_ng_init +++ b/virtme_ng_init @@ -1 +1 @@ -Subproject commit c43ba1ca5d91953a723eb1d6d0f7b4b88da327ca +Subproject commit fe8484d502456131e5975c051077da2ff67c5aa3 From 1247e9050fca91a50ebd95f52b5fbf86a69e2b81 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 2 Dec 2024 16:50:26 +0100 Subject: [PATCH 2/3] vsock: connect: pass command to execute It is useful to directly pass a command to launch in the VM, instead of getting a prompt, and then typing commands. It is helpful for automations. Signed-off-by: Matthieu Baerts (NGI0) --- virtme/commands/run.py | 16 ++++++++++------ virtme_ng/run.py | 12 ++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/virtme/commands/run.py b/virtme/commands/run.py index 1bb8239..061b178 100644 --- a/virtme/commands/run.py +++ b/virtme/commands/run.py @@ -149,8 +149,12 @@ def make_parser() -> argparse.ArgumentParser: ) g.add_argument( "--vsock-connect", - action="store_true", - help="Connect to a VM using VSock.", + action="store", + nargs="?", + metavar="COMMAND", + const="", + help="Connect to a VM using VSock. " + + "An argument can be optionally specified to launch this command instead of a prompt.", ) g.add_argument( "--balloon", @@ -872,21 +876,21 @@ def do_it() -> int: vsock_script_path = os.path.join(tempfile.gettempdir(), "virtme-vsock", f"{args.vsock_cid}.sh") - if args.vsock_connect: - # Note: we could accept arguments passed to --vsock-connect to run a - # specific command, and return, without requiring an interactive tty. + if args.vsock_connect is not None: try: (cols, rows) = os.get_terminal_size() except OSError: cols, rows = (80, 24) + cmd = args.vsock_connect if args.vsock_connect else 'su ${virtme_user:-root}' + with open(vsock_script_path, 'w', encoding="utf-8") as file: print(( '#! /bin/bash\n' f'stty rows {rows} cols {cols} iutf8 echo\n' 'HOME=$(getent passwd ${virtme_user:-root} | cut -d: -f6)\n' 'cd ${virtme_chdir:+"${virtme_chdir}"}\n' - 'exec su ${virtme_user:-root}' + f'exec {cmd}' ), file=file) os.chmod(vsock_script_path, 0o755) diff --git a/virtme_ng/run.py b/virtme_ng/run.py index 2c5aa7f..2484fd8 100644 --- a/virtme_ng/run.py +++ b/virtme_ng/run.py @@ -379,8 +379,12 @@ def make_parser(): parser.add_argument( "--vsock-connect", - action="store_true", - help="Connect to a VM using VSock.", + action="store", + nargs="?", + metavar="COMMAND", + const="", + help="Connect to a VM using VSock. " + + "An argument can be optionally specified to launch this command instead of a prompt.", ) parser.add_argument( @@ -984,8 +988,8 @@ def _get_virtme_vsock_cid(self, args): self.virtme_param["vsock_cid"] = "" def _get_virtme_vsock_connect(self, args): - if args.vsock_connect: - self.virtme_param["vsock_connect"] = "--vsock-connect" + if args.vsock_connect is not None: + self.virtme_param["vsock_connect"] = "--vsock-connect '" + args.vsock_connect + "'" else: self.virtme_param["vsock_connect"] = "" From f87af8db558583d7f425e35839beb4316e3c2fe0 Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Mon, 2 Dec 2024 16:58:56 +0100 Subject: [PATCH 3/3] vsock: connect: avoid issues with modified script When an in progress Bash script is modified, Bash will continue at the character where it was before, generally causing troubles. Because multiple --vsock-connect can be used for the same VM, it sounds good to use a simple workaround of using a function to avoid such issues. Signed-off-by: Matthieu Baerts (NGI0) --- virtme/commands/run.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/virtme/commands/run.py b/virtme/commands/run.py index 061b178..7b2150b 100644 --- a/virtme/commands/run.py +++ b/virtme/commands/run.py @@ -887,10 +887,13 @@ def do_it() -> int: with open(vsock_script_path, 'w', encoding="utf-8") as file: print(( '#! /bin/bash\n' + 'main() {\n' f'stty rows {rows} cols {cols} iutf8 echo\n' 'HOME=$(getent passwd ${virtme_user:-root} | cut -d: -f6)\n' 'cd ${virtme_chdir:+"${virtme_chdir}"}\n' - f'exec {cmd}' + f'exec {cmd}\n' + '}\n' + 'main' # use a function to avoid issues when the script is modified ), file=file) os.chmod(vsock_script_path, 0o755)