Support FDs for class loader context dex files

When compiling secondary dex files, dex2oat/dexoptanalyzer must open
all dex files in the given class loader context. However, these tools
do not have the SELinux permission to open app data files and instead
rely on installd to open them and pass file descriptors via command
line arguments.

This patch extends ClassLoaderContext::OpenDexFiles to support opening
dex files from a provided list of FDs, assuming the order corresponds
to the flattened class loader context. FDs can be passed to dex2oat/
dexoptanalyzer using a new '--class-loader-context-fds=' command line
argument. The format is a colon-separated list of integers.

dexoptanalyzer is also extended with a separate mode in which
dexopt-needed analysis is not performed, only the class loader context
is flattened and list of its dex files is printed to standard output
as a colon-separated list of paths. This mode is enabled with
'--flatten-class-loader-context' and is used by installd to obtain a
list of files it should open for dex2oat/dexoptanalyzer.

Bug: 126674985
Test: atest installd_dexopt_test
Change-Id: I46a671c90d14ad8615508c106a88ac1ee8a4ef28
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index c5988f6..61843f4 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -365,7 +365,9 @@
 
 // Opens requested class path files and appends them to opened_dex_files. If the dex files have
 // been stripped, this opens them from their oat files (which get added to opened_oat_files).
-bool ClassLoaderContext::OpenDexFiles(InstructionSet isa, const std::string& classpath_dir) {
+bool ClassLoaderContext::OpenDexFiles(InstructionSet isa,
+                                      const std::string& classpath_dir,
+                                      const std::vector<int>& fds) {
   if (dex_files_open_attempted_) {
     // Do not attempt to re-open the files if we already tried.
     return dex_files_open_result_;
@@ -388,6 +390,7 @@
   std::vector<ClassLoaderInfo*> work_list;
   CHECK(class_loader_chain_ != nullptr);
   work_list.push_back(class_loader_chain_.get());
+  size_t dex_file_index = 0;
   while (!work_list.empty()) {
     ClassLoaderInfo* info = work_list.back();
     work_list.pop_back();
@@ -399,34 +402,59 @@
         location = classpath_dir + (classpath_dir.back() == '/' ? "" : "/") + location;
       }
 
+      // If file descriptors were provided for the class loader context dex paths,
+      // get the descriptor which correponds to this dex path. We assume the `fds`
+      // vector follows the same order as a flattened class loader context.
+      int fd = -1;
+      if (!fds.empty()) {
+        if (dex_file_index >= fds.size()) {
+          LOG(WARNING) << "Number of FDs is smaller than number of dex files in the context";
+          dex_files_open_result_ = false;
+          return false;
+        }
+
+        fd = fds[dex_file_index++];
+        DCHECK_GE(fd, 0);
+      }
+
       std::string error_msg;
       // When opening the dex files from the context we expect their checksum to match their
       // contents. So pass true to verify_checksum.
-      if (!dex_file_loader.Open(location.c_str(),
-                                location.c_str(),
-                                Runtime::Current()->IsVerificationEnabled(),
-                                /*verify_checksum=*/ true,
-                                &error_msg,
-                                &info->opened_dex_files)) {
-        // If we fail to open the dex file because it's been stripped, try to open the dex file
-        // from its corresponding oat file.
-        // This could happen when we need to recompile a pre-build whose dex code has been stripped.
-        // (for example, if the pre-build is only quicken and we want to re-compile it
-        // speed-profile).
-        // TODO(calin): Use the vdex directly instead of going through the oat file.
-        OatFileAssistant oat_file_assistant(location.c_str(), isa, false);
-        std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile());
-        std::vector<std::unique_ptr<const DexFile>> oat_dex_files;
-        if (oat_file != nullptr &&
-            OatFileAssistant::LoadDexFiles(*oat_file, location, &oat_dex_files)) {
-          info->opened_oat_files.push_back(std::move(oat_file));
-          info->opened_dex_files.insert(info->opened_dex_files.end(),
-                                        std::make_move_iterator(oat_dex_files.begin()),
-                                        std::make_move_iterator(oat_dex_files.end()));
-        } else {
-          LOG(WARNING) << "Could not open dex files from location: " << location;
-          dex_files_open_result_ = false;
+      if (fd < 0) {
+        if (!dex_file_loader.Open(location.c_str(),
+                                  location.c_str(),
+                                  Runtime::Current()->IsVerificationEnabled(),
+                                  /*verify_checksum=*/ true,
+                                  &error_msg,
+                                  &info->opened_dex_files)) {
+          // If we fail to open the dex file because it's been stripped, try to
+          // open the dex file from its corresponding oat file.
+          // This could happen when we need to recompile a pre-build whose dex
+          // code has been stripped (for example, if the pre-build is only
+          // quicken and we want to re-compile it speed-profile).
+          // TODO(calin): Use the vdex directly instead of going through the oat file.
+          OatFileAssistant oat_file_assistant(location.c_str(), isa, false);
+          std::unique_ptr<OatFile> oat_file(oat_file_assistant.GetBestOatFile());
+          std::vector<std::unique_ptr<const DexFile>> oat_dex_files;
+          if (oat_file != nullptr &&
+              OatFileAssistant::LoadDexFiles(*oat_file, location, &oat_dex_files)) {
+            info->opened_oat_files.push_back(std::move(oat_file));
+            info->opened_dex_files.insert(info->opened_dex_files.end(),
+                                          std::make_move_iterator(oat_dex_files.begin()),
+                                          std::make_move_iterator(oat_dex_files.end()));
+          } else {
+            LOG(WARNING) << "Could not open dex files from location: " << location;
+            dex_files_open_result_ = false;
+          }
         }
+      } else if (!dex_file_loader.Open(fd,
+                                       location.c_str(),
+                                       Runtime::Current()->IsVerificationEnabled(),
+                                       /*verify_checksum=*/ true,
+                                       &error_msg,
+                                       &info->opened_dex_files)) {
+        LOG(WARNING) << "Could not open dex files from fd " << fd << " for location: " << location;
+        dex_files_open_result_ = false;
       }
     }
 
@@ -450,6 +478,14 @@
     AddToWorkList(info, work_list);
   }
 
+  // Check that if file descriptors were provided, there were exactly as many
+  // as we have encountered while iterating over this class loader context.
+  if (dex_file_index != fds.size()) {
+    LOG(WARNING) << fds.size() << " FDs provided but only " << dex_file_index
+        << " dex files are in the class loader context";
+    dex_files_open_result_ = false;
+  }
+
   return dex_files_open_result_;
 }
 
@@ -759,6 +795,25 @@
   return result;
 }
 
+std::string ClassLoaderContext::FlattenDexPaths() const {
+  if (class_loader_chain_ == nullptr) {
+    return "";
+  }
+
+  std::vector<std::string> result;
+  std::vector<ClassLoaderInfo*> work_list;
+  work_list.push_back(class_loader_chain_.get());
+  while (!work_list.empty()) {
+    ClassLoaderInfo* info = work_list.back();
+    work_list.pop_back();
+    for (const std::string& dex_path : info->classpath) {
+      result.push_back(dex_path);
+    }
+    AddToWorkList(info, work_list);
+  }
+  return FlattenClasspath(result);
+}
+
 const char* ClassLoaderContext::GetClassLoaderTypeName(ClassLoaderType type) {
   switch (type) {
     case kPathClassLoader: return kPathClassLoaderString;