Merge "SnapshotTest: update to match DeviceSnapshotHandler API change" into main
diff --git a/apex/com.google.cf.wifi/com.google.cf.wifi.rc b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
index 11fbcf0..18d2f98 100644
--- a/apex/com.google.cf.wifi/com.google.cf.wifi.rc
+++ b/apex/com.google.cf.wifi/com.google.cf.wifi.rc
@@ -3,7 +3,7 @@
     oneshot
 
 # For legacy wifi without openwrt
-service setup_wifi /apex/com.google.cf.wifi/bin/setup_wifi
+service setup_wifi /apex/com.google.cf.wifi/bin/setup_wifi --interface=${ro.vendor.virtwifi.port}
     user root
     oneshot
 
diff --git a/common/libs/utils/files.cpp b/common/libs/utils/files.cpp
index 45828c3..095674a 100644
--- a/common/libs/utils/files.cpp
+++ b/common/libs/utils/files.cpp
@@ -82,6 +82,66 @@
   return (follow_symlinks ? stat : lstat)(path.c_str(), &st) == 0;
 }
 
+Result<dev_t> FileDeviceId(const std::string& path) {
+  struct stat out;
+  CF_EXPECTF(
+      stat(path.c_str(), &out) == 0,
+      "stat() failed trying to retrieve device ID information for \"{}\" "
+      "with error: {}",
+      path, strerror(errno));
+  return out.st_dev;
+}
+
+Result<bool> CanHardLink(const std::string& source,
+                         const std::string& destination) {
+  return CF_EXPECT(FileDeviceId(source)) ==
+         CF_EXPECT(FileDeviceId(destination));
+}
+
+Result<ino_t> FileInodeNumber(const std::string& path) {
+  struct stat out;
+  CF_EXPECTF(
+      stat(path.c_str(), &out) == 0,
+      "stat() failed trying to retrieve inode num information for \"{}\" "
+      "with error: {}",
+      path, strerror(errno));
+  return out.st_ino;
+}
+
+Result<bool> AreHardLinked(const std::string& source,
+                           const std::string& destination) {
+  return (CF_EXPECT(FileDeviceId(source)) ==
+          CF_EXPECT(FileDeviceId(destination))) &&
+         (CF_EXPECT(FileInodeNumber(source)) ==
+          CF_EXPECT(FileInodeNumber(destination)));
+}
+
+Result<std::string> CreateHardLink(const std::string& target,
+                                   const std::string& hardlink,
+                                   const bool overwrite_existing) {
+  if (FileExists(hardlink)) {
+    if (CF_EXPECT(AreHardLinked(target, hardlink))) {
+      return hardlink;
+    }
+    if (!overwrite_existing) {
+      return CF_ERRF(
+          "Cannot hardlink from \"{}\" to \"{}\", the second file already "
+          "exists and is not hardlinked to the first",
+          target, hardlink);
+    }
+    LOG(WARNING) << "Overwriting existing file \"" << hardlink << "\" with \""
+                 << target << "\" from the cache";
+    CF_EXPECTF(unlink(hardlink.c_str()) == 0,
+               "Failed to unlink \"{}\" with error: {}", hardlink,
+               strerror(errno));
+  }
+  CF_EXPECTF(link(target.c_str(), hardlink.c_str()) == 0,
+             "link() failed trying to create hardlink from \"{}\" to \"{}\" "
+             "with error: {}",
+             target, hardlink, strerror(errno));
+  return hardlink;
+}
+
 bool FileHasContent(const std::string& path) {
   return FileSize(path) > 0;
 }
diff --git a/common/libs/utils/files.h b/common/libs/utils/files.h
index 6e423cd..e47ad43 100644
--- a/common/libs/utils/files.h
+++ b/common/libs/utils/files.h
@@ -27,6 +27,15 @@
 
 namespace cuttlefish {
 bool FileExists(const std::string& path, bool follow_symlinks = true);
+Result<dev_t> FileDeviceId(const std::string& path);
+Result<bool> CanHardLink(const std::string& source,
+                         const std::string& destination);
+Result<ino_t> FileInodeNumber(const std::string& path);
+Result<bool> AreHardLinked(const std::string& source,
+                           const std::string& destination);
+Result<std::string> CreateHardLink(const std::string& target,
+                                   const std::string& hardlink,
+                                   const bool overwrite_existing = false);
 bool FileHasContent(const std::string& path);
 Result<std::vector<std::string>> DirectoryContents(const std::string& path);
 bool DirectoryExists(const std::string& path, bool follow_symlinks = true);
diff --git a/guest/hals/vulkan/Android.bp b/guest/hals/vulkan/Android.bp
index 9e7b110..8d974da 100644
--- a/guest/hals/vulkan/Android.bp
+++ b/guest/hals/vulkan/Android.bp
@@ -34,7 +34,7 @@
                 "vulkan.pastel",
             ],
         },
-    }
+    },
 }
 
 apex {
@@ -58,6 +58,7 @@
         "vulkan.ranchu",
     ],
     prebuilts: [
+        "com.google.cf.vulkan.rc",
         "com.google.cf.vulkan-linker-config",
         "android.hardware.vulkan.level-1.prebuilt.xml",
         "android.hardware.vulkan.compute-0.prebuilt.xml",
@@ -67,6 +68,12 @@
     ],
 }
 
+prebuilt_etc {
+    name: "com.google.cf.vulkan.rc",
+    src: "com.google.cf.vulkan.rc",
+    installable: false,
+}
+
 linker_config {
     name: "com.google.cf.vulkan-linker-config",
     src: "apex_linkerconfig.json",
diff --git a/guest/hals/vulkan/com.google.cf.vulkan.rc b/guest/hals/vulkan/com.google.cf.vulkan.rc
new file mode 100644
index 0000000..bd638cc
--- /dev/null
+++ b/guest/hals/vulkan/com.google.cf.vulkan.rc
@@ -0,0 +1,5 @@
+# `on init` is normally not available for .rc files in vendor apexes
+# But the vulkan apex is okay because it's bootstrap APEX.
+# For bootstrap APEXes, `on init` is the earliest available trigger.
+on init
+  setprop ro.vulkan.apex com.google.cf.vulkan
diff --git a/host/commands/assemble_cvd/assemble_cvd.cc b/host/commands/assemble_cvd/assemble_cvd.cc
index 0f73d4d..c386c3d 100644
--- a/host/commands/assemble_cvd/assemble_cvd.cc
+++ b/host/commands/assemble_cvd/assemble_cvd.cc
@@ -242,6 +242,7 @@
   preserving.insert("persistent_composite_gpt_footer.img");
   preserving.insert("persistent_composite.img");
   preserving.insert("persistent_composite_overlay.img");
+  preserving.insert("pflash.img");
   preserving.insert("uboot_env.img");
   preserving.insert("factory_reset_protected.img");
   preserving.insert("misc.img");
diff --git a/host/commands/assemble_cvd/disk_flags.cc b/host/commands/assemble_cvd/disk_flags.cc
index d7fac96..f7144f9 100644
--- a/host/commands/assemble_cvd/disk_flags.cc
+++ b/host/commands/assemble_cvd/disk_flags.cc
@@ -613,6 +613,20 @@
   return {};
 }
 
