Merge "Move ramdisk to the new init_boot partition"
diff --git a/authfs/src/fsverity/metadata/Android.bp b/authfs/src/fsverity/metadata/Android.bp
index 39ce515..b155224 100644
--- a/authfs/src/fsverity/metadata/Android.bp
+++ b/authfs/src/fsverity/metadata/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 rust_bindgen {
     name: "libauthfs_fsverity_metadata_bindgen",
     wrapper_src: "metadata.hpp",
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 858b099..3bd96f4 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -272,6 +272,8 @@
             Path::new("/system/framework/telephony-common.jar"),
             Path::new("/system/framework/voip-common.jar"),
             Path::new("/system/etc/boot-image.prof"),
+            Path::new("/system/etc/classpaths/bootclasspath.pb"),
+            Path::new("/system/etc/classpaths/systemserverclasspath.pb"),
             Path::new("/system/etc/dirty-image-objects"),
         ];
 
diff --git a/compos/Android.bp b/compos/Android.bp
index 783ba22..ab55efb 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -3,33 +3,6 @@
 }
 
 rust_binary {
-    name: "pvm_exec",
-    srcs: ["src/pvm_exec.rs"],
-    rustlibs: [
-        "android.system.composd.internal-rust",
-        "compos_aidl_interface-rust",
-        "libandroid_logger",
-        "libanyhow",
-        "libbinder_rpc_unstable_bindgen",
-        "libbinder_rs",
-        "libclap",
-        "libcompos_common",
-        "liblibc",
-        "liblog_rust",
-        "libminijail_rust",
-        "libnix",
-        "libscopeguard",
-    ],
-    prefer_rlib: true,
-    shared_libs: [
-        "libbinder_rpc_unstable",
-    ],
-    apex_available: [
-        "com.android.compos",
-    ],
-}
-
-rust_binary {
     name: "compsvc",
     srcs: ["src/compsvc_main.rs"],
     rustlibs: [
@@ -52,6 +25,7 @@
         "libnix",
         "libodsign_proto_rust",
         "libprotobuf",
+        "libregex",
         "libring",
         "libscopeguard",
     ],
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 395a09b..1a28a18 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -33,15 +33,6 @@
     void initializeSigningKey(in byte[] keyBlob);
 
     /**
-     * Initializes the classpaths necessary for preparing and running compilation.
-     *
-     * TODO(198211396): Implement properly. We can't simply accepting the classpaths from Android
-     * since they are not derived from staged APEX (besides security reasons).
-     */
-    void initializeClasspaths(
-            String bootClasspath, String dex2oatBootClasspath, String systemServerClassPath);
-
-    /**
      * Run odrefresh in the VM context.
      *
      * The execution is based on the VM's APEX mounts, files on Android's /system (by accessing
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 564a380..ea72018 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -46,7 +46,6 @@
         "compos_verify_key",
         "composd",
         "composd_cmd",
-        "pvm_exec", // deprecated
 
         // Used in VM
         "compsvc",
diff --git a/compos/apk/assets/vm_config.json b/compos/apk/assets/vm_config.json
index 3a6eff4..0e97228 100644
--- a/compos/apk/assets/vm_config.json
+++ b/compos/apk/assets/vm_config.json
@@ -18,13 +18,10 @@
       "name": "com.android.compos"
     },
     {
-      "name": "{DEX2OATBOOTCLASSPATH}"
+      "name": "com.android.sdkext"
     },
     {
-      "name": "{BOOTCLASSPATH}"
-    },
-    {
-      "name": "{SYSTEMSERVERCLASSPATH}"
+      "name": "{CLASSPATH}"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/compos/apk/assets/vm_config_staged.json b/compos/apk/assets/vm_config_staged.json
index 9c81e4e..5820982 100644
--- a/compos/apk/assets/vm_config_staged.json
+++ b/compos/apk/assets/vm_config_staged.json
@@ -19,13 +19,10 @@
       "name": "com.android.compos"
     },
     {
-      "name": "{DEX2OATBOOTCLASSPATH}"
+      "name": "com.android.sdkext"
     },
     {
-      "name": "{BOOTCLASSPATH}"
-    },
-    {
-      "name": "{SYSTEMSERVERCLASSPATH}"
+      "name": "{CLASSPATH}"
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/compos/apk/assets/vm_test_config.json b/compos/apk/assets/vm_test_config.json
index 22f8293..16d1037 100644
--- a/compos/apk/assets/vm_test_config.json
+++ b/compos/apk/assets/vm_test_config.json
@@ -1,27 +1,24 @@
 {
-    "version": 1,
-    "os": {
-        "name": "microdroid"
+  "version": 1,
+  "os": {
+    "name": "microdroid"
+  },
+  "task": {
+    "type": "executable",
+    "command": "/apex/com.android.compos/bin/compsvc"
+  },
+  "apexes": [
+    {
+      "name": "com.android.art"
     },
-    "task": {
-        "type": "executable",
-        "command": "/apex/com.android.compos/bin/compsvc"
+    {
+      "name": "com.android.compos"
     },
-    "apexes": [
-        {
-            "name": "com.android.art"
-        },
-        {
-            "name": "com.android.compos"
-        },
-        {
-            "name": "{DEX2OATBOOTCLASSPATH}"
-        },
-        {
-            "name": "{BOOTCLASSPATH}"
-        },
-        {
-            "name": "{SYSTEMSERVERCLASSPATH}"
-        }
-    ]
-}
\ No newline at end of file
+    {
+      "name": "com.android.sdkext"
+    },
+    {
+      "name": "{CLASSPATH}"
+    }
+  ]
+}
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 91a0e61..6946c11 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -29,7 +29,6 @@
     COMPOS_DATA_ROOT, IDSIG_FILE, INSTANCE_IMAGE_FILE, PRIVATE_KEY_BLOB_FILE, PUBLIC_KEY_FILE,
 };
 use log::{info, warn};
-use std::env;
 use std::fs;
 use std::path::{Path, PathBuf};
 
@@ -111,8 +110,7 @@
         // If we get this far then the instance image is valid in the current context (e.g. the
         // current set of APEXes) and the key blob can be successfully decrypted by the VM. So the
         // files have not been tampered with and we're good to go.
-
-        Self::initialize_service(service, &key_blob)?;
+        service.initializeSigningKey(&key_blob).context("Loading signing key")?;
 
         Ok(compos_instance)
     }
@@ -145,27 +143,11 @@
 
         // Unlike when starting an existing instance, we don't need to verify the key, since we
         // just generated it and have it in memory.
-
-        Self::initialize_service(service, &key_data.keyBlob)?;
+        service.initializeSigningKey(&key_data.keyBlob).context("Loading signing key")?;
 
         Ok(compos_instance)
     }
 
-    fn initialize_service(service: &Strong<dyn ICompOsService>, key_blob: &[u8]) -> Result<()> {
-        // Key blob is assumed to be verified/trusted.
-        service.initializeSigningKey(key_blob).context("Loading signing key")?;
-
-        // TODO(198211396): Implement correctly.
-        service
-            .initializeClasspaths(
-                &env::var("BOOTCLASSPATH")?,
-                &env::var("DEX2OATBOOTCLASSPATH")?,
-                &env::var("SYSTEMSERVERCLASSPATH")?,
-            )
-            .context("Initializing *CLASSPATH")?;
-        Ok(())
-    }
-
     fn start_vm(
         &self,
         virtualization_service: &dyn IVirtualizationService,
diff --git a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
index 6918572..6ecccd2 100644
--- a/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
+++ b/compos/service/java/com/android/server/compos/IsolatedCompilationService.java
@@ -132,9 +132,7 @@
             for (String moduleName : moduleNames) {
                 try {
                     StagedApexInfo apexInfo = mPackageNative.getStagedApexInfo(moduleName);
-                    if (apexInfo != null && (apexInfo.hasBootClassPathJars
-                            || apexInfo.hasDex2OatBootClassPathJars
-                            || apexInfo.hasSystemServerClassPathJars)) {
+                    if (apexInfo != null && apexInfo.hasClassPathJars) {
                         Log.i(TAG, "Classpath affecting module updated: " + moduleName);
                         needCompilation = true;
                         break;
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 2ca4dd4..af7a9b4 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -15,12 +15,15 @@
  */
 
 use anyhow::{anyhow, bail, Context, Result};
-use log::{debug, error, info};
+use log::{debug, error, info, warn};
 use minijail::{self, Minijail};
+use regex::Regex;
 use std::env;
+use std::ffi::OsString;
 use std::fs::{read_dir, File};
 use std::os::unix::io::{AsRawFd, RawFd};
 use std::path::{self, Path, PathBuf};
+use std::process::Command;
 
 use crate::artifact_signer::ArtifactSigner;
 use crate::compos_key_service::Signer;
@@ -129,6 +132,8 @@
 
     let staging_dir = mountpoint.join(context.staging_dir_fd.to_string());
 
+    set_classpaths(&android_root)?;
+
     let args = vec![
         "odrefresh".to_string(),
         format!("--zygote-arch={}", context.zygote_arch),
@@ -164,6 +169,53 @@
     Ok(exit_code)
 }
 
+fn set_classpaths(android_root: &Path) -> Result<()> {
+    let export_lines = run_derive_classpath(android_root)?;
+    load_classpath_vars(&export_lines)
+}
+
+fn run_derive_classpath(android_root: &Path) -> Result<String> {
+    let classpaths_root = android_root.join("etc/classpaths");
+
+    let mut bootclasspath_arg = OsString::new();
+    bootclasspath_arg.push("--bootclasspath-fragment=");
+    bootclasspath_arg.push(classpaths_root.join("bootclasspath.pb"));
+
+    let mut systemserverclasspath_arg = OsString::new();
+    systemserverclasspath_arg.push("--systemserverclasspath-fragment=");
+    systemserverclasspath_arg.push(classpaths_root.join("systemserverclasspath.pb"));
+
+    let result = Command::new("/apex/com.android.sdkext/bin/derive_classpath")
+        .arg(bootclasspath_arg)
+        .arg(systemserverclasspath_arg)
+        .arg("/proc/self/fd/1")
+        .output()
+        .context("Failed to run derive_classpath")?;
+
+    if !result.status.success() {
+        bail!("derive_classpath returned {}", result.status);
+    }
+
+    String::from_utf8(result.stdout).context("Converting derive_classpath output")
+}
+
+fn load_classpath_vars(export_lines: &str) -> Result<()> {
+    // Each line should be in the format "export <var name> <value>"
+    let pattern = Regex::new(r"^export ([^ ]+) ([^ ]+)$").context("Failed to construct Regex")?;
+    for line in export_lines.lines() {
+        if let Some(captures) = pattern.captures(line) {
+            let name = &captures[1];
+            let value = &captures[2];
+            // TODO(b/213416778) Don't modify our env, construct a fresh one for odrefresh
+            env::set_var(name, value);
+        } else {
+            warn!("Malformed line from derive_classpath: {}", line);
+        }
+    }
+
+    Ok(())
+}
+
 fn add_artifacts(target_dir: &Path, artifact_signer: &mut ArtifactSigner) -> Result<()> {
     for entry in
         read_dir(&target_dir).with_context(|| format!("Traversing {}", target_dir.display()))?
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 8f1e205..ef3ae2a 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -23,7 +23,6 @@
 use compos_common::binder::to_binder_result;
 use log::warn;
 use std::default::Default;
-use std::env;
 use std::path::PathBuf;
 use std::sync::RwLock;
 
@@ -95,19 +94,6 @@
         }
     }
 
