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()]
+ );
+ }
+}