+Result<void> InitializePflash(
+    const CuttlefishConfig::InstanceSpecific& instance) {
+  if (FileExists(instance.pflash_path())) {
+    return {};
+  }
+
+  auto boot_size_mb = FileSize(instance.bootloader()) / (1 << 20);
+
+  // Pad out bootloader space to 4MB
+  CF_EXPECTF(CreateBlankImage(instance.pflash_path(), 4 - boot_size_mb, "none"),
+             "Failed to create '{}'", instance.pflash_path());
+  return {};
+}
+
 Result<void> InitializeSdCard(
     const CuttlefishConfig& config,
     const CuttlefishConfig::InstanceSpecific& instance) {
@@ -693,7 +707,8 @@
       .install(AutoSetup<GeneratePersistentBootconfig>::Component)
       .install(AutoSetup<GeneratePersistentVbmeta>::Component)
       .install(AutoSetup<InitializeInstanceCompositeDisk>::Component)
-      .install(AutoSetup<InitializeDataImage>::Component);
+      .install(AutoSetup<InitializeDataImage>::Component)
+      .install(AutoSetup<InitializePflash>::Component);
 }
 
 Result<void> DiskImageFlagsVectorization(CuttlefishConfig& config, const FetcherConfig& fetcher_config) {
diff --git a/host/commands/assemble_cvd/misc_info.cc b/host/commands/assemble_cvd/misc_info.cc
index 292258b..2548a5e 100644
--- a/host/commands/assemble_cvd/misc_info.cc
+++ b/host/commands/assemble_cvd/misc_info.cc
@@ -16,22 +16,136 @@
 #include "misc_info.h"
 
 #include <algorithm>
+#include <array>
+#include <memory>
+#include <set>
 #include <string>
+#include <string_view>
+#include <unordered_set>
 #include <vector>
 
 #include <android-base/logging.h>
-#include <android-base/stringprintf.h>
+#include <android-base/parseint.h>
 #include <android-base/strings.h>
+#include <fmt/format.h>
 
+#include "common/libs/fs/shared_buf.h"
+#include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/contains.h"
 #include "common/libs/utils/result.h"
+#include "host/libs/avb/avb.h"
+#include "host/libs/config/known_paths.h"
 
 namespace cuttlefish {
 namespace {
 
+constexpr char kAvbVbmetaAlgorithm[] = "avb_vbmeta_algorithm";
+constexpr char kAvbVbmetaArgs[] = "avb_vbmeta_args";
+constexpr char kAvbVbmetaKeyPath[] = "avb_vbmeta_key_path";
 constexpr char kDynamicPartitions[] = "dynamic_partition_list";
 constexpr char kGoogleDynamicPartitions[] = "google_dynamic_partitions";
+constexpr char kRollbackIndexSuffix[] = "_rollback_index_location";
+constexpr char kSuperBlockDevices[] = "super_block_devices";
 constexpr char kSuperPartitionGroups[] = "super_partition_groups";
+constexpr char kUseDynamicPartitions[] = "use_dynamic_partitions";
+constexpr char kRsa2048Algorithm[] = "SHA256_RSA2048";
+constexpr char kRsa4096Algorithm[] = "SHA256_RSA4096";
+constexpr std::array kNonPartitionKeysToMerge = {
+    "ab_update", "default_system_dev_certificate"};
+// based on build/make/tools/releasetools/common.py:AVB_PARTITIONS
+constexpr std::array kVbmetaPartitions = {"boot",
+                                          "init_boot",
+                                          "odm",
+                                          "odm_dlkm",
+                                          "vbmeta_system",
+                                          "vbmeta_system_dlkm",
+                                          "vbmeta_vendor_dlkm",
+                                          "vendor",
+                                          "vendor_boot"};
+
+Result<std::string> GetExpected(const MiscInfo& misc_info,
+                                const std::string& key) {
+  auto lookup = misc_info.find(key);
+  CF_EXPECTF(lookup != misc_info.end(),
+             "Unable to retrieve expected value from key: {}", key);
+  return lookup->second;
+}
+
+std::string MergePartitionLists(const std::string& vendor,
+                                const std::string& system,
+                                const std::set<std::string>& extracted_images) {
+  const std::string full_string = fmt::format("{} {}", vendor, system);
+  const std::vector<std::string> full_list =
+      android::base::Tokenize(full_string, " ");
+  // std::set removes duplicates and orders the elements, which we want
+  const std::set<std::string> full_set(full_list.begin(), full_list.end());
+  std::set<std::string> filtered_set;
+  std::set_intersection(full_set.cbegin(), full_set.cend(),
+                        extracted_images.cbegin(), extracted_images.cend(),
+                        std::inserter(filtered_set, filtered_set.begin()));
+  return android::base::Join(filtered_set, " ");
+}
+
+std::string GetPartitionList(const MiscInfo& vendor_info,
+                             const MiscInfo& system_info,
+                             const std::string& key,
+                             const std::set<std::string>& extracted_images) {
+  std::string vendor_list = GetExpected(vendor_info, key).value_or("");
+  std::string system_list = GetExpected(system_info, key).value_or("");
+  return MergePartitionLists(vendor_list, system_list, extracted_images);
+}
+
+std::vector<std::string> GeneratePartitionKeys(const std::string& name) {
+  std::vector<std::string> result;
+  result.emplace_back("avb_" + name);
+  result.emplace_back("avb_" + name + "_algorithm");
+  result.emplace_back("avb_" + name + "_key_path");
+  result.emplace_back("avb_" + name + kRollbackIndexSuffix);
+  result.emplace_back("avb_" + name + "_hashtree_enable");
+  result.emplace_back("avb_" + name + "_add_hashtree_footer_args");
+  result.emplace_back(name + "_disable_sparse");
+  result.emplace_back("building_" + name + "_image");
+  auto fs_type_key = name + "_fs_type";
+  if (name == "system") {
+    fs_type_key = "fs_type";
+  }
+  result.emplace_back(fs_type_key);
+  return result;
+}
+
+Result<int> ResolveRollbackIndexConflicts(
+    const std::string& index_string,
+    const std::unordered_set<int> used_indices) {
+  int index;
+  CF_EXPECTF(android::base::ParseInt(index_string, &index),
+             "Unable to parse value {} to string.  Maybe a wrong or bad value "
+             "read for the rollback index?",
+             index_string);
+  while (Contains(used_indices, index)) {
+    ++index;
+  }
+  return index;
+}
+
+Result<std::string> GetKeyPath(const std::string_view algorithm) {
+  if (algorithm == kRsa4096Algorithm) {
+    return TestKeyRsa4096();
+  } else if (algorithm == kRsa2048Algorithm) {
+    return TestKeyRsa2048();
+  } else {
+    return CF_ERR("Unexpected algorithm.  No key available.");
+  }
+}
+
+Result<std::string> GetPubKeyPath(const std::string_view algorithm) {
+  if (algorithm == kRsa4096Algorithm) {
+    return TestPubKeyRsa4096();
+  } else if (algorithm == kRsa2048Algorithm) {
+    return TestPubKeyRsa2048();
+  } else {
+    return CF_ERR("Unexpected algorithm.  No key available.");
+  }
+}
 
 }  // namespace
 
@@ -61,73 +175,158 @@
   return misc_info;
 }
 
-std::string WriteMiscInfo(const MiscInfo& misc_info) {
-  std::stringstream out;
+Result<void> WriteMiscInfo(const MiscInfo& misc_info,
+                           const std::string& output_path) {
+  std::stringstream file_content;
   for (const auto& entry : misc_info) {
-    out << entry.first << "=" << entry.second << "\n";
+    file_content << entry.first << "=" << entry.second << "\n";
   }
-  return out.str();
+
+  SharedFD output_file = SharedFD::Creat(output_path.c_str(), 0644);
+  CF_EXPECT(output_file->IsOpen(),
+            "Failed to open output misc file: " << output_file->StrError());
+
+  CF_EXPECT(
+      WriteAll(output_file, file_content.str()) >= 0,
+      "Failed to write output misc file contents: " << output_file->StrError());
+  return {};
 }
 
-std::vector<std::string> SuperPartitionComponents(const MiscInfo& info) {
-  auto value_it = info.find(kDynamicPartitions);
-  if (value_it == info.end()) {
-    return {};
-  }
-  auto components = android::base::Split(value_it->second, " ");
-  for (auto& component : components) {
-    component = android::base::Trim(component);
-  }
-  components.erase(std::remove(components.begin(), components.end(), ""),
-                   components.end());
-  return components;
-}
-
-bool SetSuperPartitionComponents(const std::vector<std::string>& components,
-                                 MiscInfo* misc_info) {
-  auto super_partition_groups = misc_info->find(kSuperPartitionGroups);
-  if (super_partition_groups == misc_info->end()) {
-    LOG(ERROR) << "Failed to find super partition groups in misc_info";
-    return false;
-  }
-
-  // Remove all existing update groups in misc_info
-  auto update_groups =
-      android::base::Split(super_partition_groups->second, " ");
-  for (const auto& group_name : update_groups) {
-    auto partition_list = android::base::StringPrintf("super_%s_partition_list",
-                                                      group_name.c_str());
-    auto partition_size =
-        android::base::StringPrintf("super_%s_group_size", group_name.c_str());
-    for (const auto& key : {partition_list, partition_size}) {
-      auto it = misc_info->find(key);
-      if (it == misc_info->end()) {
-        LOG(ERROR) << "Failed to find " << key << " in misc_info";
-        return false;
-      }
-      misc_info->erase(it);
+// based on build/make/tools/releasetools/merge/merge_target_files.py
+Result<MiscInfo> GetCombinedDynamicPartitions(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const std::set<std::string>& extracted_images) {
+  auto vendor_use_dp =
+      CF_EXPECT(GetExpected(vendor_info, kUseDynamicPartitions));
+  CF_EXPECTF(vendor_use_dp == "true", "Vendor build must have {}=true",
+             kUseDynamicPartitions);
+  auto system_use_dp =
+      CF_EXPECT(GetExpected(system_info, kUseDynamicPartitions));
+  CF_EXPECTF(system_use_dp == "true", "System build must have {}=true",
+             kUseDynamicPartitions);
+  MiscInfo result;
+  // copy where both files are equal
+  for (const auto& key_val : vendor_info) {
+    const auto value_result = GetExpected(system_info, key_val.first);
+    if (value_result.ok() && *value_result == key_val.second) {
+      result[key_val.first] = key_val.second;
     }
   }
 
-  // For merged target-file, put all dynamic partitions under the
-  // google_dynamic_partitions update group.
-  // TODO(xunchang) use different update groups for system and vendor images.
-  (*misc_info)[kDynamicPartitions] = android::base::Join(components, " ");
-  (*misc_info)[kSuperPartitionGroups] = kGoogleDynamicPartitions;
-  std::string partitions_list_key = android::base::StringPrintf(
-      "super_%s_partition_list", kGoogleDynamicPartitions);
-  (*misc_info)[partitions_list_key] = android::base::Join(components, " ");
+  result[kDynamicPartitions] = GetPartitionList(
+      vendor_info, system_info, kDynamicPartitions, extracted_images);
 
-  // Use the entire super partition as the group size
-  std::string group_size_key = android::base::StringPrintf(
-      "super_%s_group_size", kGoogleDynamicPartitions);
-  auto super_size_it = misc_info->find("super_partition_size");
-  if (super_size_it == misc_info->end()) {
-    LOG(ERROR) << "Failed to find super partition size";
-    return false;
+  const auto block_devices_result =
+      GetExpected(vendor_info, kSuperBlockDevices);
+  if (block_devices_result.ok()) {
+    result[kSuperBlockDevices] = *block_devices_result;
+    for (const auto& block_device :
+         android::base::Tokenize(result[kSuperBlockDevices], " ")) {
+      const auto key = "super_" + block_device + "_device_size";
+      result[key] = CF_EXPECT(GetExpected(vendor_info, key));
+    }
   }
-  (*misc_info)[group_size_key] = super_size_it->second;
-  return true;
+
+  result[kSuperPartitionGroups] =
+      CF_EXPECT(GetExpected(vendor_info, kSuperPartitionGroups));
+  for (const auto& group :
+       android::base::Tokenize(result[kSuperPartitionGroups], " ")) {
+    const auto group_size_key = "super_" + group + "_group_size";
+    result[group_size_key] =
+        CF_EXPECT(GetExpected(vendor_info, group_size_key));
+
+    const auto partition_list_key = "super_" + group + "_partition_list";
+    result[partition_list_key] = GetPartitionList(
+        vendor_info, system_info, partition_list_key, extracted_images);
+  }
+
+  // TODO(chadreynolds): add vabc_cow_version logic if we need to support older
+  // builds
+  for (const auto& key :
+       {"virtual_ab", "virtual_ab_retrofit", "lpmake", "super_metadata_device",
+        "super_partition_error_limit", "super_partition_size"}) {
+    const auto value_result = GetExpected(vendor_info, key);
+    if (value_result.ok()) {
+      result[key] = *value_result;
+    }
+  }
+  return std::move(result);
+}
+
+Result<MiscInfo> MergeMiscInfos(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const MiscInfo& combined_dp_info,
+    const std::vector<std::string>& system_partitions) {
+  // the combined misc info uses the vendor values as defaults
+  MiscInfo result = vendor_info;
+  std::unordered_set<int> used_indices;
+  for (const auto& partition : system_partitions) {
+    for (const auto& key : GeneratePartitionKeys(partition)) {
+      if (!Contains(system_info, key)) {
+        continue;
+      }
+      auto system_value = system_info.find(key)->second;
+      // avb_<partition>_rollback_index_location values can conflict across
+      // different builds
+      if (android::base::EndsWith(key, kRollbackIndexSuffix)) {
+        const auto index = CF_EXPECT(
+            ResolveRollbackIndexConflicts(system_value, used_indices));
+        used_indices.insert(index);
+        system_value = std::to_string(index);
+      }
+      result[key] = system_value;
+    }
+  }
+  for (const auto& key : kNonPartitionKeysToMerge) {
+    const auto value_result = GetExpected(system_info, key);
+    if (value_result.ok()) {
+      result[key] = *value_result;
+    }
+  }
+  for (const auto& key_val : combined_dp_info) {
+    result[key_val.first] = key_val.second;
+  }
+  return std::move(result);
+}
+
+Result<VbmetaArgs> GetVbmetaArgs(const MiscInfo& misc_info,
+                                 const std::string& image_path) {
+  // The key_path value should exist, but it is a build system path
+  // We use a host artifacts relative path instead
+  CF_EXPECT(Contains(misc_info, kAvbVbmetaKeyPath));
+  const auto algorithm = CF_EXPECT(GetExpected(misc_info, kAvbVbmetaAlgorithm));
+  auto result = VbmetaArgs{
+      .algorithm = algorithm,
+      .key_path = CF_EXPECT(GetKeyPath(algorithm)),
+  };
+  // must split and add --<flag> <arg> arguments(non-equals format) separately
+  // due to how Command.AddParameter handles each argument
+  const auto extra_args_result = GetExpected(misc_info, kAvbVbmetaArgs);
+  if (extra_args_result.ok()) {
+    for (const auto& arg : android::base::Tokenize(*extra_args_result, " ")) {
+      result.extra_arguments.emplace_back(arg);
+    }
+  }
+
+  for (const auto& partition : kVbmetaPartitions) {
+    // The key_path value should exist, but it is a build system path
+    // We use a host artifacts relative path instead
+    if (Contains(misc_info, fmt::format("avb_{}_key_path", partition))) {
+      const auto partition_algorithm = CF_EXPECT(
+          GetExpected(misc_info, fmt::format("avb_{}_algorithm", partition)));
+      result.chained_partitions.emplace_back(ChainPartition{
+          .name = partition,
+          .rollback_index = CF_EXPECT(GetExpected(
+              misc_info,
+              fmt::format("avb_{}_rollback_index_location", partition))),
+          .key_path = CF_EXPECT(GetPubKeyPath(partition_algorithm)),
+      });
+    } else {
+      result.included_partitions.emplace_back(
+          fmt::format("{}/IMAGES/{}.img", image_path, partition));
+    }
+  }
+  return result;
 }
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/misc_info.h b/host/commands/assemble_cvd/misc_info.h
index f832a4c..731a5ba 100644
--- a/host/commands/assemble_cvd/misc_info.h
+++ b/host/commands/assemble_cvd/misc_info.h
@@ -16,20 +16,37 @@
 #pragma once
 
 #include <map>
+#include <set>
 #include <string>
-#include <vector>
 
 #include "common/libs/utils/result.h"
+#include "host/libs/avb/avb.h"
 
 namespace cuttlefish {
 
+// TODO(chadreynolds): rename MiscInfo to more generic KeyValueFile since this
+// logic is processing multiple filetypes now
 using MiscInfo = std::map<std::string, std::string>;
 
-Result<MiscInfo> ParseMiscInfo(const std::string& file_contents);
-std::string WriteMiscInfo(const MiscInfo& info);
+struct VbmetaArgs {
+  std::string algorithm;
+  std::string key_path;
+  std::vector<ChainPartition> chained_partitions;
+  std::vector<std::string> included_partitions;
+  std::vector<std::string> extra_arguments;
+};
 
-std::vector<std::string> SuperPartitionComponents(const MiscInfo&);
-bool SetSuperPartitionComponents(const std::vector<std::string>& components,
-                                 MiscInfo* misc_info);
+Result<MiscInfo> ParseMiscInfo(const std::string& file_contents);
+Result<void> WriteMiscInfo(const MiscInfo& misc_info,
+                           const std::string& output_path);
+Result<MiscInfo> GetCombinedDynamicPartitions(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const std::set<std::string>& extracted_images);
+Result<MiscInfo> MergeMiscInfos(
+    const MiscInfo& vendor_info, const MiscInfo& system_info,
+    const MiscInfo& combined_dp_info,
+    const std::vector<std::string>& system_partitions);
+Result<VbmetaArgs> GetVbmetaArgs(const MiscInfo& misc_info,
+                                 const std::string& image_path);
 
 } // namespace cuttlefish
diff --git a/host/commands/assemble_cvd/super_image_mixer.cc b/host/commands/assemble_cvd/super_image_mixer.cc
index b59d163..ee23244 100644
--- a/host/commands/assemble_cvd/super_image_mixer.cc
+++ b/host/commands/assemble_cvd/super_image_mixer.cc
@@ -21,42 +21,40 @@
 #include <array>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <unordered_set>
+#include <utility>
+#include <vector>
 
 #include <android-base/strings.h>
 #include <android-base/logging.h>
 
-#include "common/libs/fs/shared_buf.h"
 #include "common/libs/utils/archive.h"
 #include "common/libs/utils/contains.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/result.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/commands/assemble_cvd/misc_info.h"
+#include "host/libs/avb/avb.h"
 #include "host/libs/config/config_utils.h"
 #include "host/libs/config/cuttlefish_config.h"
 #include "host/libs/config/fetcher_config.h"
+#include "host/libs/config/known_paths.h"
 
 namespace cuttlefish {
 namespace {
 
 constexpr char kMiscInfoPath[] = "META/misc_info.txt";
+constexpr char kDynamicPartitionsPath[] = "META/dynamic_partitions_info.txt";
 constexpr std::array kVendorTargetImages = {
-    "IMAGES/boot.img",
-    "IMAGES/dtbo.img",
-    "IMAGES/init_boot.img",
-    "IMAGES/odm.img",
-    "IMAGES/odm_dlkm.img",
-    "IMAGES/recovery.img",
-    "IMAGES/system_dlkm.img",
-    "IMAGES/userdata.img",
-    "IMAGES/vbmeta.img",
-    "IMAGES/vbmeta_system_dlkm.img",
-    "IMAGES/vbmeta_vendor.img",
-    "IMAGES/vbmeta_vendor_dlkm.img",
-    "IMAGES/vendor.img",
-    "IMAGES/vendor_dlkm.img",
-    "IMAGES/vendor_kernel_boot.img",
+    "IMAGES/boot.img",          "IMAGES/dtbo.img",
+    "IMAGES/init_boot.img",     "IMAGES/odm.img",
+    "IMAGES/odm_dlkm.img",      "IMAGES/recovery.img",
+    "IMAGES/system_dlkm.img",   "IMAGES/userdata.img",
+    "IMAGES/vbmeta.img",        "IMAGES/vbmeta_system_dlkm.img",
+    "IMAGES/vbmeta_vendor.img", "IMAGES/vbmeta_vendor_dlkm.img",
+    "IMAGES/vendor.img",        "IMAGES/vendor_boot.img",
+    "IMAGES/vendor_dlkm.img",   "IMAGES/vendor_kernel_boot.img",
 };
 constexpr std::array kVendorTargetBuildProps = {
     "ODM/build.prop",
@@ -72,6 +70,11 @@
   std::vector<std::string> system_contents;
 };
 
+struct Extracted {
+  std::set<std::string> images;
+  std::vector<std::string> system_partitions;
+};
+
 void FindImports(Archive* archive, const std::string& build_prop_file) {
   auto contents = archive->ExtractToMemory(build_prop_file);
   auto lines = android::base::Split(contents, "\n");
@@ -92,6 +95,17 @@
   return android::base::EndsWith(filename, "build.prop");
 }
 
+Result<std::string> GetPartitionNameFromPath(const std::string& path) {
+  std::string_view result(path);
+  CF_EXPECTF(
+      android::base::ConsumePrefix(&result, "IMAGES/"),
+      "target_files filepath {} expected to be in the \"IMAGES\" directory",
+      path);
+  CF_EXPECTF(android::base::ConsumeSuffix(&result, ".img"),
+             "target_files filepath {} expected to be a \".img\" file", path);
+  return std::string(result);
+}
+
 Result<TargetFiles> GetTargetFiles(const std::string& vendor_zip_path,
                                    const std::string& system_zip_path) {
   auto result = TargetFiles{
@@ -107,42 +121,50 @@
   return result;
 }
 
-Result<void> CombineMiscInfo(TargetFiles& target_files,
-                             const std::string& misc_output_path) {
+Result<MiscInfo> CombineDynamicPartitionsInfo(
+    TargetFiles& target_files, const std::set<std::string>& extracted_images) {
+  CF_EXPECTF(Contains(target_files.vendor_contents, kDynamicPartitionsPath),
+             "Vendor target files zip does not contain {}",
+             kDynamicPartitionsPath);
+  CF_EXPECTF(Contains(target_files.system_contents, kDynamicPartitionsPath),
+             "System target files zip does not contain {}",
+             kDynamicPartitionsPath);
+
+  const MiscInfo vendor_dp_info = CF_EXPECT(ParseMiscInfo(
+      target_files.vendor_zip.ExtractToMemory(kDynamicPartitionsPath)));
+  const MiscInfo system_dp_info = CF_EXPECT(ParseMiscInfo(
+      target_files.system_zip.ExtractToMemory(kDynamicPartitionsPath)));
+
+  return CF_EXPECT(GetCombinedDynamicPartitions(vendor_dp_info, system_dp_info,
+                                                extracted_images));
+}
+
+Result<MiscInfo> CombineMiscInfo(
+    TargetFiles& target_files, const std::string& misc_output_path,
+    const std::set<std::string>& extracted_images,
+    const std::vector<std::string>& system_partitions) {
   CF_EXPECTF(Contains(target_files.vendor_contents, kMiscInfoPath),
-             "Default target files zip does not contain {}", kMiscInfoPath);
+             "Vendor target files zip does not contain {}", kMiscInfoPath);
   CF_EXPECTF(Contains(target_files.system_contents, kMiscInfoPath),
              "System target files zip does not contain {}", kMiscInfoPath);
 
-  const MiscInfo default_misc = CF_EXPECT(
+  const MiscInfo vendor_misc = CF_EXPECT(
       ParseMiscInfo(target_files.vendor_zip.ExtractToMemory(kMiscInfoPath)));
   const MiscInfo system_misc = CF_EXPECT(
       ParseMiscInfo(target_files.system_zip.ExtractToMemory(kMiscInfoPath)));
 
-  auto output_misc = default_misc;
-  auto system_super_partitions = SuperPartitionComponents(system_misc);
-  // Ensure specific skipped partitions end up in the misc_info.txt
-  for (auto partition :
-       {"odm", "odm_dlkm", "vendor", "vendor_dlkm", "system_dlkm"}) {
-    if (!Contains(system_super_partitions, partition)) {
-      system_super_partitions.push_back(partition);
-    }
-  }
-  CF_EXPECT(SetSuperPartitionComponents(system_super_partitions, &output_misc),
-            "Failed to update super partitions components for misc_info");
+  const auto combined_dp_info =
+      CF_EXPECT(CombineDynamicPartitionsInfo(target_files, extracted_images));
+  const auto output_misc = CF_EXPECT(MergeMiscInfos(
+      vendor_misc, system_misc, combined_dp_info, system_partitions));
 
-  SharedFD misc_output_file = SharedFD::Creat(misc_output_path.c_str(), 0644);
-  CF_EXPECT(misc_output_file->IsOpen(), "Failed to open output misc file: "
-                                            << misc_output_file->StrError());
-
-  CF_EXPECT(WriteAll(misc_output_file, WriteMiscInfo(output_misc)) >= 0,
-            "Failed to write output misc file contents: "
-                << misc_output_file->StrError());
-  return {};
+  CF_EXPECT(WriteMiscInfo(output_misc, misc_output_path));
+  return std::move(output_misc);
 }
 
-Result<void> ExtractTargetFiles(TargetFiles& target_files,
-                                const std::string& combined_output_path) {
+Result<Extracted> ExtractTargetFiles(TargetFiles& target_files,
+                                     const std::string& combined_output_path) {
+  Extracted extracted;
   for (const auto& name : target_files.vendor_contents) {
     if (!IsTargetFilesImage(name)) {
       continue;
@@ -153,6 +175,7 @@
     CF_EXPECT(
         target_files.vendor_zip.ExtractFiles({name}, combined_output_path),
         "Failed to extract " << name << " from the vendor target zip");
+    extracted.images.emplace(CF_EXPECT(GetPartitionNameFromPath(name)));
   }
   for (const auto& name : target_files.vendor_contents) {
     if (!IsTargetFilesBuildProp(name)) {
@@ -177,6 +200,9 @@
     CF_EXPECT(
         target_files.system_zip.ExtractFiles({name}, combined_output_path),
         "Failed to extract " << name << " from the system target zip");
+    const auto partition = CF_EXPECT(GetPartitionNameFromPath(name));
+    extracted.images.emplace(partition);
+    extracted.system_partitions.emplace_back(partition);
   }
   for (const auto& name : target_files.system_contents) {
     if (!IsTargetFilesBuildProp(name)) {
@@ -190,19 +216,36 @@
         target_files.system_zip.ExtractFiles({name}, combined_output_path),
         "Failed to extract " << name << " from the system target zip");
   }
+  return extracted;
+}
+
+Result<void> RegenerateVbmeta(const MiscInfo& misc_info,
+                              const std::string& output_path,
+                              const std::string& image_path) {
+  const VbmetaArgs args = CF_EXPECT(GetVbmetaArgs(misc_info, image_path));
+  auto avbtool = Avb(AvbToolBinary(), args.algorithm, args.key_path);
+  CF_EXPECT(avbtool.MakeVbMetaImage(output_path, args.chained_partitions,
+                                    args.included_partitions,
+                                    args.extra_arguments));
   return {};
 }
 
 Result<void> CombineTargetZipFiles(const std::string& vendor_zip_path,
                                    const std::string& system_zip_path,
-                                   const std::string& output_path) {
-  CF_EXPECT(EnsureDirectoryExists(output_path));
-  CF_EXPECT(EnsureDirectoryExists(output_path + "/META"));
+                                   const std::string& combined_target_path,
+                                   const std::string& vbmeta_output_path) {
+  CF_EXPECT(EnsureDirectoryExists(combined_target_path));
+  CF_EXPECT(EnsureDirectoryExists(combined_target_path + "/META"));
   auto target_files =
       CF_EXPECT(GetTargetFiles(vendor_zip_path, system_zip_path));
-  CF_EXPECT(ExtractTargetFiles(target_files, output_path));
-  const auto misc_output_path = output_path + "/" + kMiscInfoPath;
-  CF_EXPECT(CombineMiscInfo(target_files, misc_output_path));
+  const auto extracted =
+      CF_EXPECT(ExtractTargetFiles(target_files, combined_target_path));
+  const auto misc_output_path = combined_target_path + "/" + kMiscInfoPath;
+  const auto combined_info =
+      CF_EXPECT(CombineMiscInfo(target_files, misc_output_path,
+                                extracted.images, extracted.system_partitions));
+  CF_EXPECT(RegenerateVbmeta(combined_info, vbmeta_output_path,
+                             combined_target_path));
   return {};
 }
 
@@ -240,7 +283,8 @@
 
 Result<void> RebuildSuperImage(const FetcherConfig& fetcher_config,
                                const CuttlefishConfig& config,
-                               const std::string& output_path) {
+                               const std::string& super_image_output,
+                               const std::string& vbmeta_image_output) {
   auto instance = config.ForDefaultInstance();
   // In SuperImageNeedsRebuilding, it already checked both
   // has_default_target_zip and has_system_target_zip are the same.
@@ -262,10 +306,10 @@
   std::string combined_target_path = instance.PerInstanceInternalPath("target_combined");
   // TODO(schuffelen): Use otatools/bin/merge_target_files
   CF_EXPECT(CombineTargetZipFiles(default_target_zip, system_target_zip,
-                                  combined_target_path),
+                                  combined_target_path, vbmeta_image_output),
             "Could not combine target zip files.");
 
-  CF_EXPECT(BuildSuperImage(combined_target_path, output_path),
+  CF_EXPECT(BuildSuperImage(combined_target_path, super_image_output),
             "Could not write the final output super image.");
   return {};
 }
@@ -287,7 +331,8 @@
                                             instance_.default_target_zip(),
                                             instance_.system_target_zip()))) {
       CF_EXPECT(RebuildSuperImage(fetcher_config_, config_,
-                                  instance_.new_super_image()));
+                                  instance_.new_super_image(),
+                                  instance_.new_vbmeta_image()));
     }
     return {};
   }
diff --git a/host/commands/control_env_proxy_server/Android.bp b/host/commands/control_env_proxy_server/Android.bp
index 0494145..0783d31 100644
--- a/host/commands/control_env_proxy_server/Android.bp
+++ b/host/commands/control_env_proxy_server/Android.bp
@@ -56,7 +56,6 @@
     static_libs: [
         "grpc_cli_libs",
         "libabsl_host",
-        "libc++fs",
         "libcuttlefish_control_env",
         "libcuttlefish_host_config",
         "libgflags",
diff --git a/host/commands/cvd_env/Android.bp b/host/commands/cvd_env/Android.bp
index 58c4852..885088d 100644
--- a/host/commands/cvd_env/Android.bp
+++ b/host/commands/cvd_env/Android.bp
@@ -32,7 +32,6 @@
     static_libs: [
         "grpc_cli_libs",
         "libabsl_host",
-        "libc++fs",
         "libcuttlefish_control_env",
         "libcuttlefish_host_config",
         "libgflags",
@@ -40,7 +39,6 @@
     cflags: [
         "-Wno-unused-parameter",
     ],
-    cpp_std: "c++17",
     defaults: ["cuttlefish_host"],
     target: {
         darwin: {
diff --git a/host/commands/modem_simulator/Android.bp b/host/commands/modem_simulator/Android.bp
index 0f684e8..9dec221 100644
--- a/host/commands/modem_simulator/Android.bp
+++ b/host/commands/modem_simulator/Android.bp
@@ -118,7 +118,4 @@
         "device/google/cuttlefish/host/commands",
     ],
     defaults: ["cuttlefish_host", "modem_simulator_base"],
-    whole_static_libs: [
-        "libc++fs"
-    ],
 }
diff --git a/host/commands/run_cvd/boot_state_machine.cc b/host/commands/run_cvd/boot_state_machine.cc
index fd858a4..e78aac0 100644
--- a/host/commands/run_cvd/boot_state_machine.cc
+++ b/host/commands/run_cvd/boot_state_machine.cc
@@ -176,6 +176,11 @@
     if (boot_event_handler_.joinable()) {
       boot_event_handler_.join();
     }
+    if (restore_complete_stop_write_->IsOpen()) {
+      char c = 1;
+      CHECK_EQ(restore_complete_stop_write_->Write(&c, 1), 1)
+          << restore_complete_stop_write_->StrError();
+    }
     if (restore_complete_handler_.joinable()) {
       restore_complete_handler_.join();
     }
@@ -208,19 +213,29 @@
     CF_EXPECTF(boot_events_pipe->IsOpen(), "Could not get boot events pipe: {}",
                boot_events_pipe->StrError());
 
+    // Pipe to tell `ThreadLoop` that the restore is complete.
     SharedFD restore_complete_pipe, restore_complete_pipe_write;
+    // Pipe to tell `restore_complete_handler_` thread to give up.
+    // It isn't perfect, can only break out of the `WaitForRestoreComplete`
+    // step.
+    SharedFD restore_complete_stop_read;
     if (IsRestoring(config_)) {
       CF_EXPECT(
           SharedFD::Pipe(&restore_complete_pipe, &restore_complete_pipe_write),
           "unable to create pipe");
+      CF_EXPECT(SharedFD::Pipe(&restore_complete_stop_read,
+                               &restore_complete_stop_write_),
+                "unable to create pipe");
 
-      // Unlike `boot_event_handler_`, this doesn't support graceful shutdown,
-      // it blocks until it finishes its work.
-      restore_complete_handler_ =
-          std::thread([this, restore_complete_pipe_write]() {
-            const auto result = vm_manager_.WaitForRestoreComplete();
+      restore_complete_handler_ = std::thread(
+          [this, restore_complete_pipe_write, restore_complete_stop_read]() {
+            const auto result =
+                vm_manager_.WaitForRestoreComplete(restore_complete_stop_read);
             CHECK(result.ok()) << "Failed to wait for restore complete: "
                                << result.error().FormatForEnv();
+            if (!result.value()) {
+              return;
+            }
 
             cuttlefish::SharedFD restore_adbd_pipe = cuttlefish::SharedFD::Open(
                 config_.ForDefaultInstance().restore_adbd_pipe_name().c_str(),
@@ -404,6 +419,7 @@
 
   std::thread boot_event_handler_;
   std::thread restore_complete_handler_;
+  SharedFD restore_complete_stop_write_;
   SharedFD fg_launcher_pipe_;
   SharedFD reboot_notification_;
   SharedFD interrupt_fd_read_;
diff --git a/host/commands/run_cvd/server_loop_impl_snapshot.cpp b/host/commands/run_cvd/server_loop_impl_snapshot.cpp
index c90c4ac..44539d6 100644
--- a/host/commands/run_cvd/server_loop_impl_snapshot.cpp
+++ b/host/commands/run_cvd/server_loop_impl_snapshot.cpp
@@ -144,19 +144,31 @@
   }
 }
 
+static Result<void> RunAdbShellCommand(
+    const CuttlefishConfig::InstanceSpecific& ins,
+    const std::vector<std::string>& command_args) {
+  Command adb_command(SubtoolPath("adb"));
+  // Avoid the adb server being started in the runtime directory and looking
+  // like a process that is still using the directory.
+  adb_command.SetWorkingDirectory("/");
+  adb_command.AddParameter("-s").AddParameter(ins.adb_ip_and_port());
+  adb_command.AddParameter("wait-for-device");
+
+  adb_command.AddParameter("shell").AddParameter("cmd");
+  for (const auto& argument : command_args) {
+    adb_command.AddParameter(argument);
+  }
+  CF_EXPECT_EQ(adb_command.Start().Wait(), 0);
+  return {};
+}
+
 Result<void> ServerLoopImpl::HandleSuspend(ProcessMonitor& process_monitor) {
   // right order: guest -> host
   LOG(DEBUG) << "Suspending the guest..";
-  const auto adb_bin_path = SubtoolPath("adb");
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "bluetooth_manager", "disable"},
-                    SubprocessOptions(), WEXITED));
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "bluetooth_manager", "wait-for-state:STATE_OFF"},
-                    SubprocessOptions(), WEXITED));
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "uwb", "disable-uwb"},
-                    SubprocessOptions(), WEXITED));
+  CF_EXPECT(RunAdbShellCommand(instance_, {"bluetooth_manager", "disable"}));
+  CF_EXPECT(RunAdbShellCommand(
+      instance_, {"bluetooth_manager", "wait-for-state:STATE_OFF"}));
+  CF_EXPECT(RunAdbShellCommand(instance_, {"uwb", "disable-uwb"}));
   // right order: guest -> host
   CF_EXPECT(SuspendGuest());
   LOG(DEBUG) << "The guest is suspended.";
@@ -174,13 +186,8 @@
   LOG(DEBUG) << "Resuming the guest..";
   CF_EXPECT(ResumeGuest());
   // Resume services after guest has resumed.
-  const auto adb_bin_path = SubtoolPath("adb");
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "bluetooth_manager", "enable"},
-                    SubprocessOptions(), WEXITED));
-  CF_EXPECT(Execute({adb_bin_path, "-s", instance_.adb_ip_and_port(), "shell",
-                     "cmd", "uwb", "enable-uwb"},
-                    SubprocessOptions(), WEXITED));
+  CF_EXPECT(RunAdbShellCommand(instance_, {"bluetooth_manager", "enable"}));
+  CF_EXPECT(RunAdbShellCommand(instance_, {"uwb", "enable-uwb"}));
   LOG(DEBUG) << "The guest resumed.";
   return {};
 }
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index dfa38cb..f23148d 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -394,6 +394,8 @@
 
     std::string pstore_path() const;
 
+    std::string pflash_path() const;
+
     std::string console_path() const;
 
     std::string logcat_path() const;
diff --git a/host/libs/config/cuttlefish_config_instance.cpp b/host/libs/config/cuttlefish_config_instance.cpp
index de063a8..2bc2a3a 100644
--- a/host/libs/config/cuttlefish_config_instance.cpp
+++ b/host/libs/config/cuttlefish_config_instance.cpp
@@ -1277,6 +1277,10 @@
   return AbsolutePath(PerInstancePath("pstore"));
 }
 
