Framework JNI uses libvmclient to connect to virtmgr
libvmclient, as its name shows, is a library designed as a client-side
library for AVF. It is responsible for spawning virtmgr and make a
connection to it.
However, the library wasn't used for the Java APIs. For those APIs, the
JNI part (libvirtualization_jni) implemented the almost identical
routine for spawning virtmgr.
This change de-duplicates the code in the JNI part by exposing an FFI
for libvmclient and let the JNI part use it.
Note that the JNI part had more checks at the end of the connection
establishment. That part is copied over to libvmclient.
Bug: N/A
Test: watch TH
Change-Id: Ib6b0f3a180a7dbd4c797188e98d4b0e66f76423b
diff --git a/libs/libvirtualization_jni/Android.bp b/libs/libvirtualization_jni/Android.bp
index 4a569d4..9dc86b0 100644
--- a/libs/libvirtualization_jni/Android.bp
+++ b/libs/libvirtualization_jni/Android.bp
@@ -16,7 +16,10 @@
"liblog",
"libnativehelper",
],
- static_libs: ["libavf_cc_flags"],
+ static_libs: [
+ "libavf_cc_flags",
+ "libvmclient.ffi",
+ ],
}
cc_library_shared {
diff --git a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
index 0538c9e..f0c9b4f 100644
--- a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
+++ b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualizationService.cpp
@@ -19,6 +19,7 @@
#include <android-base/unique_fd.h>
#include <android/avf_cc_flags.h>
#include <android/binder_ibinder_jni.h>
+#include <errno.h>
#include <jni.h>
#include <log/log.h>
#include <poll.h>
@@ -29,57 +30,25 @@
using namespace android::base;
-static constexpr const char VIRTMGR_PATH[] = "/apex/com.android.virt/bin/virtmgr";
static constexpr size_t VIRTMGR_THREADS = 2;
+void error_callback(int code, const char* msg, void* ctx) {
+ JNIEnv* env = reinterpret_cast<JNIEnv*>(ctx);
+ if (code == EPERM || code == EACCES) {
+ env->ThrowNew(env->FindClass("java/lang/SecurityException"),
+ "Virtmgr didn't send any data through pipe. Please consider checking if "
+ "android.permission.MANAGE_VIRTUAL_MACHINE permission is granted");
+ return;
+ }
+ env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"), msg);
+}
+
+extern "C" int get_virtualization_service(decltype(error_callback)*, void*);
+
extern "C" JNIEXPORT jint JNICALL
Java_android_system_virtualmachine_VirtualizationService_nativeSpawn(
JNIEnv* env, [[maybe_unused]] jclass clazz) {
- unique_fd serverFd, clientFd;
- if (!Socketpair(SOCK_STREAM, &serverFd, &clientFd)) {
- env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
- ("Failed to create socketpair: " + std::string(strerror(errno))).c_str());
- return -1;
- }
-
- unique_fd waitFd, readyFd;
- if (!Pipe(&waitFd, &readyFd, 0)) {
- env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
- ("Failed to create pipe: " + std::string(strerror(errno))).c_str());
- return -1;
- }
-
- if (fork() == 0) {
- // Close client's FDs.
- clientFd.reset();
- waitFd.reset();
-
- auto strServerFd = std::to_string(serverFd.get());
- auto strReadyFd = std::to_string(readyFd.get());
-
- execl(VIRTMGR_PATH, VIRTMGR_PATH, "--rpc-server-fd", strServerFd.c_str(), "--ready-fd",
- strReadyFd.c_str(), NULL);
- }
-
- // Close virtmgr's FDs.
- serverFd.reset();
- readyFd.reset();
-
- // Wait for the server to signal its readiness by closing its end of the pipe.
- char buf;
- int ret = read(waitFd.get(), &buf, sizeof(buf));
- if (ret < 0) {
- env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
- "Failed to wait for VirtualizationService to be ready");
- return -1;
- } else if (ret < 1) {
- env->ThrowNew(env->FindClass("java/lang/SecurityException"),
- "Virtmgr didn't send any data through pipe. Please consider checking if "
- "android.permission.MANAGE_VIRTUAL_MACHINE permission is granted");
- return -1;
- }
-
- return clientFd.release();
+ return get_virtualization_service(error_callback, env);
}
extern "C" JNIEXPORT jobject JNICALL
diff --git a/libs/libvmclient/Android.bp b/libs/libvmclient/Android.bp
index 9fdeaf8..5bd59da 100644
--- a/libs/libvmclient/Android.bp
+++ b/libs/libvmclient/Android.bp
@@ -2,8 +2,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-rust_library {
- name: "libvmclient",
+rust_defaults {
+ name: "libvmclient.default",
crate_name: "vmclient",
defaults: ["avf_build_flags_rust"],
srcs: ["src/lib.rs"],
@@ -25,3 +25,13 @@
"com.android.virt",
],
}
+
+rust_library {
+ name: "libvmclient",
+ defaults: ["libvmclient.default"],
+}
+
+rust_ffi_static {
+ name: "libvmclient.ffi",
+ defaults: ["libvmclient.default"],
+}
diff --git a/libs/libvmclient/src/lib.rs b/libs/libvmclient/src/lib.rs
index 7b576e6..bc9d683 100644
--- a/libs/libvmclient/src/lib.rs
+++ b/libs/libvmclient/src/lib.rs
@@ -43,7 +43,9 @@
use log::warn;
use rpcbinder::{FileDescriptorTransportMode, RpcSession};
use shared_child::SharedChild;
+use std::ffi::{c_char, c_int, c_void, CString};
use std::io::{self, Read};
+use std::os::fd::RawFd;
use std::process::Command;
use std::{
fmt::{self, Debug, Formatter},
@@ -74,6 +76,40 @@
Ok(socketpair(AddressFamily::Unix, SockType::Stream, None, SockFlag::SOCK_CLOEXEC)?)
}
+/// Error handling function for `get_virtualization_service`.
+///
+/// # Safety
+/// `message` shouldn't be used outside of the lifetime of the function. Management of `ctx` is
+/// entirely up to the function.
+pub type ErrorCallback =
+ unsafe extern "C" fn(code: c_int, message: *const c_char, ctx: *mut c_void);
+
+/// Spawns a new instance of virtmgr and rerturns a file descriptor for the socket connection to
+/// the service. When error occurs, it is reported via the ErrorCallback function along with the
+/// error message and any context that is set by the client.
+///
+/// # Safety
+/// `cb` should be null or a valid function pointer of type `ErrorCallback`
+#[no_mangle]
+pub unsafe extern "C" fn get_virtualization_service(
+ cb: Option<ErrorCallback>,
+ ctx: *mut c_void,
+) -> RawFd {
+ match VirtualizationService::new() {
+ Ok(vs) => vs.client_fd.into_raw_fd(),
+ Err(e) => {
+ if let Some(cb) = cb {
+ let code = e.raw_os_error().unwrap_or(-1);
+ let msg = CString::new(e.to_string()).unwrap();
+ // SAFETY: `cb` doesn't use `msg` outside of the lifetime of the function.
+ // msg's lifetime is longer than `cb` as it is bound to a local variable.
+ unsafe { cb(code, msg.as_ptr(), ctx) };
+ }
+ -1
+ }
+ }
+}
+
/// A running instance of virtmgr which is hosting a VirtualizationService
/// RpcBinder server.
pub struct VirtualizationService {
@@ -97,10 +133,11 @@
SharedChild::spawn(&mut command)?;
- // Wait for the child to signal that the RpcBinder server is ready
- // by closing its end of the pipe.
- let _ignored = File::from(wait_fd).read(&mut [0]);
-
+ // Wait for the child to signal that the RpcBinder server is read by closing its end of the
+ // pipe. Failing to read (especially EACCESS or EPERM) can happen if the client lacks the
+ // MANAGE_VIRTUAL_MACHINE permission. Therefore, such errors are propagated instead of
+ // being ignored.
+ let _ = File::from(wait_fd).read(&mut [0])?;
Ok(VirtualizationService { client_fd })
}