vmclient: Spawn child virtmgr and connect to it

Instead of connecting to the global virtualizationservice, make
vmclient::connect() spawn a child virtmgr process and connect to it via
RpcBinder.

Bug: 245727626
Test: atest -p packages/modules/Virtualization:avf-presubmit
Change-Id: I456a151071a5f0d8448b11b89d88c1c2892f911f
diff --git a/vmclient/Android.bp b/vmclient/Android.bp
index 88b0c9a..0a2e692 100644
--- a/vmclient/Android.bp
+++ b/vmclient/Android.bp
@@ -11,8 +11,11 @@
         "android.system.virtualizationcommon-rust",
         "android.system.virtualizationservice-rust",
         "libbinder_rs",
+        "libcommand_fds",
         "liblog_rust",
+        "libnix",
         "librpcbinder_rs",
+        "libshared_child",
         "libthiserror",
     ],
     shared_libs: [
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index a9c2619..0e3d140 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -39,12 +39,16 @@
         ParcelFileDescriptor, Result as BinderResult, StatusCode, Strong,
     },
 };
+use command_fds::CommandFdExt;
 use log::warn;
-use rpcbinder::RpcSession;
+use rpcbinder::{FileDescriptorTransportMode, RpcSession};
+use shared_child::SharedChild;
+use std::io::{self, Read};
+use std::process::Command;
 use std::{
     fmt::{self, Debug, Formatter},
     fs::File,
-    os::unix::io::IntoRawFd,
+    os::unix::io::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd},
     sync::Arc,
     time::Duration,
 };
@@ -52,6 +56,79 @@
 const VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER: &str =
     "android.system.virtualizationservice";
 
+const VIRTMGR_PATH: &str = "/apex/com.android.virt/bin/virtmgr";
+const VIRTMGR_THREADS: usize = 16;
+
+fn posix_pipe() -> Result<(OwnedFd, OwnedFd), io::Error> {
+    use nix::fcntl::OFlag;
+    use nix::unistd::pipe2;
+
+    // Create new POSIX pipe. Make it O_CLOEXEC to align with how Rust creates
+    // file descriptors (expected by SharedChild).
+    let (raw1, raw2) = pipe2(OFlag::O_CLOEXEC)?;
+
+    // SAFETY - Taking ownership of brand new FDs.
+    unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+}
+
+fn posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error> {
+    use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
+
+    // Create new POSIX socketpair, suitable for use with RpcBinder UDS bootstrap
+    // transport. Make it O_CLOEXEC to align with how Rust creates file
+    // descriptors (expected by SharedChild).
+    let (raw1, raw2) =
+        socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?;
+
+    // SAFETY - Taking ownership of brand new FDs.
+    unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+}
+
+/// A running instance of virtmgr which is hosting a VirtualizationService
+/// RpcBinder server.
+pub struct VirtualizationService {
+    /// Client FD for UDS connection to virtmgr's RpcBinder server. Closing it
+    /// will make virtmgr shut down.
+    client_fd: OwnedFd,
+}
+
+impl VirtualizationService {
+    /// Spawns a new instance of virtmgr, a child process that will host
+    /// the VirtualizationService AIDL service.
+    pub fn new() -> Result<VirtualizationService, io::Error> {
+        let (wait_fd, ready_fd) = posix_pipe()?;
+        let (client_fd, server_fd) = posix_socketpair()?;
+
+        let mut command = Command::new(VIRTMGR_PATH);
+        command.arg("--rpc-server-fd").arg(format!("{}", server_fd.as_raw_fd()));
+        command.arg("--ready-fd").arg(format!("{}", ready_fd.as_raw_fd()));
+        command.preserved_fds(vec![server_fd.as_raw_fd(), ready_fd.as_raw_fd()]);
+
+        SharedChild::spawn(&mut command)?;
+
+        // Drop FDs that belong to virtmgr.
+        drop(server_fd);
+        drop(ready_fd);
+
+        // Wait for the child to signal that the RpcBinder server is ready
+        // by closing its end of the pipe.
+        let _ = File::from(wait_fd).read(&mut [0]);
+
+        Ok(VirtualizationService { client_fd })
+    }
+
+    /// Connects to the VirtualizationService AIDL service.
+    pub fn connect(&self) -> Result<Strong<dyn IVirtualizationService>, io::Error> {
+        let session = RpcSession::new();
+        session.set_file_descriptor_transport_mode(FileDescriptorTransportMode::Unix);
+        session.set_max_incoming_threads(VIRTMGR_THREADS);
+        session.set_max_outgoing_threads(VIRTMGR_THREADS);
+        session
+            .setup_unix_domain_bootstrap_client(self.client_fd.as_fd())
+            .map_err(|_| io::Error::from(io::ErrorKind::ConnectionRefused))
+    }
+}
+
 /// Connects to the VirtualizationService AIDL service.
 pub fn connect() -> Result<Strong<dyn IVirtualizationService>, StatusCode> {
     wait_for_interface(VIRTUALIZATION_SERVICE_BINDER_SERVICE_IDENTIFIER)