+std::string CuttlefishConfig::InstanceSpecific::pflash_path() const {
+  return AbsolutePath(PerInstancePath("pflash.img"));
+}
+
 std::string CuttlefishConfig::InstanceSpecific::console_path() const {
   return AbsolutePath(PerInstancePath("console"));
 }
diff --git a/host/libs/control_env/Android.bp b/host/libs/control_env/Android.bp
index 3c33d82..5b5264f 100644
--- a/host/libs/control_env/Android.bp
+++ b/host/libs/control_env/Android.bp
@@ -32,13 +32,11 @@
     static_libs: [
         "grpc_cli_libs",
         "libabsl_host",
-        "libc++fs",
         "libgflags",
     ],
     cflags: [
         "-Wno-unused-parameter",
     ],
-    cpp_std: "c++17",
     defaults: ["cuttlefish_buildhost_only"],
     target: {
         darwin: {
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 3eb5118..d5475b6 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -16,6 +16,7 @@
 
 #include "host/libs/vm_manager/crosvm_manager.h"
 
+#include <poll.h>
 #include <signal.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -816,6 +817,10 @@
         ":shared:type=fs");
   }
 
+  if (instance.target_arch() == Arch::X86_64) {
+    crosvm_cmd.Cmd().AddParameter("--pflash=", instance.pflash_path());
+  }
+
   // This needs to be the last parameter
   crosvm_cmd.Cmd().AddParameter("--bios=", instance.bootloader());
 