-    fn initializeClasspaths(
-        &self,
-        boot_classpath: &str,
-        dex2oat_boot_classpath: &str,
-        system_server_classpath: &str,
-    ) -> BinderResult<()> {
-        // TODO(198211396): Implement correctly.
-        env::set_var("BOOTCLASSPATH", boot_classpath);
-        env::set_var("DEX2OATBOOTCLASSPATH", dex2oat_boot_classpath);
-        env::set_var("SYSTEMSERVERCLASSPATH", system_server_classpath);
-        Ok(())
-    }
-
     fn odrefresh(
         &self,
         system_dir_fd: i32,
diff --git a/compos/src/pvm_exec.rs b/compos/src/pvm_exec.rs
deleted file mode 100644
index 3068c66..0000000
--- a/compos/src/pvm_exec.rs
+++ /dev/null
@@ -1,245 +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.
- */
-
-//! pvm_exec is a proxy/wrapper command to run compilation task remotely. The most important task
-//! for this program is to run a `fd_server` that serves remote file read/write requests.
-//!
-//! It currently works as a command line wrapper to make it easy to schedule an existing dex2oat
-//! task to run in the VM.
-//!
-//! Example:
-//! $ adb shell exec 3</input/dex 4<>/output/oat ... pvm_exec --in-fd 3 --out-fd 4 -- dex2oat64 ...
-//!
-//! Note the immediate argument "dex2oat64" right after "--" is not really used. It is only for
-//! ergonomics.
-
-use anyhow::{bail, Context, Result};
-use binder::unstable_api::{new_spibinder, AIBinder};
-use binder::FromIBinder;
-use clap::{value_t, App, Arg};
-use log::{debug, error, warn};
-use minijail::Minijail;
-use nix::fcntl::{fcntl, FcntlArg::F_GETFD, OFlag};
-use nix::unistd::pipe2;
-use std::fs::File;
-use std::io::Read;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
-use std::path::Path;
-use std::process::exit;
-
-use android_system_composd_internal::{
-    aidl::android::system::composd::internal::ICompilationInternal::ICompilationInternal,
-    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 ICompilationInternal>> {
-    wait_for_interface::<dyn ICompilationInternal>("android.system.composd.internal")
-        .context("Failed to find ICompilationInternal")
-}
-
-fn get_rpc_binder(cid: u32) -> Result<Strong<dyn ICompOsService>> {
-    // 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, COMPOS_VSOCK_PORT) as *mut AIBinder
-        )
-    };
-    if let Some(ibinder) = ibinder {
-        <dyn ICompOsService>::try_from(ibinder).context("Cannot connect to RPC service")
-    } else {
-        bail!("Invalid raw AIBinder")
-    }
-}
-
-fn spawn_fd_server(
-    fd_annotation: &FdAnnotation,
-    ready_file: File,
-    debuggable: bool,
-) -> Result<Minijail> {
-    let mut inheritable_fds = if debuggable {
-        vec![1, 2] // inherit/redirect stdout/stderr for debugging
-    } else {
-        vec![]
-    };
-
-    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 is_fd_valid(fd: RawFd) -> Result<bool> {
-    let retval = fcntl(fd, F_GETFD)?;
-    Ok(retval >= 0)
-}
-
-fn parse_arg_fd(arg: &str) -> Result<RawFd> {
-    let fd = arg.parse::<RawFd>()?;
-    if !is_fd_valid(fd)? {
-        bail!("Bad FD: {}", fd);
-    }
-    Ok(fd)
-}
-
-struct Config {
-    args: Vec<String>,
-    fd_annotation: FdAnnotation,
-    cid: u32,
-    debuggable: bool,
-}
-
-fn parse_args() -> Result<Config> {
-    #[rustfmt::skip]
-    let matches = App::new("pvm_exec")
-        .arg(Arg::with_name("in-fd")
-             .long("in-fd")
-             .takes_value(true)
-             .multiple(true)
-             .use_delimiter(true))
-        .arg(Arg::with_name("out-fd")
-             .long("out-fd")
-             .takes_value(true)
-             .multiple(true)
-             .use_delimiter(true))
-        .arg(Arg::with_name("cid")
-             .takes_value(true)
-             .required(true)
-             .long("cid"))
-        .arg(Arg::with_name("debug")
-             .long("debug"))
-        .arg(Arg::with_name("args")
-             .last(true)
-             .required(true)
-             .multiple(true))
-        .get_matches();
-
-    let results: Result<Vec<_>> =
-        matches.values_of("in-fd").unwrap_or_default().map(parse_arg_fd).collect();
-    let input_fds = results?;
-
-    let results: Result<Vec<_>> =
-        matches.values_of("out-fd").unwrap_or_default().map(parse_arg_fd).collect();
-    let output_fds = results?;
-
-    let args: Vec<_> = matches.values_of("args").unwrap().map(|s| s.to_string()).collect();
-    let cid = value_t!(matches, "cid", i32)? as u32;
-    let debuggable = matches.is_present("debug");
-
-    Ok(Config { args, fd_annotation: FdAnnotation { input_fds, output_fds }, cid, debuggable })
-}
-
-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_main() -> Result<()> {
-    // 1. Parse the command line arguments for collect execution data.
-    let Config { args, fd_annotation, cid, debuggable } = parse_args()?;
-
-    // 2. Spawn and configure 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, debuggable)?;
-    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);
-            }
-        }
-    });
-
-    // 3. Send the command line args to the remote to execute.
-    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_cmd(&args, &fd_annotation)
-    } else {
-        // Call directly into the VM
-        let compos_vm = get_rpc_binder(cid)?;
-        wait_for_fd_server_ready(ready_read_fd)?;
-        compos_vm.compile_cmd(&args, &fd_annotation)
-    };
-    let result = result.context("Binder call failed")?;
-
-    // TODO: store/use the signature
-    debug!(
-        "Signature length: oat {}, vdex {}, image {}",
-        result.oatSignature.len(),
-        result.vdexSignature.len(),
-        result.imageSignature.len()
-    );
-
-    // Be explicit about the lifetime, which should last at least until the task is finished.
-    drop(fd_server_lifetime);
-
-    if result.exitCode != 0 {
-        error!("remote execution failed with exit code {}", result.exitCode);
-        exit(result.exitCode as i32);
-    }
-    Ok(())
-}
-
-fn main() {
-    let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
-    let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
-    android_logger::init_once(
-        android_logger::Config::default().with_tag("pvm_exec").with_min_level(log_level),
-    );
-
-    // Make sure we log and indicate failure if we were unable to run the command and get its exit
-    // code.
-    if let Err(e) = try_main() {
-        error!("{:?}", e);
-        std::process::exit(-1)
-    }
-}
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 8906361..7b027de 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -126,8 +126,6 @@
     private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception {
         return android.runForResultWithTimeout(
                 ODREFRESH_TIMEOUT_MS,
-                // TODO(b/210472252): Remove this when the VM handles STANDALONE_SYSTEMSERVER_JARS
-                "STANDALONE_SYSTEMSERVER_JARS=",
                 ODREFRESH_BIN,
                 "--dalvik-cache=" + TEST_ARTIFACTS_DIR,
                 command);
diff --git a/microdroid/build.prop b/microdroid/build.prop
index 4ae50c0..8cbabff 100644
--- a/microdroid/build.prop
+++ b/microdroid/build.prop
@@ -4,8 +4,10 @@
 service.adb.listen_addrs=vsock:5555
 
 # TODO(b/189164487): support build related properties
-ro.build.version.release=11
-ro.build.version.security_patch=2021-07-05
+ro.build.version.codename=Tiramisu
+ro.build.version.release=12
+ro.build.version.sdk=32
+ro.build.version.security_patch=2021-12-05
 
 # Payload metadata partition
 apexd.payload_metadata.path=/dev/block/by-name/payload-metadata
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index 277432b..c3ab39a 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -36,6 +36,7 @@
         "libmicrodroid_metadata",
         "libmicrodroid_payload_config",
         "libonce_cell",
+        "libregex",
         "librustutils",
         "libselinux_bindgen",
         "libserde",
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 8f7a69a..a8c22cd 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -22,16 +22,18 @@
 use android_system_virtualizationservice::binder::ParcelFileDescriptor;
 use anyhow::{anyhow, bail, Context, Result};
 use binder::wait_for_interface;
-use log::{error, info};
+use log::{info, warn};
 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 regex::Regex;
 use serde::Deserialize;
 use serde_xml_rs::from_reader;
-use std::env;
+use std::collections::HashSet;
 use std::fs::{File, OpenOptions};
 use std::path::{Path, PathBuf};
+use std::process::Command;
 use vmconfig::open_parcel_file;
 
 /// The list of APEXes which microdroid requires.
@@ -58,11 +60,7 @@
     path: PathBuf,
 
     #[serde(default)]
-    boot_classpath: bool,
-    #[serde(default)]
-    systemserver_classpath: bool,
-    #[serde(default)]
-    dex2oatboot_classpath: bool,
+    has_classpath_jar: bool,
 }
 
 impl ApexInfoList {
@@ -75,19 +73,16 @@
             let mut apex_info_list: ApexInfoList = from_reader(apex_info_list)
                 .context(format!("Failed to parse {}", APEX_INFO_LIST_PATH))?;
 
-            // For active APEXes, we refer env variables to see if it contributes to classpath
-            let boot_classpath_apexes = find_apex_names_in_classpath_env("BOOTCLASSPATH");
-            let systemserver_classpath_apexes =
-                find_apex_names_in_classpath_env("SYSTEMSERVERCLASSPATH");
-            let dex2oatboot_classpath_apexes =
-                find_apex_names_in_classpath_env("DEX2OATBOOTCLASSPATH");
+            // For active APEXes, we run derive_classpath and parse its output to see if it
+            // contributes to the classpath(s). (This allows us to handle any new classpath env
+            // vars seamlessly.)
+            let classpath_vars = run_derive_classpath()?;
+            let classpath_apexes = find_apex_names_in_classpath(&classpath_vars)?;
+
             for apex_info in apex_info_list.list.iter_mut() {
-                apex_info.boot_classpath = boot_classpath_apexes.contains(&apex_info.name);
-                apex_info.systemserver_classpath =
-                    systemserver_classpath_apexes.contains(&apex_info.name);
-                apex_info.dex2oatboot_classpath =
-                    dex2oatboot_classpath_apexes.contains(&apex_info.name);
+                apex_info.has_classpath_jar = classpath_apexes.contains(&apex_info.name);
             }
+
             Ok(apex_info_list)
         })
     }
