Merge "Extract run_rpc_server to libbinder_common"
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 547fd44..3a9d5f5 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -38,12 +38,19 @@
     platform_apis: true,
 
     binaries: [
+        // Used in Android
         "compos_key_cmd",
         "compos_verify_key",
         "composd",
         "composd_cmd",
+        "pvm_exec", // to be superseded by libcompos_client
+
+        // Used in VM
         "compsvc",
-        "pvm_exec",
+    ],
+
+    native_shared_libs: [
+        "libcompos_client",
     ],
 
     apps: [
diff --git a/compos/composd/aidl/Android.bp b/compos/composd/aidl/Android.bp
index 0352001..8116632 100644
--- a/compos/composd/aidl/Android.bp
+++ b/compos/composd/aidl/Android.bp
@@ -18,5 +18,11 @@
                 "com.android.compos",
             ],
         },
+        ndk: {
+            enabled: true,
+            apex_available: [
+                "com.android.compos",
+            ],
+        },
     },
 }
diff --git a/compos/libcompos_client/Android.bp b/compos/libcompos_client/Android.bp
new file mode 100644
index 0000000..c9a0e5f
--- /dev/null
+++ b/compos/libcompos_client/Android.bp
@@ -0,0 +1,23 @@
+cc_library {
+    name: "libcompos_client",
+    srcs: ["libcompos_client.cc"],
+    min_sdk_version: "apex_inherit",
+    shared_libs: [
+        "android.system.composd-ndk",
+        "compos_aidl_interface-ndk",
+        "libbase",
+        "libbinder_ndk",
+        "libbinder_rpc_unstable",
+    ],
+    export_include_dirs: ["include"],
+    stubs: {
+        symbol_file: "libcompos_client.map.txt",
+    },
+    apex_available: [
+        "com.android.compos",
+    ],
+    visibility: [
+        "//packages/modules/Virtualization/compos:__subpackages__",
+        "//art/odrefresh:__subpackages__",
+    ],
+}
diff --git a/compos/libcompos_client/include/libcompos_client.h b/compos/libcompos_client/include/libcompos_client.h
new file mode 100644
index 0000000..171854d
--- /dev/null
+++ b/compos/libcompos_client/include/libcompos_client.h
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Sends request encoded in a marshaled byte buffer to the Compilation OS service, which will
+ * execute the compiler with context encoded in the marshaled byte buffer.
+ *
+ * @param cid the VM's cid to send the request to.
+ * @param marshaled pointer to a marshaled byte buffer.
+ * @param size size of the marshaled byte buffer pointed by `marshaled`.
+ * @param ro_fds pointer to a int array of read-only file descriptor numbers.
+ * @param ro_fds_num size of the array pointed by `ro_fds`.
+ * @param rw_fds pointer to a int array of read-writable file descriptor numbers.
+ * @param rw_fds_num size of the array pointed by `rw_fds`.
+ * @return the exit code of the compiler.
+ *
+ * Available since API level 33.
+ */
+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)
+        __INTRODUCED_IN(33);
+
+__END_DECLS
diff --git a/compos/libcompos_client/libcompos_client.cc b/compos/libcompos_client/libcompos_client.cc
new file mode 100644
index 0000000..147fcd0
--- /dev/null
+++ b/compos/libcompos_client/libcompos_client.cc
@@ -0,0 +1,209 @@
+/*
+ * 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.map.txt b/compos/libcompos_client/libcompos_client.map.txt
new file mode 100644
index 0000000..9d47c53
--- /dev/null
+++ b/compos/libcompos_client/libcompos_client.map.txt
@@ -0,0 +1,6 @@
+LIBCOMPOS_CLIENT {
+    global:
+        AComposClient_Request; # apex
+    local:
+        *;
+};
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 59ad81c..3520d9f 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -21,13 +21,14 @@
 use android_system_virtualizationservice::binder::ParcelFileDescriptor;
 use anyhow::{anyhow, Context, Result};
 use binder::{wait_for_interface, Strong};
-use log::info;
+use log::{error, info};
 use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
 use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
 use once_cell::sync::OnceCell;
 use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative;
 use serde::Deserialize;
 use serde_xml_rs::from_reader;
+use std::env;
 use std::fs::{File, OpenOptions};
 use std::path::{Path, PathBuf};
 use vmconfig::open_parcel_file;
@@ -107,17 +108,17 @@
 
 fn make_metadata_file(
     config_path: &str,
-    apexes: &[ApexConfig],
+    apex_names: &[String],
     temporary_directory: &Path,
 ) -> Result<ParcelFileDescriptor> {
     let metadata_path = temporary_directory.join("metadata");
     let metadata = Metadata {
         version: 1,
-        apexes: apexes
+        apexes: apex_names
             .iter()
             .enumerate()
-            .map(|(i, apex)| ApexPayload {
-                name: apex.name.clone(),
+            .map(|(i, apex_name)| ApexPayload {
+                name: apex_name.clone(),
                 partition_name: format!("microdroid-apex-{}", i),
                 ..Default::default()
             })
@@ -157,7 +158,7 @@
     apk_file: File,
     idsig_file: File,
     config_path: &str,
-    apexes: &[ApexConfig],
+    apexes: &[String],
     prefer_staged: bool,
     temporary_directory: &Path,
 ) -> Result<DiskImage> {
@@ -171,7 +172,7 @@
 
     let pm = PackageManager::new()?;
     for (i, apex) in apexes.iter().enumerate() {
-        let apex_path = pm.get_apex_path(&apex.name, prefer_staged)?;
+        let apex_path = pm.get_apex_path(apex, prefer_staged)?;
         let apex_file = open_parcel_file(&apex_path, false)?;
         partitions.push(Partition {
             label: format!("microdroid-apex-{}", i),
@@ -193,6 +194,47 @@
     Ok(DiskImage { image: None, partitions, writable: false })
 }
 
+fn find_apex_names_in_classpath_env(classpath_env_var: &str) -> Vec<String> {
+    let val = env::var(classpath_env_var).unwrap_or_else(|e| {
+        error!("Reading {} failed: {}", classpath_env_var, e);
+        String::from("")
+    });
+    val.split(':')
+        .filter_map(|path| {
+            Path::new(path)
+                .strip_prefix("/apex/")
+                .map(|stripped| {
+                    let first = stripped.iter().next().unwrap();
+                    first.to_str().unwrap().to_string()
+                })
+                .ok()
+        })
+        .collect()
+}
+
+// Collect APEX names from config
+fn collect_apex_names(apexes: &[ApexConfig]) -> Vec<String> {
+    // Process pseudo names like "{BOOTCLASSPATH}".
+    // For now we have following pseudo APEX names:
+    // - {BOOTCLASSPATH}: represents APEXes contributing "BOOTCLASSPATH" environment variable
+    // - {DEX2OATBOOTCLASSPATH}: represents APEXes contributing "DEX2OATBOOTCLASSPATH" environment variable
+    // - {SYSTEMSERVERCLASSPATH}: represents APEXes contributing "SYSTEMSERVERCLASSPATH" environment variable
+    let mut apex_names: Vec<String> = apexes
+        .iter()
+        .flat_map(|apex| match apex.name.as_str() {
+            "{BOOTCLASSPATH}" => find_apex_names_in_classpath_env("BOOTCLASSPATH"),
+            "{DEX2OATBOOTCLASSPATH}" => find_apex_names_in_classpath_env("DEX2OATBOOTCLASSPATH"),
+            "{SYSTEMSERVERCLASSPATH}" => find_apex_names_in_classpath_env("SYSTEMSERVERCLASSPATH"),
+            _ => vec![apex.name.clone()],
+        })
+        .collect();
+    // Add required APEXes
+    apex_names.extend(MICRODROID_REQUIRED_APEXES.iter().map(|name| name.to_string()));
+    apex_names.sort();
+    apex_names.dedup();
+    apex_names
+}
+
 pub fn add_microdroid_images(
     config: &VirtualMachineAppConfig,
     temporary_directory: &Path,
@@ -202,12 +244,9 @@
     vm_payload_config: &VmPayloadConfig,
     vm_config: &mut VirtualMachineRawConfig,
 ) -> Result<()> {
-    let mut apexes = vm_payload_config.apexes.clone();
-    apexes.extend(
-        MICRODROID_REQUIRED_APEXES.iter().map(|name| ApexConfig { name: name.to_string() }),
-    );
-    apexes.dedup_by(|a, b| a.name == b.name);
-
+    // collect APEX names from config
+    let apexes = collect_apex_names(&vm_payload_config.apexes);
+    info!("Microdroid payload APEXes: {:?}", apexes);
     vm_config.disks.push(make_payload_disk(
         apk_file,
         idsig_file,
@@ -237,3 +276,18 @@
 
     Ok(())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    #[test]
+    fn test_find_apex_names_in_classpath_env() {
+        let key = "TEST_BOOTCLASSPATH";
+        let classpath = "/apex/com.android.foo/javalib/foo.jar:/system/framework/framework.jar:/apex/com.android.bar/javalib/bar.jar";
+        env::set_var(key, classpath);
+        assert_eq!(
+            find_apex_names_in_classpath_env(key),
+            vec!["com.android.foo".to_owned(), "com.android.bar".to_owned()]
+        );
+    }
+}