Add runtime option -Xbootclasspathfds: for pre-opened fds

The new option allows the client to pass a pre-opened fds to the
runtime. The number of elements must match the number of BCP jars
specified in -Xbootclasspath. An fd of negative number is a valid
option, in such case the runtime will still open the jar in the
corresponding path in -Xbootclasspath.

Example: -Xbootclasspathfds:10:11:-1:12

The option is currently only used in "unstarted runtime", but will also
be used elsewhere in the follow-up changes.

Bug: 187327262
Test: patch odrefresh to use the option, no longer seeing such openat(2)
Test: m test-art-host-gtest

Change-Id: I1bebbd80136419c03ac1309a8cb8229a0fd69838
diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc
index cfb79e5..e377308 100644
--- a/dex2oat/dex2oat_image_test.cc
+++ b/dex2oat/dex2oat_image_test.cc
@@ -422,6 +422,7 @@
     ScopedObjectAccess soa(Thread::Current());
     return gc::space::ImageSpace::LoadBootImage(/*boot_class_path=*/ boot_class_path,
                                                 /*boot_class_path_locations=*/ libcore_dex_files,
+                                                /*boot_class_path_fds=*/ std::vector<int>(),
                                                 android::base::Split(image_location, ":"),
                                                 kRuntimeISA,
                                                 relocate,
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index a6fcd02..92baff7 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -368,6 +368,7 @@
     std::string error_msg;
     const std::vector<std::string>& bcp = runtime->GetBootClassPath();
     const std::vector<std::string>& bcp_locations = runtime->GetBootClassPathLocations();
+    const std::vector<int>& bcp_fds = runtime->GetBootClassPathFds();
     const std::vector<std::string>& image_locations = runtime->GetImageLocations();
     const std::string bcp_locations_path = android::base::Join(bcp_locations, ':');
     if (!ImageSpace::VerifyBootClassPathChecksums(checksums,
@@ -375,6 +376,7 @@
                                                   ArrayRef<const std::string>(image_locations),
                                                   ArrayRef<const std::string>(bcp_locations),
                                                   ArrayRef<const std::string>(bcp),
+                                                  ArrayRef<const int>(bcp_fds),
                                                   runtime->GetInstructionSet(),
                                                   &error_msg)) {
       LOG(INFO) << "Failed to verify boot class path checksums: " << error_msg;
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index abde17c..f0b31cc 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -176,6 +176,7 @@
         ArrayRef<const std::string>(&image_location, 1),
         ArrayRef<const std::string>(Runtime::Current()->GetBootClassPathLocations()),
         ArrayRef<const std::string>(Runtime::Current()->GetBootClassPath()),
+        ArrayRef<const int>(Runtime::Current()->GetBootClassPathFds()),
         kRuntimeISA,
         &error_msg);
     ASSERT_EQ(!with_alternate_image, match) << error_msg;
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index e84c436..4676ded 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -262,6 +262,7 @@
            size_t non_moving_space_capacity,
            const std::vector<std::string>& boot_class_path,
            const std::vector<std::string>& boot_class_path_locations,
+           const std::vector<int>& boot_class_path_fds,
            const std::vector<std::string>& image_file_names,
            const InstructionSet image_instruction_set,
            CollectorType foreground_collector_type,
@@ -462,6 +463,7 @@
   MemMap heap_reservation;
   if (space::ImageSpace::LoadBootImage(boot_class_path,
                                        boot_class_path_locations,
+                                       boot_class_path_fds,
                                        image_file_names,
                                        image_instruction_set,
                                        runtime->ShouldRelocate(),
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 047af16..f9dd83d 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -201,6 +201,7 @@
        size_t non_moving_space_capacity,
        const std::vector<std::string>& boot_class_path,
        const std::vector<std::string>& boot_class_path_locations,
+       const std::vector<int>& boot_class_path_fds,
        const std::vector<std::string>& image_file_names,
        InstructionSet image_instruction_set,
        CollectorType foreground_collector_type,
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 750256d..bd34df4 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -1413,10 +1413,12 @@
 
   BootImageLayout(ArrayRef<const std::string> image_locations,
                   ArrayRef<const std::string> boot_class_path,
-                  ArrayRef<const std::string> boot_class_path_locations)
+                  ArrayRef<const std::string> boot_class_path_locations,
+                  ArrayRef<const int> boot_class_path_fds)
      : image_locations_(image_locations),
        boot_class_path_(boot_class_path),
-       boot_class_path_locations_(boot_class_path_locations) {}
+       boot_class_path_locations_(boot_class_path_locations),
+       boot_class_path_fds_(boot_class_path_fds) {}
 
   std::string GetPrimaryImageLocation();
 
@@ -1530,6 +1532,7 @@
   ArrayRef<const std::string> image_locations_;
   ArrayRef<const std::string> boot_class_path_;
   ArrayRef<const std::string> boot_class_path_locations_;
+  ArrayRef<const int> boot_class_path_fds_;
 
   std::vector<ImageChunk> chunks_;
   uint32_t base_address_ = 0u;
@@ -2219,12 +2222,14 @@
  public:
   BootImageLoader(const std::vector<std::string>& boot_class_path,
                   const std::vector<std::string>& boot_class_path_locations,
+                  const std::vector<int>& boot_class_path_fds,
                   const std::vector<std::string>& image_locations,
                   InstructionSet image_isa,
                   bool relocate,
                   bool executable)
       : boot_class_path_(boot_class_path),
         boot_class_path_locations_(boot_class_path_locations),
+        boot_class_path_fds_(boot_class_path_fds),
         image_locations_(image_locations),
         image_isa_(image_isa),
         relocate_(relocate),
@@ -2233,7 +2238,10 @@
   }
 
   void FindImageFiles() {
-    BootImageLayout layout(image_locations_, boot_class_path_, boot_class_path_locations_);
+    BootImageLayout layout(image_locations_,
+                           boot_class_path_,
+                           boot_class_path_locations_,
+                           boot_class_path_fds_);
     std::string image_location = layout.GetPrimaryImageLocation();
     std::string system_filename;
     bool found_image = FindImageFilenameImpl(image_location.c_str(),
@@ -3091,6 +3099,7 @@
 
   const ArrayRef<const std::string> boot_class_path_;
   const ArrayRef<const std::string> boot_class_path_locations_;
+  const ArrayRef<const int> boot_class_path_fds_;
   const ArrayRef<const std::string> image_locations_;
   const InstructionSet image_isa_;
   const bool relocate_;
@@ -3105,7 +3114,10 @@
     /*out*/std::string* error_msg) {
   TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image));
 
-  BootImageLayout layout(image_locations_, boot_class_path_, boot_class_path_locations_);
+  BootImageLayout layout(image_locations_,
+                         boot_class_path_,
+                         boot_class_path_locations_,
+                         boot_class_path_fds_);
   if (!layout.LoadFromSystem(image_isa_, error_msg)) {
     return false;
   }
@@ -3132,7 +3144,8 @@
   Runtime* runtime = Runtime::Current();
   BootImageLayout layout(ArrayRef<const std::string>(runtime->GetImageLocations()),
                          ArrayRef<const std::string>(runtime->GetBootClassPath()),
-                         ArrayRef<const std::string>(runtime->GetBootClassPathLocations()));
+                         ArrayRef<const std::string>(runtime->GetBootClassPathLocations()),
+                         ArrayRef<const int>(runtime->GetBootClassPathFds()));
   const std::string image_location = layout.GetPrimaryImageLocation();
   std::unique_ptr<ImageHeader> image_header;
   std::string error_msg;