@@ -133,11 +128,7 @@
                     let staged_apex_info = pm.getStagedApexInfo(&apex_info.name)?;
                     if let Some(staged_apex_info) = staged_apex_info {
                         apex_info.path = PathBuf::from(staged_apex_info.diskImagePath);
-                        apex_info.boot_classpath = staged_apex_info.hasBootClassPathJars;
-                        apex_info.systemserver_classpath =
-                            staged_apex_info.hasSystemServerClassPathJars;
-                        apex_info.dex2oatboot_classpath =
-                            staged_apex_info.hasDex2OatBootClassPathJars;
+                        apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
                     }
                 }
             }
@@ -269,22 +260,41 @@
     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()
+fn run_derive_classpath() -> Result<String> {
+    let result = Command::new("/apex/com.android.sdkext/bin/derive_classpath")
+        .arg("/proc/self/fd/1")
+        .output()
+        .context("Failed to run derive_classpath")?;
+
+    if !result.status.success() {
+        bail!("derive_classpath returned {}", result.status);
+    }
+
+    String::from_utf8(result.stdout).context("Converting derive_classpath output")
+}
+
+fn find_apex_names_in_classpath(classpath_vars: &str) -> Result<HashSet<String>> {
+    // Each line should be in the format "export <var name> <paths>", where <paths> is a
+    // colon-separated list of paths to JARs. We don't care about the var names, and we're only
+    // interested in paths that look like "/apex/<apex name>/<anything>" so we know which APEXes
+    // contribute to at least one var.
+    let mut apexes = HashSet::new();
+
+    let pattern = Regex::new(r"^export [^ ]+ ([^ ]+)$").context("Failed to construct Regex")?;
+    for line in classpath_vars.lines() {
+        if let Some(captures) = pattern.captures(line) {
+            if let Some(paths) = captures.get(1) {
+                apexes.extend(paths.as_str().split(':').filter_map(|path| {
+                    let path = path.strip_prefix("/apex/")?;
+                    Some(path[..path.find('/')?].to_owned())
+                }));
+                continue;
+            }
+        }
+        warn!("Malformed line from derive_classpath: {}", line);
+    }
+
+    Ok(apexes)
 }
 
 // Collect APEX names from config
