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
+        }
+    }
+}