@@ -890,14 +895,21 @@
   return commands;
 }
 
-Result<void> CrosvmManager::WaitForRestoreComplete() const {
+Result<bool> CrosvmManager::WaitForRestoreComplete(SharedFD stop_fd) const {
   auto instance = CF_EXPECT(CuttlefishConfig::Get())->ForDefaultInstance();
 
   // Wait for the control socket to exist. It is created early in crosvm's
   // startup sequence, but the process may not even have been exec'd by CF at
   // this point.
   while (!FileExists(instance.CrosvmSocketPath())) {
-    usleep(50000);  // 50 ms, arbitrarily chosen
+    std::vector<PollSharedFd> poll = {{.fd = stop_fd, .events = POLLIN}};
+    const int result = SharedFD::Poll(poll, 50 /* ms */);
+    // Check for errors.
+    CF_EXPECT(result >= 0, "failed to wait on stop_fd: " << strerror(errno));
+    // Check if pipe became readable or closed.
+    if (result > 0) {
+      return false;
+    }
   }
 
   // Ask crosvm to resume the VM. crosvm promises to not complete this command
@@ -914,7 +926,7 @@
   CF_EXPECT_EQ(infop.si_code, CLD_EXITED);
   CF_EXPECTF(infop.si_status == 0, "crosvm resume returns non zero code {}",
              infop.si_status);
-  return {};
+  return true;
 }
 
 }  // namespace vm_manager
