git: ed60e5710e74 - main - bhyvectl: support querying VM pid

From: Roman Bogorodskiy <novel_at_FreeBSD.org>
Date: Sat, 19 Sep 2026 16:19:52 UTC
The branch main has been updated by novel:

URL: https://cgit.FreeBSD.org/src/commit/?id=ed60e5710e7489651c2829f220d1e87b3ed7429a

commit ed60e5710e7489651c2829f220d1e87b3ed7429a
Author:     Roman Bogorodskiy <novel@FreeBSD.org>
AuthorDate: 2026-08-22 08:50:00 +0000
Commit:     Roman Bogorodskiy <novel@FreeBSD.org>
CommitDate: 2026-09-19 16:19:35 +0000

    bhyvectl: support querying VM pid
    
    Extend bhyvectl(8) to support querying VM pid using the
    `--get-vm-pid` flag.
    
    This is useful in monitor mode when the VM pid differs
    from the main bhyve(8) process run by the user. Knowing the VM pid
    is necessary, for example, to query process resource usage or
    trigger ACPI shutdown with SIGTERM.
    
    Of course, it could be obtained by matching the monitor process
    children by the process title, but it's a little more complex
    and fragile than it could be.
    
    Implement that by adding the "get_vm_pid" IPC command,
    and using it to implement `bhyvectl --get-vm-pid`, which
    prints the VM PID.
    
    When the VM PID is not known, ESRCH is returned.
    
    Reviewed by:    bnovkov (previous iteration without man changes)
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D59108
---
 usr.sbin/bhyve/bhyve_config.5 |  2 +-
 usr.sbin/bhyve/ipc.c          | 11 ++++++++
 usr.sbin/bhyvectl/Makefile    |  3 +--
 usr.sbin/bhyvectl/bhyvectl.8  |  8 ++++--
 usr.sbin/bhyvectl/bhyvectl.c  | 60 ++++++++++++++++++++++++++++++++++++-------
 5 files changed, 70 insertions(+), 14 deletions(-)

diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
index 37a740aec3ec..55742b6fd5a8 100644
--- a/usr.sbin/bhyve/bhyve_config.5
+++ b/usr.sbin/bhyve/bhyve_config.5
@@ -232,7 +232,7 @@ Configuration can specify up to 255 strings.
 .It Va rundir Ta String Ta Pa /var/run/bhyve/ Ta
 Path to the runtime directory for
 .Xr bhyve 8
-to store its service files, such as the checkpoint socket.
+to store its service files, such as the IPC socket.
 Overriding this path is useful when running
 .Xr bhyve 8
 as an unprivileged user.
diff --git a/usr.sbin/bhyve/ipc.c b/usr.sbin/bhyve/ipc.c
index cdf07e18ed3f..ba938e99aeb5 100644
--- a/usr.sbin/bhyve/ipc.c
+++ b/usr.sbin/bhyve/ipc.c
@@ -56,6 +56,17 @@ static struct ipc_thread_ctx {
 	int sockfd;
 } thr_ctx;
 