@@ -3154,6 +3167,7 @@
 bool ImageSpace::LoadBootImage(
     const std::vector<std::string>& boot_class_path,
     const std::vector<std::string>& boot_class_path_locations,
+    const std::vector<int>& boot_class_path_fds,
     const std::vector<std::string>& image_locations,
     const InstructionSet image_isa,
     bool relocate,
@@ -3175,6 +3189,7 @@
 
   BootImageLoader loader(boot_class_path,
                          boot_class_path_locations,
+                         boot_class_path_fds,
                          image_locations,
                          image_isa,
                          relocate,
@@ -3420,6 +3435,7 @@
                                               ArrayRef<const std::string> image_locations,
                                               ArrayRef<const std::string> boot_class_path_locations,
                                               ArrayRef<const std::string> boot_class_path,
+                                              ArrayRef<const int> boot_class_path_fds,
                                               InstructionSet image_isa,
                                               /*out*/std::string* error_msg) {
   if (oat_checksums.empty() || oat_boot_class_path.empty()) {
@@ -3437,10 +3453,15 @@
 
   size_t bcp_pos = 0u;
   if (StartsWith(oat_checksums, "i")) {
-    // Use only the matching part of the BCP for validation.
+    // Use only the matching part of the BCP for validation.  FDs are optional, so only pass the
+    // sub-array if provided.
+    ArrayRef<const int> bcp_fds = boot_class_path_fds.empty()
+        ? ArrayRef<const int>()
+        : boot_class_path_fds.SubArray(/*pos=*/ 0u, bcp_size);
     BootImageLayout layout(image_locations,
                            boot_class_path.SubArray(/*pos=*/ 0u, bcp_size),
-                           boot_class_path_locations.SubArray(/*pos=*/ 0u, bcp_size));
+                           boot_class_path_locations.SubArray(/*pos=*/ 0u, bcp_size),
+                           bcp_fds);
     std::string primary_image_location = layout.GetPrimaryImageLocation();
     std::string system_filename;
     bool has_system = false;
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index aa93247..c8879cb 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -124,6 +124,7 @@
   static bool LoadBootImage(
       const std::vector<std::string>& boot_class_path,
       const std::vector<std::string>& boot_class_path_locations,
+      const std::vector<int>& boot_class_path_fds,
       const std::vector<std::string>& image_locations,
       const InstructionSet image_isa,
       bool relocate,
@@ -236,6 +237,7 @@
                                            ArrayRef<const std::string> image_locations,
                                            ArrayRef<const std::string> boot_class_path_locations,
                                            ArrayRef<const std::string> boot_class_path,
+                                           ArrayRef<const int> boot_class_path_fds,
                                            InstructionSet image_isa,
                                            /*out*/std::string* error_msg);
 
diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc
index 1a53efa..58be15d 100644
--- a/runtime/gc/space/image_space_test.cc
+++ b/runtime/gc/space/image_space_test.cc
@@ -136,6 +136,7 @@
     extra_reservation = MemMap::Invalid();
     return ImageSpace::LoadBootImage(bcp,
                                      bcp_locations,
+                                     /*boot_class_path_fds=*/ std::vector<int>(),
                                      full_image_locations,
                                      kRuntimeISA,
                                      /*relocate=*/ false,
@@ -336,6 +337,7 @@
         ArrayRef<const std::string>(runtime->GetImageLocations()),
         ArrayRef<const std::string>(bcp_locations),
         ArrayRef<const std::string>(bcp),
+        /*boot_class_path_fds=*/ ArrayRef<const int>(),
         kRuntimeISA,
         &error_msg);
   };
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 73fffe8..9ed3b5e 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -485,12 +485,18 @@
 }
 
 static MemMap FindAndExtractEntry(const std::string& jar_file,
+                                  int jar_fd,
                                   const char* entry_name,
                                   size_t* size,
                                   std::string* error_msg) {
   CHECK(size != nullptr);
 
-  std::unique_ptr<ZipArchive> zip_archive(ZipArchive::Open(jar_file.c_str(), error_msg));
+  std::unique_ptr<ZipArchive> zip_archive;
+  if (jar_fd >= 0) {
+    zip_archive.reset(ZipArchive::OpenFromFd(jar_fd, jar_file.c_str(), error_msg));
+  } else {
+    zip_archive.reset(ZipArchive::Open(jar_file.c_str(), error_msg));
+  }
   if (zip_archive == nullptr) {
     return MemMap::Invalid();
   }
@@ -540,12 +546,18 @@
     return;
   }
 
+  const std::vector<int>& boot_class_path_fds = Runtime::Current()->GetBootClassPathFds();
+  DCHECK(boot_class_path_fds.empty() || boot_class_path_fds.size() == boot_class_path.size());
+
   MemMap mem_map;
   size_t map_size;
   std::string last_error_msg;  // Only store the last message (we could concatenate).
 
-  for (const std::string& jar_file : boot_class_path) {
-    mem_map = FindAndExtractEntry(jar_file, resource_cstr, &map_size, &last_error_msg);
+  bool has_bcp_fds = !boot_class_path_fds.empty();
+  for (size_t i = 0; i < boot_class_path.size(); ++i) {
+    const std::string& jar_file = boot_class_path[i];
+    const int jar_fd = has_bcp_fds ? boot_class_path_fds[i] : -1;
+    mem_map = FindAndExtractEntry(jar_file, jar_fd, resource_cstr, &map_size, &last_error_msg);
     if (mem_map.IsValid()) {
       break;
     }
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index d176eb6..f323647 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -652,6 +652,7 @@
       ArrayRef<const std::string>(runtime->GetImageLocations()),
       ArrayRef<const std::string>(runtime->GetBootClassPathLocations()),
       ArrayRef<const std::string>(runtime->GetBootClassPath()),
+      ArrayRef<const int>(runtime->GetBootClassPathFds()),
       isa_,
       &error_msg);
   if (!result) {
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 38552c6..cbee356 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -112,6 +112,9 @@
       .Define("-Xbootclasspath:_")
           .WithType<ParseStringList<':'>>()  // std::vector<std::string>, split by :
           .IntoKey(M::BootClassPath)
+      .Define("-Xbootclasspathfds:_")
+          .WithType<ParseIntList<':'>>()
+          .IntoKey(M::BootClassPathFds)
       .Define("-Xcheck:jni")
           .IntoKey(M::CheckJni)
       .Define("-Xms_")
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 35b7055..d54ef6b 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -1394,6 +1394,13 @@
     }
   }
 
+  boot_class_path_fds_ = runtime_options.ReleaseOrDefault(Opt::BootClassPathFds);
+  if (!boot_class_path_fds_.empty() && boot_class_path_fds_.size() != boot_class_path_.size()) {
+    LOG(ERROR) << "Number of FDs specified in -Xbootclasspathfds must match the number of JARs in "
+               << "-Xbootclasspath.";
+    return false;
+  }
+
   class_path_string_ = runtime_options.ReleaseOrDefault(Opt::ClassPath);
   properties_ = runtime_options.ReleaseOrDefault(Opt::PropertiesList);
 
@@ -1517,6 +1524,7 @@
                        runtime_options.GetOrDefault(Opt::NonMovingSpaceCapacity),
                        GetBootClassPath(),
                        GetBootClassPathLocations(),
+                       GetBootClassPathFds(),
                        image_locations_,
                        instruction_set_,
                        // Override the collector type to CC if the read barrier config.
diff --git a/runtime/runtime.h b/runtime/runtime.h
index cdaf1ed..f399849 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -286,6 +286,10 @@
     return boot_class_path_locations_.empty() ? boot_class_path_ : boot_class_path_locations_;
   }
 
+  const std::vector<int>& GetBootClassPathFds() const {
+    return boot_class_path_fds_;
+  }
+
   const std::string& GetClassPathString() const {
     return class_path_string_;
   }
@@ -1105,6 +1109,7 @@
 
   std::vector<std::string> boot_class_path_;
   std::vector<std::string> boot_class_path_locations_;
+  std::vector<int> boot_class_path_fds_;
   std::string class_path_string_;
   std::vector<std::string> properties_;
 
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 7be2fa4..3717fbf 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -40,6 +40,7 @@
 RUNTIME_OPTIONS_KEY (Unit,                ShowVersion)
 RUNTIME_OPTIONS_KEY (ParseStringList<':'>,BootClassPath)           // std::vector<std::string>
 RUNTIME_OPTIONS_KEY (ParseStringList<':'>,BootClassPathLocations)  // std::vector<std::string>
+RUNTIME_OPTIONS_KEY (ParseIntList<':'>,   BootClassPathFds)        // std::vector<int>
 RUNTIME_OPTIONS_KEY (std::string,         ClassPath)
 RUNTIME_OPTIONS_KEY (ParseStringList<':'>,Image)
 RUNTIME_OPTIONS_KEY (Unit,                CheckJni)