diff --git a/host/libs/vm_manager/crosvm_manager.h b/host/libs/vm_manager/crosvm_manager.h
index cb119fe..6e26c05 100644
--- a/host/libs/vm_manager/crosvm_manager.h
+++ b/host/libs/vm_manager/crosvm_manager.h
@@ -46,7 +46,7 @@
       const CuttlefishConfig& config,
       std::vector<VmmDependencyCommand*>& dependencyCommands) override;
 
-  Result<void> WaitForRestoreComplete() const override;
+  Result<bool> WaitForRestoreComplete(SharedFD stop_fd) const override;
 
  private:
   static constexpr int kCrosvmVmResetExitCode = 32;
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index ffd431d..2da484a 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -802,10 +802,17 @@
 
   if (is_riscv64) {
     qemu_cmd.AddParameter("-kernel");
-  } else {
+    qemu_cmd.AddParameter(instance.bootloader());
+  } else if (is_arm) {
     qemu_cmd.AddParameter("-bios");
+    qemu_cmd.AddParameter(instance.bootloader());
+  } else {
+    qemu_cmd.AddParameter("-drive");
+    qemu_cmd.AddParameter("if=pflash,format=raw,readonly=on,file=",
+                          instance.bootloader());
+    qemu_cmd.AddParameter("-drive");
+    qemu_cmd.AddParameter("if=pflash,format=raw,file=", instance.pflash_path());
   }