+static nvlist_t *
+ipc_get_vm_pid(struct vmctx *ctx __unused, const nvlist_t *nvl __unused)
+{
+	nvlist_t *reply;
+
+	reply = nvlist_create(0);
+	nvlist_add_number(reply, "pid", getpid());
+	return (reply);
+}
+IPC_COMMAND(get_vm_pid, ipc_get_vm_pid);
+
 static nvlist_t *
 handle_message(struct vmctx *ctx, nvlist_t *nvl)
 {
diff --git a/usr.sbin/bhyvectl/Makefile b/usr.sbin/bhyvectl/Makefile
index 8cfb1169e585..829e5a5734e7 100644
--- a/usr.sbin/bhyvectl/Makefile
+++ b/usr.sbin/bhyvectl/Makefile
@@ -10,12 +10,11 @@ PACKAGE=	bhyve
 
 MAN=	bhyvectl.8
 
-LIBADD=	vmmapi util
+LIBADD=	vmmapi util nv
 
 CFLAGS+= -I${.CURDIR} -I${SRCTOP}/sys/amd64/vmm
 
 .if ${MK_BHYVE_SNAPSHOT} != "no"
-LIBADD+= nv
 CFLAGS+= -DBHYVE_SNAPSHOT
 
 # usr.sbin/bhyve/snapshot.h needs ucl header
diff --git a/usr.sbin/bhyvectl/bhyvectl.8 b/usr.sbin/bhyvectl/bhyvectl.8
index b999230cfd73..366fc2e8b3ef 100644
--- a/usr.sbin/bhyvectl/bhyvectl.8
+++ b/usr.sbin/bhyvectl/bhyvectl.8
@@ -25,7 +25,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd July 8, 2026
+.Dd September 11, 2026
 .Dt BHYVECTL 8
 .Os
 .Sh NAME
@@ -37,6 +37,7 @@
 .Op Fl -create
 .Op Fl -destroy
 .Op Fl -get-stats
+.Op Fl -get-vm-pid
 .Op Fl -inject-nmi
 .Op Fl -force-reset
 .Op Fl -force-poweroff
@@ -61,6 +62,9 @@ Create the specified VM.
 Destroy the specified VM.
 .It Fl -get-stats
 Retrieve statistics for the specified VM.
+.It Fl -get-vm-pid
+Print the process ID of the bhyve process running the specified VM.
+In monitor mode, this is the child process executing the VM.
 .It Fl -inject-nmi
 Inject a non-maskable interrupt (NMI) into the VM.
 .It Fl -force-reset
@@ -79,7 +83,7 @@ Save a snapshot of a virtual machine similar to
 The virtual machine will terminate after the snapshot has been
 saved.
 .It Fl -rundir= Ns Ar <path>
-Specify path to the directory containing the checkpoint socket.
+Specify path to the directory containing the bhyve IPC socket.
 If not specified,
 .Pa /var/run/bhyve
 is used.
diff --git a/usr.sbin/bhyvectl/bhyvectl.c b/usr.sbin/bhyvectl/bhyvectl.c
index ba2518267b14..3f67daa7be21 100644
--- a/usr.sbin/bhyvectl/bhyvectl.c
+++ b/usr.sbin/bhyvectl/bhyvectl.c
@@ -72,6 +72,7 @@ static int get_active_cpus, get_debug_cpus, get_suspended_cpus;
 static uint64_t memsize;
 static int run;
 static int get_cpu_topology;
+static int get_vm_pid;
 #ifdef BHYVE_SNAPSHOT
 static int vm_suspend_opt;
 #endif
@@ -87,8 +88,8 @@ enum {
 #ifdef BHYVE_SNAPSHOT
 	SET_CHECKPOINT_FILE,
 	SET_SUSPEND_FILE,
-	SET_RUNDIR,
 #endif
+	SET_RUNDIR,
 	OPT_LAST,
 };
 
@@ -137,11 +138,12 @@ setup_options(void)
 		{ "get-debug-cpus",	NO_ARG,	&get_debug_cpus,	1 },
 		{ "get-suspended-cpus", NO_ARG,	&get_suspended_cpus, 	1 },
 		{ "get-cpu-topology",	NO_ARG, &get_cpu_topology,	1 },
+		{ "get-vm-pid",		NO_ARG, &get_vm_pid,		1 },
 #ifdef BHYVE_SNAPSHOT
 		{ "checkpoint", 	REQ_ARG, 0,	SET_CHECKPOINT_FILE},
 		{ "suspend", 		REQ_ARG, 0,	SET_SUSPEND_FILE},
-		{ "rundir", 		REQ_ARG, 0,	SET_RUNDIR},
 #endif
+		{ "rundir", 		REQ_ARG, 0,	SET_RUNDIR},
 	};
 
 	return (bhyvectl_opts(common_opts, nitems(common_opts)));
@@ -158,8 +160,8 @@ usage(const struct option *opts)
 #ifdef BHYVE_SNAPSHOT
 	    [SET_CHECKPOINT_FILE] = "filename",
 	    [SET_SUSPEND_FILE] = "filename",