@@ -293,17 +303,13 @@
     apexes: &[ApexConfig],
     debug_level: DebugLevel,
 ) -> Vec<String> {
-    // Process pseudo names like "{BOOTCLASSPATH}".
+    // Process pseudo names like "{CLASSPATH}".
     // 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
+    // - {CLASSPATH}: represents APEXes contributing to any derive_classpath environment variable
     let mut apex_names: Vec<String> = apexes
         .iter()
         .flat_map(|apex| match apex.name.as_str() {
-            "{BOOTCLASSPATH}" => apex_list.get_matching(|apex| apex.boot_classpath),
-            "{DEX2OATBOOTCLASSPATH}" => apex_list.get_matching(|apex| apex.dex2oatboot_classpath),
-            "{SYSTEMSERVERCLASSPATH}" => apex_list.get_matching(|apex| apex.systemserver_classpath),
+            "{CLASSPATH}" => apex_list.get_matching(|apex| apex.has_classpath_jar),
             _ => vec![apex.name.clone()],
         })
         .collect();
@@ -369,13 +375,16 @@
 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()]
-        );
+    fn test_find_apex_names_in_classpath() {
+        let vars = r#"
+export FOO /apex/unterminated
+export BAR /apex/valid.apex/something
+wrong
+export EMPTY
+export OTHER /foo/bar:/baz:/apex/second.valid.apex/:gibberish:"#;
+        let expected = vec!["valid.apex", "second.valid.apex"];
+        let expected: HashSet<_> = expected.into_iter().map(ToString::to_string).collect();
+
+        assert_eq!(find_apex_names_in_classpath(vars).unwrap(), expected);
     }
 }