-  qemu_cmd.AddParameter(instance.bootloader());
 
   if (instance.gdb_port() > 0) {
     qemu_cmd.AddParameter("-S");
diff --git a/host/libs/vm_manager/vm_manager.h b/host/libs/vm_manager/vm_manager.h
index 9b70477..fe66824 100644
--- a/host/libs/vm_manager/vm_manager.h
+++ b/host/libs/vm_manager/vm_manager.h
@@ -107,8 +107,10 @@
   // Block until the restore work is finished and the guest is running. Only
   // called if a snapshot is being restored.
   //
+  // If FD becomes readable or closed, gives up and returns false.
+  //
   // Must be thread safe.
-  virtual Result<void> WaitForRestoreComplete() const {
+  virtual Result<bool> WaitForRestoreComplete(SharedFD) const {
     return CF_ERR("not implemented");
   }
 };
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 004372f..c5accd1 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -18,10 +18,14 @@
 # Common BoardConfig for all supported architectures.
 #
 
-# Wear 32 bit is currently supported and 6.6 kernels don't support
-# 32 bit devices
+# Some targets still require 32 bit, and 6.6 kernels don't support
+# 32 bit devices (Wear, Go, Auto)
 ifneq (,$(findstring gwear_x86,$(PRODUCT_NAME)))
 TARGET_KERNEL_USE ?= 6.1
+else ifneq (,$(findstring x86_phone,$(PRODUCT_NAME)))
+TARGET_KERNEL_USE ?= 6.1
+else ifneq (,$(findstring x86_tv,$(PRODUCT_NAME)))
+TARGET_KERNEL_USE ?= 6.1
 else
 TARGET_KERNEL_USE ?= 6.6
 endif
diff --git a/shared/config/input/Android.bp b/shared/config/input/Android.bp
index 7db65df..544e31c 100644
--- a/shared/config/input/Android.bp
+++ b/shared/config/input/Android.bp
@@ -26,6 +26,9 @@
     // Install the apex in /vendor/apex
     soc_specific: true,
     prebuilts: [
+        // Set input_device.config_file.apex={apexname} sysprop
+        "com.google.cf.input.config.rc",
+        // Configs
         "Crosvm_Virtio_Multitouch_Touchpad_0.idc",
         "Crosvm_Virtio_Multitouch_Touchscreen_0.idc",
         "Crosvm_Virtio_Multitouch_Touchscreen_1.idc",
@@ -35,6 +38,12 @@
     ],
 }
 
