Re-implement libcompos_client in Rust
libcompos_client.rs is larged copied from pvm_exec.rs, which we will
remove once the transition is done.
Bug: 193668901
Test: Replace libcompos_client in ART, tests still passed.
Change-Id: I8daedb789e6f0ab4964c020180692839ce0c1cb5
diff --git a/binder_common/lib.rs b/binder_common/lib.rs
index f2391e3..fa91f5a 100644
--- a/binder_common/lib.rs
+++ b/binder_common/lib.rs
@@ -17,6 +17,7 @@
//! Common items useful for binder clients and/or servers.
pub mod lazy_service;
+pub mod rpc_client;
pub mod rpc_server;
use binder::public_api::{ExceptionCode, Status};
diff --git a/binder_common/rpc_client.rs b/binder_common/rpc_client.rs
new file mode 100644
index 0000000..262a689
--- /dev/null
+++ b/binder_common/rpc_client.rs
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Helpers for implementing an RPC Binder client.
+
+use binder::public_api::{StatusCode, Strong};
+use binder::unstable_api::{new_spibinder, AIBinder};
+
+/// Connects to a binder RPC server.
+pub fn connect_rpc_binder<T: binder::FromIBinder + ?Sized>(
+ cid: u32,
+ port: u32,
+) -> binder::Result<Strong<T>> {
+ // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
+ // safely taken by new_spibinder.
+ let ibinder = unsafe {
+ new_spibinder(binder_rpc_unstable_bindgen::RpcClient(cid, port) as *mut AIBinder)
+ };
+ if let Some(ibinder) = ibinder {
+ <T>::try_from(ibinder)
+ } else {
+ Err(StatusCode::BAD_VALUE)
+ }
+}
diff --git a/compos/libcompos_client/Android.bp b/compos/libcompos_client/Android.bp
index b6a4ef6..5528ea1 100644
--- a/compos/libcompos_client/Android.bp
+++ b/compos/libcompos_client/Android.bp
@@ -4,14 +4,12 @@
cc_library {
name: "libcompos_client",
- srcs: ["libcompos_client.cc"],
+ whole_static_libs: ["libcompos_client_ffi"],
min_sdk_version: "apex_inherit",
shared_libs: [
- "android.system.composd-ndk",
- "compos_aidl_interface-ndk",
- "libbase",
"libbinder_ndk",
"libbinder_rpc_unstable",
+ "libminijail",
],
export_include_dirs: ["include"],
stubs: {
@@ -25,3 +23,36 @@
"//art/odrefresh:__subpackages__",
],
}
+
+// TODO(203478530): Once rust_ffi supports stubs/symbol file, remove the wrapping cc_library above.
+rust_ffi {
+ name: "libcompos_client_ffi",
+ crate_name: "compos_client_ffi",
+ srcs: ["libcompos_client.rs"],
+ include_dirs: ["include"],
+ rustlibs: [
+ "android.system.composd-rust",
+ "compos_aidl_interface-rust",
+ "libandroid_logger",
+ "libanyhow",
+ "libbinder_common",
+ "libbinder_rs",
+ "libcompos_common",
+ "liblibc",
+ "liblog_rust",
+ "libminijail_rust",
+ "libnix",
+ "libscopeguard",
+ ],
+ prefer_rlib: true,
+ shared_libs: [
+ "libbinder_ndk",
+ ],
+ apex_available: [
+ "com.android.compos",
+ ],
+ visibility: [
+ "//packages/modules/Virtualization/compos:__subpackages__",
+ "//art/odrefresh:__subpackages__",
+ ],
+}
diff --git a/compos/libcompos_client/libcompos_client.cc b/compos/libcompos_client/libcompos_client.cc
deleted file mode 100644
index 147fcd0..0000000
--- a/compos/libcompos_client/libcompos_client.cc
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "libcompos_client.h"
-
-#include <android-base/logging.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-#include <android/binder_auto_utils.h>
-#include <android/binder_manager.h>
-#include <binder/IInterface.h>
-#include <stdlib.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <binder_rpc_unstable.hpp>
-#include <memory>
-
-#include "aidl/android/system/composd/IIsolatedCompilationService.h"
-#include "aidl/com/android/compos/FdAnnotation.h"
-#include "aidl/com/android/compos/ICompOsService.h"
-
-using aidl::android::system::composd::IIsolatedCompilationService;
-using aidl::com::android::compos::FdAnnotation;
-using aidl::com::android::compos::ICompOsService;
-using android::base::Join;
-using android::base::Pipe;
-using android::base::unique_fd;
-
-namespace {
-
-constexpr unsigned int kCompsvcRpcPort = 6432;
-constexpr const char* kComposdServiceName = "android.system.composd";
-
-void ExecFdServer(const int* ro_fds, size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num,
- unique_fd ready_fd) {
- // Holder of C Strings, with enough memory reserved to avoid reallocation. Otherwise,
- // `holder.rbegin()->c_str()` may become invalid.
- std::vector<std::string> holder;
- holder.reserve(ro_fds_num + rw_fds_num + 1 /* for --ready-fd */);
-
- std::vector<char const*> args = {"/apex/com.android.virt/bin/fd_server"};
- for (int i = 0; i < ro_fds_num; ++i) {
- args.emplace_back("--ro-fds");
- holder.emplace_back(std::to_string(*(ro_fds + i)));
- args.emplace_back(holder.rbegin()->c_str());
- }
- for (int i = 0; i < rw_fds_num; ++i) {
- args.emplace_back("--rw-fds");
- holder.emplace_back(std::to_string(*(rw_fds + i)));
- args.emplace_back(holder.rbegin()->c_str());
- }
- args.emplace_back("--ready-fd");
- holder.emplace_back(std::to_string(ready_fd.get()));
- args.emplace_back(holder.rbegin()->c_str());
-
- LOG(DEBUG) << "Starting fd_server, args: " << Join(args, ' ');
- args.emplace_back(nullptr);
- if (execv(args[0], const_cast<char* const*>(args.data())) < 0) {
- PLOG(ERROR) << "execv failed";
- }
-}
-
-class FileSharingSession final {
-public:
- static std::unique_ptr<FileSharingSession> Create(const int* ro_fds, size_t ro_fds_num,
- const int* rw_fds, size_t rw_fds_num) {
- // Create pipe for receiving a ready ping from fd_server.
- unique_fd pipe_read, pipe_write;
- if (!Pipe(&pipe_read, &pipe_write, /* flags= */ 0)) {
- PLOG(ERROR) << "Cannot create pipe";
- return nullptr;
- }
-
- pid_t pid = fork();
- if (pid < 0) {
- PLOG(ERROR) << "fork error";
- return nullptr;
- } else if (pid > 0) {
- pipe_write.reset();
-
- // When fd_server is ready it closes its end of the pipe. And if it exits, the pipe is
- // also closed. Either way this read will return 0 bytes at that point, and there's no
- // point waiting any longer.
- char c;
- read(pipe_read.get(), &c, sizeof(c));
-
- std::unique_ptr<FileSharingSession> session(new FileSharingSession(pid));
- return session;
- } else if (pid == 0) {
- pipe_read.reset();
- ExecFdServer(ro_fds, ro_fds_num, rw_fds, rw_fds_num, std::move(pipe_write));
- exit(EXIT_FAILURE);
- }
- return nullptr;
- }
-
- ~FileSharingSession() {
- if (kill(fd_server_pid_, SIGTERM) < 0) {
- PLOG(ERROR) << "Cannot kill fd_server (pid " << std::to_string(fd_server_pid_)
- << ") with SIGTERM. Retry with SIGKILL.";
- if (kill(fd_server_pid_, SIGKILL) < 0) {
- PLOG(ERROR) << "Still cannot terminate with SIGKILL. Give up.";
- // TODO: it may be the safest if we turn fd_server into a library to run in a
- // thread.
- }
- }
- }
-
-private:
- explicit FileSharingSession(pid_t pid) : fd_server_pid_(pid) {}
-
- pid_t fd_server_pid_;
-};
-
-int MakeRequestToVM(int cid, const uint8_t* marshaled, size_t size, const int* ro_fds,
- size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num) {
- ndk::SpAIBinder binder(RpcClient(cid, kCompsvcRpcPort));
- std::shared_ptr<ICompOsService> service = ICompOsService::fromBinder(binder);
- if (!service) {
- LOG(ERROR) << "Cannot connect to the service";
- return -1;
- }
-
- std::unique_ptr<FileSharingSession> session_raii =
- FileSharingSession::Create(ro_fds, ro_fds_num, rw_fds, rw_fds_num);
- if (!session_raii) {
- LOG(ERROR) << "Cannot start to share FDs";
- return -1;
- }
-
- // Since the input from the C API are raw pointers, we need to duplicate them into vectors in
- // order to pass to the binder API.
- std::vector<uint8_t> duplicated_buffer(marshaled, marshaled + size);
- FdAnnotation fd_annotation = {
- .input_fds = std::vector<int>(ro_fds, ro_fds + ro_fds_num),
- .output_fds = std::vector<int>(rw_fds, rw_fds + rw_fds_num),
- };
- int8_t exit_code;
- ndk::ScopedAStatus status = service->compile(duplicated_buffer, fd_annotation, &exit_code);
- if (!status.isOk()) {
- LOG(ERROR) << "Compilation failed (exit " << std::to_string(exit_code)
- << "): " << status.getDescription();
- return -1;
- }
- return 0;
-}
-
-int MakeRequestToComposd(const uint8_t* marshaled, size_t size, const int* ro_fds,
- size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num) {
- ndk::SpAIBinder binder(AServiceManager_getService(kComposdServiceName));
- std::shared_ptr<IIsolatedCompilationService> service =
- IIsolatedCompilationService::fromBinder(binder);
- if (!service) {
- LOG(ERROR) << "Cannot connect to the service";
- return -1;
- }
-
- auto session_raii = std::unique_ptr<FileSharingSession>(
- FileSharingSession::Create(ro_fds, ro_fds_num, rw_fds, rw_fds_num));
- if (!session_raii) {
- LOG(ERROR) << "Cannot start to share FDs";
- return -1;
- }
-
- // Since the input from the C API are raw pointers, we need to duplicate them into vectors in
- // order to pass to the binder API.
- std::vector<uint8_t> duplicated_buffer(marshaled, marshaled + size);
- FdAnnotation fd_annotation = {
- .input_fds = std::vector<int>(ro_fds, ro_fds + ro_fds_num),
- .output_fds = std::vector<int>(rw_fds, rw_fds + rw_fds_num),
- };
- int8_t exit_code;
- ndk::ScopedAStatus status = service->compile(duplicated_buffer, fd_annotation, &exit_code);
- if (!status.isOk()) {
- LOG(ERROR) << "Compilation failed (exit " << std::to_string(exit_code)
- << "): " << status.getDescription();
- return -1;
- }
- return 0;
-}
-
-} // namespace
-
-__BEGIN_DECLS
-
-int AComposClient_Request(int cid, const uint8_t* marshaled, size_t size, const int* ro_fds,
- size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num) {
- if (cid == -1 /* VMADDR_CID_ANY */) {
- return MakeRequestToComposd(marshaled, size, ro_fds, ro_fds_num, rw_fds, rw_fds_num);
- } else {
- return MakeRequestToVM(cid, marshaled, size, ro_fds, ro_fds_num, rw_fds, rw_fds_num);
- }
-}
-
-__END_DECLS
diff --git a/compos/libcompos_client/libcompos_client.rs b/compos/libcompos_client/libcompos_client.rs
new file mode 100644
index 0000000..55d70a4
--- /dev/null
+++ b/compos/libcompos_client/libcompos_client.rs
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! A library for a client to send requests to the CompOS service in the VM.
+
+use anyhow::{Context, Result};
+use binder_common::rpc_client::connect_rpc_binder;
+use libc::c_int;
+use log::{debug, error, warn};
+use minijail::Minijail;
+use nix::fcntl::OFlag;
+use nix::unistd::pipe2;
+use std::fs::File;
+use std::io::Read;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::path::Path;
+use std::slice::from_raw_parts;
+
+use android_system_composd::{
+ aidl::android::system::composd::IIsolatedCompilationService::IIsolatedCompilationService,
+ binder::wait_for_interface,
+};
+use compos_aidl_interface::aidl::com::android::compos::{
+ FdAnnotation::FdAnnotation, ICompOsService::ICompOsService,
+};
+use compos_aidl_interface::binder::Strong;
+use compos_common::{COMPOS_VSOCK_PORT, VMADDR_CID_ANY};
+
+const FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
+
+fn get_composd() -> Result<Strong<dyn IIsolatedCompilationService>> {
+ wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
+ .context("Failed to find IIsolatedCompilationService")
+}
+
+fn spawn_fd_server(fd_annotation: &FdAnnotation, ready_file: File) -> Result<Minijail> {
+ let mut inheritable_fds = Vec::new();
+ let mut args = vec![FD_SERVER_BIN.to_string()];
+ for fd in &fd_annotation.input_fds {
+ args.push("--ro-fds".to_string());
+ args.push(fd.to_string());
+ inheritable_fds.push(*fd);
+ }
+ for fd in &fd_annotation.output_fds {
+ args.push("--rw-fds".to_string());
+ args.push(fd.to_string());
+ inheritable_fds.push(*fd);
+ }
+ let ready_fd = ready_file.as_raw_fd();
+ args.push("--ready-fd".to_string());
+ args.push(ready_fd.to_string());
+ inheritable_fds.push(ready_fd);
+
+ let jail = Minijail::new()?;
+ let _pid = jail.run(Path::new(FD_SERVER_BIN), &inheritable_fds, &args)?;
+ Ok(jail)
+}
+
+fn create_pipe() -> Result<(File, File)> {
+ let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC)?;
+ // SAFETY: We are the sole owners of these fds as they were just created.
+ let read_fd = unsafe { File::from_raw_fd(raw_read) };
+ let write_fd = unsafe { File::from_raw_fd(raw_write) };
+ Ok((read_fd, write_fd))
+}
+
+fn wait_for_fd_server_ready(mut ready_fd: File) -> Result<()> {
+ let mut buffer = [0];
+ // When fd_server is ready it closes its end of the pipe. And if it exits, the pipe is also
+ // closed. Either way this read will return 0 bytes at that point, and there's no point waiting
+ // any longer.
+ let _ = ready_fd.read(&mut buffer).context("Waiting for fd_server to be ready")?;
+ debug!("fd_server is ready");
+ Ok(())
+}
+
+fn try_request(cid: c_int, marshaled: &[u8], fd_annotation: FdAnnotation) -> Result<c_int> {
+ // 1. Spawn a fd_server to serve remote read/write requests.
+ let (ready_read_fd, ready_write_fd) = create_pipe()?;
+ let fd_server_jail = spawn_fd_server(&fd_annotation, ready_write_fd)?;
+ let fd_server_lifetime = scopeguard::guard(fd_server_jail, |fd_server_jail| {
+ if let Err(e) = fd_server_jail.kill() {
+ if !matches!(e, minijail::Error::Killed(_)) {
+ warn!("Failed to kill fd_server: {}", e);
+ }
+ }
+ });
+
+ // 2. Send the marshaled request the remote.
+ let cid = cid as u32;
+ let result = if cid == VMADDR_CID_ANY {
+ // Sentinel value that indicates we should use composd
+ let composd = get_composd()?;
+ wait_for_fd_server_ready(ready_read_fd)?;
+ composd.compile(marshaled, &fd_annotation)
+ } else {
+ // Call directly into the VM
+ let compos_vm = connect_rpc_binder::<dyn ICompOsService>(cid, COMPOS_VSOCK_PORT)
+ .context("Cannot connect to RPC binder")?;
+ wait_for_fd_server_ready(ready_read_fd)?;
+ compos_vm.compile(marshaled, &fd_annotation)
+ };
+ let result = result.context("Binder call failed")?;
+
+ // Be explicit about the lifetime, which should last at least until the task is finished.
+ drop(fd_server_lifetime);
+
+ Ok(c_int::from(result))
+}
+
+/// A public C API. See libcompos_client.h for the canonical doc.
+///
+/// # Safety
+///
+/// The client must provide legitimate pointers with correct sizes to the backing arrays.
+#[no_mangle]
+pub unsafe extern "C" fn AComposClient_Request(
+ cid: c_int,
+ marshaled: *const u8,
+ size: usize,
+ ro_fds: *const c_int,
+ ro_fds_num: usize,
+ rw_fds: *const c_int,
+ rw_fds_num: usize,
+) -> c_int {
+ if marshaled.is_null() || ro_fds.is_null() || rw_fds.is_null() {
+ error!("Argument pointers should not be null");
+ return -1;
+ }
+
+ // The unsafe parts.
+ let ro_fd_slice = from_raw_parts(ro_fds, ro_fds_num);
+ let rw_fd_slice = from_raw_parts(rw_fds, rw_fds_num);
+ let marshaled_slice = from_raw_parts(marshaled, size);
+
+ let fd_annotation =
+ FdAnnotation { input_fds: ro_fd_slice.to_vec(), output_fds: rw_fd_slice.to_vec() };
+
+ match try_request(cid, marshaled_slice, fd_annotation) {
+ Ok(exit_code) => exit_code,
+ Err(e) => {
+ error!("AComposClient_Request failed: {:?}", e);
+ -1
+ }
+ }
+}