-	    [SET_RUNDIR] = "path",
 #endif
+	    [SET_RUNDIR] = "path",
 	};
 	(void)fprintf(stderr, "Usage: %s --vm=<vmname>\n", progname);
 	for (const struct option *o = opts; o->name; o++) {
@@ -255,12 +257,13 @@ show_memseg(struct vmctx *ctx)
 }
 
 static int __unused
-ipc_send_message(const char *vmname, nvlist_t *request, const char *rundir)
+ipc_send_message(const char *vmname, nvlist_t *request, const char *rundir,
+    nvlist_t **replyp)
 {
 	int err = 0, socket_fd, ret;
 	struct sockaddr_un addr;
 	const char* errmsg;
-	nvlist_t *reply;
+	nvlist_t *reply = NULL;
 
 	socket_fd = socket(PF_UNIX, SOCK_STREAM, 0);
 	if (socket_fd < 0) {
@@ -292,22 +295,54 @@ ipc_send_message(const char *vmname, nvlist_t *request, const char *rundir)
 	request = NULL;
 	if (reply == NULL) {
 		perror("nvlist_xfer() failed");
+		err = errno;
 		goto done;
 	}
 	if (nvlist_exists_string(reply, "error")) {
 		errmsg = nvlist_get_string(reply, "error");
-		fprintf(stderr, "%s: IPC command failed: %s\n", __func__, errmsg);
+		fprintf(stderr, "%s: IPC command failed: %s\n",
+		    __func__, errmsg);
 		err = -1;
+	} else if (replyp != NULL) {
+		*replyp = reply;
+		reply = NULL;
 	}
 done:
 	if (request != NULL)
 		nvlist_destroy(request);
+	if (reply != NULL)
+		nvlist_destroy(reply);
 
 	if (socket_fd >= 0)
 		close(socket_fd);
 	return (err);
 }
 
+static int
+vm_pid_request(const char *vmname, const char *rundir, pid_t *pid)
+{
+	nvlist_t *nvl, *reply;
+	int error;
+
+	nvl = nvlist_create(0);
+	nvlist_add_string(nvl, "cmd", "get_vm_pid");
+	error = ipc_send_message(vmname, nvl, rundir, &reply);
+	if (error == -1)
+		error = EOPNOTSUPP;
+	else if (error == ENOENT || error == ECONNREFUSED)
+		error = ESRCH;
+	if (error == 0) {
+		if (nvlist_exists_number(reply, "pid"))
+			*pid = (pid_t)nvlist_get_number(reply, "pid");
+		else
+			error = EPROTO;
+		nvlist_destroy(reply);
+	}
+	if (error != 0)
+		errno = error;
+	return (error);
+}
+
 #ifdef BHYVE_SNAPSHOT
 static int
 open_directory(const char *file)
@@ -340,7 +375,7 @@ snapshot_request(const char *vmname, char *file, bool suspend, const char *rundi
 	nvlist_add_bool(nvl, "suspend", suspend);
 	nvlist_move_descriptor(nvl, "fddir", fd);
 
-	return (ipc_send_message(vmname, nvl, rundir));
+	return (ipc_send_message(vmname, nvl, rundir, NULL));
 }
 #endif
 
@@ -402,11 +437,10 @@ main(int argc, char *argv[])
 			vm_suspend_opt = (ch == SET_SUSPEND_FILE);
 			break;
 
+#endif
 		case SET_RUNDIR:
 			rundir = optarg;
 			break;
-
-#endif
 		default:
 			usage(opts);
 		}
@@ -536,6 +570,14 @@ main(int argc, char *argv[])
 		    "maxcpus=%hu\n", sockets, cores, threads, maxcpus);
 	}
 
+	if (!error && get_vm_pid) {
+		pid_t pid;
+
+		error = vm_pid_request(vmname, rundir, &pid);
+		if (!error)
+			printf("vm pid:\t%d\n", pid);
+	}
+
 	if (!error && run) {
 		struct vm_exit vmexit;
 		cpuset_t cpuset;