+prebuilt_etc {
+    name: "com.google.cf.input.config.rc",
+    src: "com.google.cf.input.config.rc",
+    installable: false,
+}
+
 prebuilt_defaults {
     name: "crosvm_idc_defaults",
     relative_install_path: "usr/idc",
diff --git a/shared/config/input/com.google.cf.input.config.rc b/shared/config/input/com.google.cf.input.config.rc
new file mode 100644
index 0000000..917c3fb
--- /dev/null
+++ b/shared/config/input/com.google.cf.input.config.rc
@@ -0,0 +1,2 @@
+on property:apex.all.ready=true
+  setprop input_device.config_file.apex com.google.cf.input.config
diff --git a/shared/config/input/file_contexts b/shared/config/input/file_contexts
index e982bd5..3f8e059 100644
--- a/shared/config/input/file_contexts
+++ b/shared/config/input/file_contexts
@@ -1,4 +1,5 @@
 (/.*)?                          u:object_r:vendor_file:s0
+/etc(/.*)?                      u:object_r:vendor_configs_file:s0
 /etc/usr/keylayout(/.*)?\.kl    u:object_r:vendor_keylayout_file:s0
 /etc/usr/keychars(/.*)?\.kcm    u:object_r:vendor_keychars_file:s0
 /etc/usr/idc(/.*)?\.idc         u:object_r:vendor_idc_file:s0
diff --git a/shared/device.mk b/shared/device.mk
index 34d87ea..ae77f41 100644
--- a/shared/device.mk
+++ b/shared/device.mk
@@ -218,6 +218,8 @@
 endif
 DEVICE_MANIFEST_FILE += $(LOCAL_DEVICE_FCM_MANIFEST_FILE)
 
+PRODUCT_CHECK_PREBUILT_MAX_PAGE_SIZE := true
+
 #
 # General files
 #
@@ -258,7 +260,6 @@
 # Install .kcm/.kl/.idc files via input.config apex
 #
 PRODUCT_PACKAGES += com.google.cf.input.config
-PRODUCT_VENDOR_PROPERTIES += input_device.config_file.apex=com.google.cf.input.config
 
 PRODUCT_PACKAGES += \
     fstab.cf.f2fs.hctr2 \
diff --git a/shared/graphics/device_vendor.mk b/shared/graphics/device_vendor.mk
index 2d01146..d13904d 100644
--- a/shared/graphics/device_vendor.mk
+++ b/shared/graphics/device_vendor.mk
@@ -45,7 +45,6 @@
 # Gfxstream Vulkan implementation (Vulkan streamed to the host).
 ifeq ($(TARGET_VULKAN_SUPPORT),true)
 PRODUCT_PACKAGES += com.google.cf.vulkan
-PRODUCT_VENDOR_PROPERTIES += ro.vulkan.apex=com.google.cf.vulkan
 endif
 
 #
diff --git a/shared/sepolicy/system_ext/private/platform_app.te b/shared/sepolicy/system_ext/private/platform_app.te
index 3a789d8..fc1b99f 100644
--- a/shared/sepolicy/system_ext/private/platform_app.te
+++ b/shared/sepolicy/system_ext/private/platform_app.te
@@ -2,3 +2,6 @@
 
 # allow systemui to set boot animation colors
 set_prop(platform_app, bootanim_system_prop);
+
+# allow platform_app/systemui access to fingerprint
+hal_client_domain(platform_app, hal_fingerprint)
diff --git a/shared/tv/device_vendor.mk b/shared/tv/device_vendor.mk
index f6e10ab..6b1de33 100644
--- a/shared/tv/device_vendor.mk
+++ b/shared/tv/device_vendor.mk
@@ -91,3 +91,13 @@
      CuttlefishTetheringOverlayGoogle \
      CuttlefishWifiOverlayGoogle \
      TvWifiOverlayGoogle
+
+# OEM Key:
+#   ATV00       - Schema identifier.
+#   0           - Voice remote not included.
+#   000         - Not a panel TV.
+#   24          - Year of production.
+#   CUTTLEFISH  - Custom OEM key for future use.
+#   EMU         - Last 3 char for custom targeting.
+PRODUCT_PRODUCT_PROPERTIES += \
+    ro.oem.key1=ATV00000024CUTTLEFISHEMU
diff --git a/system_image/Android.bp b/system_image/Android.bp
index e4295da..83dc891 100644
--- a/system_image/Android.bp
+++ b/system_image/Android.bp
@@ -443,6 +443,7 @@
         "printflags", // base_system
         "privapp-permissions-platform.xml", // base_system
         "prng_seeder", // base_system
+        "public.libraries.android.txt",
         "recovery-persist", // base_system
         "recovery-refresh", // generic_system
         "requestsync", // media_system
@@ -550,6 +551,7 @@
                 "BlockedNumberProvider", // handheld_system
                 "BluetoothMidiService", // handheld_system
                 "BookmarkProvider", // handheld_system
+                "build_flag_system", // base_system
                 "BuiltInPrintService", // handheld_system
                 "CalendarProvider", // handheld_system
                 "CallLogBackup", // telephony_system
diff --git a/tools/create_base_image_gce.sh b/tools/create_base_image_gce.sh
index ba189cd..7073c83 100755
--- a/tools/create_base_image_gce.sh
+++ b/tools/create_base_image_gce.sh
@@ -37,6 +37,19 @@
 # Stuff we need to get build support
 sudo apt install -y debhelper ubuntu-dev-tools equivs "${extra_packages[@]}"
 
+function install_bazel() {
+  # From https://bazel.build/install/ubuntu
+  echo "Installing bazel"
+  sudo apt install apt-transport-https curl gnupg -y
+  curl -fsSL https://bazel.build/bazel-release.pub.gpg | gpg --dearmor >bazel-archive-keyring.gpg
+  sudo mv bazel-archive-keyring.gpg /usr/share/keyrings
+  echo "deb [arch=amd64 signed-by=/usr/share/keyrings/bazel-archive-keyring.gpg] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list
+  # bazel needs the zip command to gather test outputs but doesn't depend on it
+  sudo apt-get update && sudo apt-get install -y bazel zip unzip
+}
+
+install_bazel
+
 # Resize
 sudo apt install -y cloud-utils
 sudo apt install -y cloud-guest-utils
@@ -94,7 +107,7 @@
 sudo chroot /mnt/image /usr/bin/apt update
 sudo chroot /mnt/image /usr/bin/apt install -y "${tmp_debs[@]}"
 # install tools dependencies
-sudo chroot /mnt/image /usr/bin/apt install -y openjdk-17-jre
+sudo chroot /mnt/image /usr/bin/apt install -y openjdk-21-jre
 sudo chroot /mnt/image /usr/bin/apt install -y unzip bzip2 lzop
 sudo chroot /mnt/image /usr/bin/apt install -y aapt
 sudo chroot /mnt/image /usr/bin/apt install -y screen # needed by tradefed
diff --git a/tools/launch_cvd_arm64_server_docker.sh b/tools/launch_cvd_arm64_server_docker.sh
index ac1a939..d3fcceb 100755
--- a/tools/launch_cvd_arm64_server_docker.sh
+++ b/tools/launch_cvd_arm64_server_docker.sh
@@ -18,9 +18,10 @@
 color_yellow="\033[0;33m"
 
 # validate number of arguments
-if [ "$#" -lt 1 ] || [ "$#" -gt 3 ]; then
-  echo "This script requires 1 mandatory and 2 optional parameters,"
-  echo "server address and optionally cvd instances per docker, and number of docker instances to invoke"
+if [ "$#" -lt 1 ] || [ "$#" -gt 4 ]; then
+  echo "This script requires 1 mandatory and 3 optional parameters,"
+  echo "server address and optionally cvd instances per docker, and number of " \
+       "docker instances to invoke, and vendor_boot image to replace."
   exit 1
 fi
 
@@ -28,6 +29,7 @@
 # $1: ARM server address
 # $2: CVD Instance number per docker (Optional, default is 1)
 # $3: Docker Instance number (Optional, default is 1)
+# $4: Vendor Boot Image path (Optional, default is "")
 server=$1
 
 if [ "$#" -lt 2 ]; then
@@ -42,6 +44,12 @@
  num_dockers=$3
 fi
 
+if [ "$#" -lt 4 ]; then
+ vendor_boot_image=""
+else
+ vendor_boot_image=$4
+fi
+
 # set img_dir and cvd_host_tool_dir
 img_dir=${ANDROID_PRODUCT_OUT:-$PWD}
 cvd_host_tool_dir=${ANDROID_HOST_OUT:+"$ANDROID_HOST_OUT/../linux_musl-arm64"}
@@ -58,6 +66,10 @@
   cvd_home_files=($(rsync -rzan --recursive $img_dir/bootloader --out-format="%n" $img_dir/*.img $server:~/$cvd_home_dir --info=name2 | awk '{print $1}'))
 fi
 
+if [[ $vendor_boot_image != "" ]]; then
+  scp $vendor_boot_image $server:~/$cvd_home_dir/vendor_boot.img
+fi
+
 # upload cvd-host_package.tar.gz into ARM server
 temp_dir=/tmp/cvd_dist
 rm -rf $temp_dir
diff --git a/vsoc_arm64_only/slim/aosp_cf.mk b/vsoc_arm64_only/slim/aosp_cf.mk
index 821b79e..1b9ab17 100644
--- a/vsoc_arm64_only/slim/aosp_cf.mk
+++ b/vsoc_arm64_only/slim/aosp_cf.mk
@@ -37,7 +37,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 $(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
 
 #
diff --git a/vsoc_riscv64/phone/aosp_cf.mk b/vsoc_riscv64/phone/aosp_cf.mk
index b5af1b9..668ea09 100644
--- a/vsoc_riscv64/phone/aosp_cf.mk
+++ b/vsoc_riscv64/phone/aosp_cf.mk
@@ -36,7 +36,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 LOCAL_ENABLE_WIDEVINE := false
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
 
diff --git a/vsoc_riscv64/slim/aosp_cf.mk b/vsoc_riscv64/slim/aosp_cf.mk
index ce71274..285317a 100644
--- a/vsoc_riscv64/slim/aosp_cf.mk
+++ b/vsoc_riscv64/slim/aosp_cf.mk
@@ -37,7 +37,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 LOCAL_ENABLE_WIDEVINE := false
 $(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
 
diff --git a/vsoc_x86_64/phone/aosp_cf.mk b/vsoc_x86_64/phone/aosp_cf.mk
index 41d7d29..a101d68 100644
--- a/vsoc_x86_64/phone/aosp_cf.mk
+++ b/vsoc_x86_64/phone/aosp_cf.mk
@@ -36,7 +36,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 $(call inherit-product, device/google/cuttlefish/shared/phone/device_vendor.mk)
 
 # Nested virtualization support
diff --git a/vsoc_x86_64_only/slim/aosp_cf.mk b/vsoc_x86_64_only/slim/aosp_cf.mk
index 47b94c2..b38e918 100644
--- a/vsoc_x86_64_only/slim/aosp_cf.mk
+++ b/vsoc_x86_64_only/slim/aosp_cf.mk
@@ -37,7 +37,6 @@
 #
 # All components inherited here go to vendor image
 #
-LOCAL_PREFER_VENDOR_APEX := true
 $(call inherit-product, device/google/cuttlefish/shared/slim/device_vendor.mk)
 
 #