Revert "Revert "Enable f2fs sparse images + qcow changes""

This reverts commit 6efd9aa78b9ffa5ca52ba9227748d6dbe4641d11.

Test: TreeHugger with later changes
Change-Id: I32393d86843da3cb98501fda054188141d27cfc5
diff --git a/host/commands/assemble_cvd/Android.bp b/host/commands/assemble_cvd/Android.bp
index fcfa903..807f18c 100644
--- a/host/commands/assemble_cvd/Android.bp
+++ b/host/commands/assemble_cvd/Android.bp
@@ -53,6 +53,7 @@
         "libz",
     ],
     static_libs: [
+        "libsparse",
         "libcuttlefish_host_config",
         "libcuttlefish_vm_manager",
         "libgflags",
diff --git a/host/commands/assemble_cvd/flags.cc b/host/commands/assemble_cvd/flags.cc
index f9b183d..241912b 100644
--- a/host/commands/assemble_cvd/flags.cc
+++ b/host/commands/assemble_cvd/flags.cc
@@ -1,6 +1,10 @@
 #include "host/commands/assemble_cvd/flags.h"
 
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <sys/statvfs.h>
+#include <unistd.h>
 
 #include <algorithm>
 #include <iostream>
@@ -68,10 +72,6 @@
               "be vendor_boot.img in the directory specified by -system_image_dir.");
 DEFINE_int32(memory_mb, 2048,
              "Total amount of memory available for guest, MB.");
-DEFINE_string(mobile_interface, ForCurrentInstance("cvd-mbr-"),
-              "Network interface to use for mobile networking");
-DEFINE_string(mobile_tap_name, ForCurrentInstance("cvd-mtap-"),
-              "The name of the tap interface to use for mobile");
 DEFINE_string(serial_number, ForCurrentInstance("CUTTLEFISHCVD"),
               "Serial number to use for the device");
 DEFINE_string(assembly_dir,
@@ -93,13 +93,15 @@
 DEFINE_string(misc_image, "",
               "Location of the misc partition image. If the image does not "
               "exist, a blank new misc partition image is created.");
-DEFINE_string(composite_disk, "", "Location of the composite disk image. "
-                                  "If empty, a composite disk is not used.");
+DEFINE_string(composite_disk, "", "Location of the composite disk image. ");
 
 DEFINE_bool(deprecated_boot_completed, false, "Log boot completed message to"
             " host kernel. This is only used during transition of our clients."
             " Will be deprecated soon.");
-DEFINE_bool(start_vnc_server, false, "Whether to start the vnc server process.");
+DEFINE_bool(start_vnc_server, false, "Whether to start the vnc server process. "
+                                     "The VNC server runs at port 6443 + i for "
+                                     "the vsoc-i user or CUTTLEFISH_INSTANCE=i, "
+                                     "starting from 1.");
 
 DEFINE_bool(start_webrtc, false, "Whether to start the webrtc process.");
 
@@ -123,8 +125,6 @@
         false,
         "If enabled, exposes local adb service through a websocket.");
 
-DEFINE_int32(vnc_server_port, ForCurrentInstance(6444),
-             "The port on which the vnc server should listen");
 DEFINE_string(adb_mode, "vsock_half_tunnel",
               "Mode for ADB connection."
               "'vsock_tunnel' for a TCP connection tunneled through vsock, "
@@ -135,11 +135,6 @@
 DEFINE_bool(run_adb_connector, true,
             "Maintain adb connection by sending 'adb connect' commands to the "
             "server. Only relevant with -adb_mode=tunnel or vsock_tunnel");
-DEFINE_string(wifi_tap_name, ForCurrentInstance("cvd-wtap-"),
-              "The name of the tap interface to use for wifi");
-DEFINE_int32(vsock_guest_cid,
-             vsoc::GetDefaultPerInstanceVsockCid(),
-             "Guest identifier for vsock. Disabled if under 3.");
 
 DEFINE_string(uuid, vsoc::ForCurrentInstance(vsoc::kDefaultUuidPrefix),
               "UUID to use for the device. Random if not specified");
@@ -169,6 +164,12 @@
              "the slot will be chosen based on the misc partition if using a "
              "bootloader. It will default to 'a' if empty and not using a "
              "bootloader.");
+DEFINE_int32(num_instances, 1, "Number of Android guests to launch");
+DEFINE_bool(resume, true, "Resume using the disk from the last session, if "
+                          "possible. i.e., if --noresume is passed, the disk "
+                          "will be reset to the state it was initially launched "
+                          "in. This flag is ignored if the underlying partition "
+                          "images have been updated since the first launch.");
 
 namespace {
 
@@ -222,19 +223,20 @@
   return config.ForDefaultInstance().PerInstancePath("cuttlefish_config.json");
 }
 
-int GetHostPort() {
-  constexpr int kFirstHostPort = 6520;
-  return vsoc::ForCurrentInstance(kFirstHostPort);
-}
-
 int NumStreamers() {
   auto start_flags = {FLAGS_start_vnc_server, FLAGS_start_webrtc};
   return std::count(start_flags.begin(), start_flags.end(), true);
 }
 
+std::string StrForInstance(const std::string& prefix, int num) {
+  std::ostringstream stream;
+  stream << prefix << std::setfill('0') << std::setw(2) << num;
+  return stream.str();
+}
+
 // Initializes the config object and saves it to file. It doesn't return it, all
 // further uses of the config should happen through the singleton
-bool InitializeCuttlefishConfiguration(
+vsoc::CuttlefishConfig InitializeCuttlefishConfiguration(
     const cvd::BootImageUnpacker& boot_image_unpacker,
     const cvd::FetcherConfig& fetcher_config) {
   // At most one streamer can be started.
@@ -242,28 +244,20 @@
 
   vsoc::CuttlefishConfig tmp_config_obj;
   tmp_config_obj.set_assembly_dir(FLAGS_assembly_dir);
-  auto instance = tmp_config_obj.ForDefaultInstance();
-  // Set this first so that calls to PerInstancePath below are correct
-  instance.set_instance_dir(FLAGS_instance_dir);
   if (!vm_manager::VmManager::IsValidName(FLAGS_vm_manager)) {
-    LOG(ERROR) << "Invalid vm_manager: " << FLAGS_vm_manager;
-    return false;
+    LOG(FATAL) << "Invalid vm_manager: " << FLAGS_vm_manager;
   }
   if (!vm_manager::VmManager::IsValidName(FLAGS_vm_manager)) {
-    LOG(ERROR) << "Invalid vm_manager: " << FLAGS_vm_manager;
-    return false;
+    LOG(FATAL) << "Invalid vm_manager: " << FLAGS_vm_manager;
   }
   tmp_config_obj.set_vm_manager(FLAGS_vm_manager);
   tmp_config_obj.set_gpu_mode(FLAGS_gpu_mode);
   if (vm_manager::VmManager::ConfigureGpuMode(tmp_config_obj.vm_manager(),
                                               tmp_config_obj.gpu_mode()).empty()) {
-    LOG(ERROR) << "Invalid gpu_mode=" << FLAGS_gpu_mode <<
+    LOG(FATAL) << "Invalid gpu_mode=" << FLAGS_gpu_mode <<
                " does not work with vm_manager=" << FLAGS_vm_manager;
-    return false;
   }
 
-  instance.set_serial_number(FLAGS_serial_number);
-
   tmp_config_obj.set_cpus(FLAGS_cpus);
   tmp_config_obj.set_memory_mb(FLAGS_memory_mb);
 
@@ -275,10 +269,6 @@
   tmp_config_obj.set_gdb_flag(FLAGS_qemu_gdb);
   std::vector<std::string> adb = android::base::Split(FLAGS_adb_mode, ",");
   tmp_config_obj.set_adb_mode(std::set<std::string>(adb.begin(), adb.end()));
-  instance.set_host_port(GetHostPort());
-  instance.set_adb_ip_and_port("127.0.0.1:" + std::to_string(GetHostPort()));
-
-  instance.set_device_title(FLAGS_device_title);
   std::string discovered_kernel = fetcher_config.FindCvdFileWithSuffix(kKernelDefaultPath);
   std::string foreign_kernel = FLAGS_kernel_path.size() ? FLAGS_kernel_path : discovered_kernel;
   if (foreign_kernel.size()) {
@@ -298,8 +288,7 @@
   auto ramdisk_path = tmp_config_obj.AssemblyPath("ramdisk.img");
   auto vendor_ramdisk_path = tmp_config_obj.AssemblyPath("vendor_ramdisk.img");
   if (!boot_image_unpacker.HasRamdiskImage()) {
-    LOG(INFO) << "A ramdisk is required, but the boot image did not have one.";
-    return false;
+    LOG(FATAL) << "A ramdisk is required, but the boot image did not have one.";
   }
 
   tmp_config_obj.set_boot_image_kernel_cmdline(boot_image_unpacker.kernel_cmdline());
@@ -309,8 +298,6 @@
   tmp_config_obj.set_guest_force_normal_boot(FLAGS_guest_force_normal_boot);
   tmp_config_obj.set_extra_kernel_cmdline(FLAGS_extra_kernel_cmdline);
 
-  tmp_config_obj.set_virtual_disk_paths({FLAGS_composite_disk});
-
   tmp_config_obj.set_ramdisk_image_path(ramdisk_path);
   tmp_config_obj.set_vendor_ramdisk_image_path(vendor_ramdisk_path);
 
@@ -336,15 +323,6 @@
   tmp_config_obj.set_config_server_binary(
       vsoc::DefaultHostArtifactsPath("bin/config_server"));
 
-  instance.set_mobile_bridge_name(FLAGS_mobile_interface);
-  instance.set_mobile_tap_name(FLAGS_mobile_tap_name);
-
-  instance.set_wifi_tap_name(FLAGS_wifi_tap_name);
-
-  instance.set_vsock_guest_cid(FLAGS_vsock_guest_cid);
-
-  instance.set_uuid(FLAGS_uuid);
-
   tmp_config_obj.set_qemu_binary(FLAGS_qemu_binary);
   tmp_config_obj.set_crosvm_binary(FLAGS_crosvm_binary);
   tmp_config_obj.set_console_forwarder_binary(
@@ -355,7 +333,6 @@
   tmp_config_obj.set_enable_vnc_server(FLAGS_start_vnc_server);
   tmp_config_obj.set_vnc_server_binary(
       vsoc::DefaultHostArtifactsPath("bin/vnc_server"));
-  instance.set_vnc_server_port(FLAGS_vnc_server_port);
 
   tmp_config_obj.set_enable_webrtc(FLAGS_start_webrtc);
   tmp_config_obj.set_webrtc_binary(
@@ -394,6 +371,41 @@
 
   tmp_config_obj.set_cuttlefish_env_path(GetCuttlefishEnvPath());
 
+  std::vector<int> instance_nums;
+  for (int i = 0; i < FLAGS_num_instances; i++) {
+    instance_nums.push_back(vsoc::GetInstance() + i);
+  }
+
+  for (const auto& num : instance_nums) {
+    auto instance = tmp_config_obj.ForInstance(num);
+    auto const_instance = const_cast<const vsoc::CuttlefishConfig&>(tmp_config_obj)
+        .ForInstance(num);
+    // Set this first so that calls to PerInstancePath below are correct
+    instance.set_instance_dir(FLAGS_instance_dir + "." + std::to_string(num));
+    instance.set_serial_number(FLAGS_serial_number + std::to_string(num));
+
+    instance.set_mobile_bridge_name(StrForInstance("cvd-mbr-", num));
+    instance.set_mobile_tap_name(StrForInstance("cvd-mtap-", num));
+
+    instance.set_wifi_tap_name(StrForInstance("cvd-wtap-", num));
+
+    instance.set_vsock_guest_cid(3 + num - 1);
+
+    instance.set_uuid(FLAGS_uuid);
+
+    instance.set_vnc_server_port(6444 + num - 1);
+    instance.set_host_port(6520 + num - 1);
+    instance.set_adb_ip_and_port("127.0.0.1:" + std::to_string(6520 + num - 1));
+
+    instance.set_device_title(FLAGS_device_title);
+
+    instance.set_virtual_disk_paths({const_instance.PerInstancePath("overlay.img")});
+  }
+
+  return tmp_config_obj;
+}
+
+bool SaveConfig(const vsoc::CuttlefishConfig& tmp_config_obj) {
   auto config_file = GetConfigFilePath(tmp_config_obj);
   auto config_link = vsoc::GetGlobalConfigFileLink();
   // Save the config object before starting any host process
@@ -456,15 +468,77 @@
   return ResolveInstanceFiles();
 }
 
-bool CleanPriorFiles() {
-  // Everything on the instance directory
-  std::string prior_files = FLAGS_instance_dir + "/*";
-  // Everything in the assembly directory
-  prior_files += " " + FLAGS_assembly_dir + "/*";
-  // The environment file
-  prior_files += " " + GetCuttlefishEnvPath();
-  // The global link to the config file
-  prior_files += " " + vsoc::GetGlobalConfigFileLink();
+std::string cpp_basename(const std::string& str) {
+  char* copy = strdup(str.c_str()); // basename may modify its argument
+  std::string ret(basename(copy));
+  free(copy);
+  return ret;
+}
+
+bool CleanPriorFiles(const std::string& path, const std::set<std::string>& preserving) {
+  if (preserving.count(cpp_basename(path))) {
+    LOG(INFO) << "Preserving: " << path;
+    return true;
+  }
+  struct stat statbuf;
+  if (lstat(path.c_str(), &statbuf) < 0) {
+    int error_num = errno;
+    if (error_num == ENOENT) {
+      return true;
+    } else {
+      LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
+      return false;
+    }
+  }
+  if ((statbuf.st_mode & S_IFMT) != S_IFDIR) {
+    LOG(INFO) << "Deleting: " << path;
+    if (unlink(path.c_str()) < 0) {
+      int error_num = errno;
+      LOG(ERROR) << "Could not unlink \"" << path << "\", error was " << strerror(error_num);
+      return false;
+    }
+    return true;
+  }
+  std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(path.c_str()), closedir);
+  if (!dir) {
+    int error_num = errno;
+    LOG(ERROR) << "Could not clean \"" << path << "\": error was " << strerror(error_num);
+    return false;
+  }
+  for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
+    std::string entity_name(entity->d_name);
+    if (entity_name == "." || entity_name == "..") {
+      continue;
+    }
+    std::string entity_path = path + "/" + entity_name;
+    if (!CleanPriorFiles(entity_path.c_str(), preserving)) {
+      return false;
+    }
+  }
+  if (rmdir(path.c_str()) < 0) {
+    if (!(errno == EEXIST || errno == ENOTEMPTY)) {
+      // If EEXIST or ENOTEMPTY, probably because a file was preserved
+      int error_num = errno;
+      LOG(ERROR) << "Could not rmdir \"" << path << "\", error was " << strerror(error_num);
+      return false;
+    }
+  }
+  return true;
+}
+
+bool CleanPriorFiles(const std::vector<std::string>& paths, const std::set<std::string>& preserving) {
+  std::string prior_files;
+  for (auto path : paths) {
+    struct stat statbuf;
+    if (stat(path.c_str(), &statbuf) < 0 && errno != ENOENT) {
+      // If ENOENT, it doesn't exist yet, so there is no work to do'
+      int error_num = errno;
+      LOG(ERROR) << "Could not stat \"" << path << "\": " << strerror(error_num);
+      return false;
+    }
+    bool is_directory = (statbuf.st_mode & S_IFMT) == S_IFDIR;
+    prior_files += (is_directory ? (path + "/*") : path) + " ";
+  }
   LOG(INFO) << "Assuming prior files of " << prior_files;
   std::string lsof_cmd = "lsof -t " + prior_files + " >/dev/null 2>&1";
   int rval = std::system(lsof_cmd.c_str());
@@ -473,15 +547,31 @@
     LOG(ERROR) << "Clean aborted: files are in use";
     return false;
   }
-  std::string clean_command = "rm -rf " + prior_files;
-  rval = std::system(clean_command.c_str());
-  if (WEXITSTATUS(rval) != 0) {
-    LOG(ERROR) << "Remove of files failed";
-    return false;
+  for (const auto& path : paths) {
+    if (!CleanPriorFiles(path, preserving)) {
+      LOG(ERROR) << "Remove of file under \"" << path << "\" failed";
+      return false;
+    }
   }
   return true;
 }
 
+bool CleanPriorFiles(const vsoc::CuttlefishConfig& config, const std::set<std::string>& preserving) {
+  std::vector<std::string> paths = {
+    // Everything in the assembly directory
+    FLAGS_assembly_dir,
+    // The environment file
+    GetCuttlefishEnvPath(),
+    // The global link to the config file
+    vsoc::GetGlobalConfigFileLink(),
+  };
+  for (const auto& instance : config.Instances()) {
+    paths.push_back(instance.instance_dir());
+  }
+  paths.push_back(FLAGS_instance_dir);
+  return CleanPriorFiles(paths, preserving);
+}
+
 bool DecompressKernel(const std::string& src, const std::string& dst) {
   cvd::Command decomp_cmd(vsoc::DefaultHostArtifactsPath("bin/extract-vmlinux"));
   decomp_cmd.AddParameter(src);
@@ -537,23 +627,20 @@
   return partitions;
 }
 
-bool ShouldCreateCompositeDisk() {
-  if (FLAGS_vm_manager == vm_manager::CrosvmManager::name()) {
-    // The crosvm implementation is very fast to rebuild but also more brittle due to being split
-    // into multiple files. The QEMU implementation is slow to build, but completely self-contained
-    // at that point. Therefore, always rebuild on crosvm but check if it is necessary for QEMU.
-    return true;
-  }
-  auto composite_age = cvd::FileModificationTime(FLAGS_composite_disk);
+std::chrono::system_clock::time_point LastUpdatedInputDisk() {
+  std::chrono::system_clock::time_point ret;
   for (auto& partition : disk_config()) {
-    auto partition_age = cvd::FileModificationTime(partition.image_file_path);
-    if (partition_age >= composite_age) {
-      LOG(INFO) << "composite disk age was \"" << std::chrono::system_clock::to_time_t(composite_age) << "\", "
-                << "partition age was \"" << std::chrono::system_clock::to_time_t(partition_age) << "\"";
-      return true;
+    auto partition_mod_time = cvd::FileModificationTime(partition.image_file_path);
+    if (partition_mod_time > ret) {
+      ret = partition_mod_time;
     }
   }
-  return false;
+  return ret;
+}
+
+bool ShouldCreateCompositeDisk() {
+  auto composite_age = cvd::FileModificationTime(FLAGS_composite_disk);
+  return composite_age < LastUpdatedInputDisk();
 }
 
 bool ConcatRamdisks(const std::string& new_ramdisk_path, const std::string& ramdisk_a_path,
@@ -606,7 +693,7 @@
     }
     std::string header_path = config.AssemblyPath("gpt_header.img");
     std::string footer_path = config.AssemblyPath("gpt_footer.img");
-    create_composite_disk(disk_config(), header_path, footer_path, FLAGS_composite_disk);
+    CreateCompositeDisk(disk_config(), header_path, footer_path, FLAGS_composite_disk);
   } else {
     auto existing_size = cvd::FileSize(FLAGS_composite_disk);
     auto available_space = AvailableSpaceAtPath(FLAGS_composite_disk);
@@ -616,7 +703,7 @@
       LOG(ERROR) << "Got " << available_space;
       return false;
     }
-    aggregate_image(disk_config(), FLAGS_composite_disk);
+    AggregateImage(disk_config(), FLAGS_composite_disk);
   }
   return true;
 }
@@ -630,39 +717,72 @@
     exit(AssemblerExitCodes::kArgumentParsingError);
   }
 
-  // Clean up prior files before saving the config file (doing it after would
-  // delete it)
-  if (!CleanPriorFiles()) {
-    LOG(ERROR) << "Failed to clean prior files";
-    exit(AssemblerExitCodes::kPrioFilesCleanupError);
-  }
-  // Create assembly directory if it doesn't exist.
-  if (!cvd::DirectoryExists(FLAGS_assembly_dir.c_str())) {
-    LOG(INFO) << "Setting up " << FLAGS_assembly_dir;
-    if (mkdir(FLAGS_assembly_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
-      LOG(ERROR) << "Failed to create assembly directory: "
-                 << FLAGS_assembly_dir << ". Error: " << errno;
-      exit(AssemblerExitCodes::kAssemblyDirCreationError);
+  auto boot_img_unpacker =
+    cvd::BootImageUnpacker::FromImages(FLAGS_boot_image,
+                                      FLAGS_vendor_boot_image);
+  {
+    // The config object is created here, but only exists in memory until the
+    // SaveConfig line below. Don't launch cuttlefish subprocesses between these
+    // two operations, as those will assume they can read the config object from
+    // disk.
+    auto config = InitializeCuttlefishConfiguration(*boot_img_unpacker, fetcher_config);
+    std::set<std::string> preserving;
+    if (FLAGS_resume && ShouldCreateCompositeDisk()) {
+      LOG(WARNING) << "Requested resuming a previous session (the default behavior) "
+                   << "but the base images have changed under the overlay, making the "
+                   << "overlay incompatible. Wiping the overlay files.";
+    } else if (FLAGS_resume && !ShouldCreateCompositeDisk()) {
+      preserving.insert("overlay.img");
+      preserving.insert("gpt_header.img");
+      preserving.insert("gpt_footer.img");
+      preserving.insert("composite.img");
+      preserving.insert("access-kregistry");
     }
-  }
-  // Create instance directory if it doesn't exist.
-  if (!cvd::DirectoryExists(FLAGS_instance_dir.c_str())) {
-    LOG(INFO) << "Setting up " << FLAGS_instance_dir;
-    if (mkdir(FLAGS_instance_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
-      LOG(ERROR) << "Failed to create instance directory: "
-                 << FLAGS_instance_dir << ". Error: " << errno;
-      exit(AssemblerExitCodes::kInstanceDirCreationError);
+    if (!CleanPriorFiles(config, preserving)) {
+      LOG(ERROR) << "Failed to clean prior files";
+      exit(AssemblerExitCodes::kPrioFilesCleanupError);
+    }
+    // Create assembly directory if it doesn't exist.
+    if (!cvd::DirectoryExists(FLAGS_assembly_dir.c_str())) {
+      LOG(INFO) << "Setting up " << FLAGS_assembly_dir;
+      if (mkdir(FLAGS_assembly_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
+          && errno != EEXIST) {
+        LOG(ERROR) << "Failed to create assembly directory: "
+                  << FLAGS_assembly_dir << ". Error: " << errno;
+        exit(AssemblerExitCodes::kAssemblyDirCreationError);
+      }
+    }
+    for (const auto& instance : config.Instances()) {
+      // Create instance directory if it doesn't exist.
+      if (!cvd::DirectoryExists(instance.instance_dir().c_str())) {
+        LOG(INFO) << "Setting up " << FLAGS_instance_dir << ".N";
+        if (mkdir(instance.instance_dir().c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
+            && errno != EEXIST) {
+          LOG(ERROR) << "Failed to create instance directory: "
+                    << FLAGS_instance_dir << ". Error: " << errno;
+          exit(AssemblerExitCodes::kInstanceDirCreationError);
+        }
+      }
+      auto internal_dir = instance.instance_dir() + "/" + vsoc::kInternalDirName;
+      if (!cvd::DirectoryExists(internal_dir)) {
+        if (mkdir(internal_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0
+           && errno != EEXIST) {
+          LOG(ERROR) << "Failed to create internal instance directory: "
+                    << internal_dir << ". Error: " << errno;
+          exit(AssemblerExitCodes::kInstanceDirCreationError);
+        }
+      }
+    }
+    if (!SaveConfig(config)) {
+      LOG(ERROR) << "Failed to initialize configuration";
+      exit(AssemblerExitCodes::kCuttlefishConfigurationInitError);
     }
   }
 
-  auto internal_dir = FLAGS_instance_dir + "/" + vsoc::kInternalDirName;
-  if (!cvd::DirectoryExists(internal_dir)) {
-    if (mkdir(internal_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) <
-        0) {
-      LOG(ERROR) << "Failed to create internal instance directory: "
-                 << internal_dir << ". Error: " << errno;
-      exit(AssemblerExitCodes::kInstanceDirCreationError);
-    }
+  std::string first_instance = FLAGS_instance_dir + "." + std::to_string(vsoc::GetInstance());
+  if (symlink(first_instance.c_str(), FLAGS_instance_dir.c_str()) < 0) {
+    LOG(ERROR) << "Could not symlink \"" << first_instance << "\" to \"" << FLAGS_instance_dir << "\"";
+    exit(cvd::kCuttlefishConfigurationInitError);
   }
 
   if (!cvd::FileHasContent(FLAGS_boot_image)) {
@@ -675,14 +795,6 @@
     exit(cvd::kCuttlefishConfigurationInitError);
   }
 
-  auto boot_img_unpacker =
-    cvd::BootImageUnpacker::FromImages(FLAGS_boot_image,
-                                       FLAGS_vendor_boot_image);
-
-  if (!InitializeCuttlefishConfiguration(*boot_img_unpacker, fetcher_config)) {
-    LOG(ERROR) << "Failed to initialize configuration";
-    exit(AssemblerExitCodes::kCuttlefishConfigurationInitError);
-  }
   // Do this early so that the config object is ready for anything that needs it
   auto config = vsoc::CuttlefishConfig::Get();
   if (!config) {
@@ -751,9 +863,10 @@
     CreateBlankImage(FLAGS_metadata_image, FLAGS_blank_metadata_image_mb, "none");
   }
 
-  if (!cvd::FileExists(config->ForDefaultInstance().access_kregistry_path())) {
-    CreateBlankImage(config->ForDefaultInstance().access_kregistry_path(), 1,
-                     "none", "64K");
+  for (const auto& instance : config->Instances()) {
+    if (!cvd::FileExists(instance.access_kregistry_path())) {
+      CreateBlankImage(instance.access_kregistry_path(), 1, "none", "64K");
+    }
   }
 
   if (SuperImageNeedsRebuilding(fetcher_config, *config)) {
@@ -769,11 +882,26 @@
     }
   }
 
-  // Check that the files exist
-  for (const auto& file : config->virtual_disk_paths()) {
-    if (!file.empty() && !cvd::FileHasContent(file.c_str())) {
-      LOG(ERROR) << "File not found: " << file;
-      exit(cvd::kCuttlefishConfigurationInitError);
+  for (auto instance : config->Instances()) {
+    auto overlay_path = instance.PerInstancePath("overlay.img");
+    if (!cvd::FileExists(overlay_path) || ShouldCreateCompositeDisk() || !FLAGS_resume
+        || cvd::FileModificationTime(overlay_path) < cvd::FileModificationTime(FLAGS_composite_disk)) {
+      if (FLAGS_resume) {
+        LOG(WARNING) << "Requested to continue an existing session, but the overlay was "
+                     << "newer than its underlying composite disk. Wiping the overlay.";
+      }
+      CreateQcowOverlay(config->crosvm_binary(), FLAGS_composite_disk, overlay_path);
+      CreateBlankImage(instance.access_kregistry_path(), 1, "none", "64K");
+    }
+  }
+
+  for (auto instance : config->Instances()) {
+    // Check that the files exist
+    for (const auto& file : instance.virtual_disk_paths()) {
+      if (!file.empty() && !cvd::FileHasContent(file.c_str())) {
+        LOG(ERROR) << "File not found: " << file;
+        exit(cvd::kCuttlefishConfigurationInitError);
+      }
     }
   }
 
diff --git a/host/commands/assemble_cvd/image_aggregator.cc b/host/commands/assemble_cvd/image_aggregator.cc
index 17fe9db..eac6a2e 100644
--- a/host/commands/assemble_cvd/image_aggregator.cc
+++ b/host/commands/assemble_cvd/image_aggregator.cc
@@ -16,6 +16,11 @@
 
 #include "host/commands/assemble_cvd/image_aggregator.h"
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+
 #include <fstream>
 #include <string>
 #include <vector>
@@ -23,6 +28,7 @@
 #include <glog/logging.h>
 #include <json/json.h>
 #include <google/protobuf/text_format.h>
+#include <sparse/sparse.h>
 
 #include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
@@ -38,15 +44,36 @@
 
 const std::string BPTTOOL_FILE_PATH = "bin/cf_bpttool";
 
-Json::Value bpttool_input(const std::vector<ImagePartition>& partitions) {
+Json::Value BpttoolInput(const std::vector<ImagePartition>& partitions) {
   std::vector<off_t> file_sizes;
   off_t total_size = 20 << 20; // 20 MB for padding
   for (auto& partition : partitions) {
-    off_t partition_file_size = cvd::FileSize(partition.image_file_path);
-    if (partition_file_size == 0) {
-      LOG(FATAL) << "Expected partition file \"" << partition.image_file_path
-                 << "\" but it was missing";
+    LOG(INFO) << "Examining " << partition.label;
+    auto file = cvd::SharedFD::Open(partition.image_file_path.c_str(), O_RDONLY);
+    if (!file->IsOpen()) {
+      LOG(FATAL) << "Could not open \"" << partition.image_file_path
+                 << "\": " << file->StrError();
+      break;
     }
+    int fd = file->UNMANAGED_Dup();
+    auto sparse = sparse_file_import(fd, /* verbose */ false, /* crc */ false);
+    off_t partition_file_size = 0;
+    if (sparse) {
+      partition_file_size = sparse_file_len(sparse, /* sparse */ false,
+                                            /* crc */ true);
+      sparse_file_destroy(sparse);
+      close(fd);
+      LOG(INFO) << "was sparse";
+    } else {
+      partition_file_size = cvd::FileSize(partition.image_file_path);
+      if (partition_file_size == 0) {
+        LOG(FATAL) << "Could not get file size of \"" << partition.image_file_path
+                  << "\"";
+        break;
+      }
+      LOG(INFO) << "was not sparse";
+    }
+    LOG(INFO) << "size was " << partition_file_size;
     total_size += partition_file_size;
     file_sizes.push_back(partition_file_size);
   }
@@ -65,7 +92,7 @@
   return bpttool_input_json;
 }
 
-std::string create_file(size_t len) {
+std::string CreateFile(size_t len) {
   char file_template[] = "/tmp/diskXXXXXX";
   int fd = mkstemp(file_template);
   if (fd < 0) {
@@ -97,7 +124,7 @@
   for (auto& bpt_partition: bpt_file["partitions"]) {
     if (bpt_partition["offset"].asUInt64() != previous_end) {
       ComponentDisk* component = disk.add_component_disks();
-      component->set_file_path(create_file(bpt_partition["offset"].asUInt64() - previous_end));
+      component->set_file_path(CreateFile(bpt_partition["offset"].asUInt64() - previous_end));
       component->set_offset(previous_end);
     }
     ComponentDisk* component = disk.add_component_disks();
@@ -113,7 +140,7 @@
   size_t footer_start = bpt_file["settings"]["disk_size"].asUInt64() - GPT_FOOTER_SIZE;
   if (footer_start != previous_end) {
     ComponentDisk* component = disk.add_component_disks();
-    component->set_file_path(create_file(footer_start - previous_end));
+    component->set_file_path(CreateFile(footer_start - previous_end));
     component->set_offset(previous_end);
   }
   ComponentDisk* footer = disk.add_component_disks();
@@ -123,7 +150,7 @@
   return disk;
 }
 
-cvd::SharedFD json_to_fd(const Json::Value& json) {
+cvd::SharedFD JsonToFd(const Json::Value& json) {
   Json::FastWriter json_writer;
   std::string json_string = json_writer.write(json);
   cvd::SharedFD pipe[2];
@@ -137,7 +164,7 @@
   return pipe[0];
 }
 
-Json::Value fd_to_json(cvd::SharedFD fd) {
+Json::Value FdToJson(cvd::SharedFD fd) {
   std::string contents;
   cvd::ReadAll(fd, &contents);
   Json::Reader reader;
@@ -148,7 +175,7 @@
   return json;
 }
 
-cvd::SharedFD bpttool_make_table(const cvd::SharedFD& input) {
+cvd::SharedFD BpttoolMakeTable(const cvd::SharedFD& input) {
   auto bpttool_path = vsoc::DefaultHostArtifactsPath(BPTTOOL_FILE_PATH);
   cvd::Command bpttool_cmd(bpttool_path);
   bpttool_cmd.AddParameter("make_table");
@@ -165,7 +192,7 @@
   return output_pipe[0];
 }
 
-cvd::SharedFD bpttool_make_partition_table(cvd::SharedFD input) {
+cvd::SharedFD BpttoolMakePartitionTable(cvd::SharedFD input) {
   auto bpttool_path = vsoc::DefaultHostArtifactsPath(BPTTOOL_FILE_PATH);
   cvd::Command bpttool_cmd(bpttool_path);
   bpttool_cmd.AddParameter("make_table");
@@ -203,8 +230,8 @@
   }
 }
 
-void bpttool_make_disk_image(const std::vector<ImagePartition>& partitions,
-                             cvd::SharedFD table, const std::string& output) {
+void BptToolMakeDiskImage(const std::vector<ImagePartition>& partitions,
+                          cvd::SharedFD table, const std::string& output) {
   auto bpttool_path = vsoc::DefaultHostArtifactsPath(BPTTOOL_FILE_PATH);
   cvd::Command bpttool_cmd(bpttool_path);
   bpttool_cmd.AddParameter("make_disk_image");
@@ -221,28 +248,77 @@
   }
 }
 
+void DeAndroidSparse(const std::vector<ImagePartition>& partitions) {
+  for (const auto& partition : partitions) {
+    auto file = cvd::SharedFD::Open(partition.image_file_path.c_str(), O_RDONLY);
+    if (!file->IsOpen()) {
+      LOG(FATAL) << "Could not open \"" << partition.image_file_path
+                  << "\": " << file->StrError();
+      break;
+    }
+    int fd = file->UNMANAGED_Dup();
+    auto sparse = sparse_file_import(fd, /* verbose */ false, /* crc */ false);
+    if (!sparse) {
+      close(fd);
+      continue;
+    }
+    LOG(INFO) << "Desparsing " << partition.image_file_path;
+    std::string out_file_name = partition.image_file_path + ".desparse";
+    auto out_file = cvd::SharedFD::Open(out_file_name.c_str(), O_RDWR | O_CREAT | O_TRUNC,
+                                        S_IRUSR | S_IWUSR | S_IRGRP);
+    int write_fd = out_file->UNMANAGED_Dup();
+    int write_status = sparse_file_write(sparse, write_fd, /* gz */ false,
+                                         /* sparse */ false, /* crc */ false);
+    if (write_status < 0) {
+      LOG(FATAL) << "Failed to desparse \"" << partition.image_file_path << "\": " << write_status;
+    }
+    close(write_fd);
+    if (rename(out_file_name.c_str(), partition.image_file_path.c_str()) < 0) {
+      int error_num = errno;
+      LOG(FATAL) << "Could not move \"" << out_file_name << "\" to \""
+                 << partition.image_file_path << "\": " << strerror(error_num);
+    }
+    sparse_file_destroy(sparse);
+    close(fd);
+  }
+}
+
 } // namespace
 
-void aggregate_image(const std::vector<ImagePartition>& partitions,
-                     const std::string& output_path) {
-  auto bpttool_input_json = bpttool_input(partitions);
-  auto input_json_fd = json_to_fd(bpttool_input_json);
-  auto table_fd = bpttool_make_table(input_json_fd);
-  bpttool_make_disk_image(partitions, table_fd, output_path);
+void AggregateImage(const std::vector<ImagePartition>& partitions,
+                    const std::string& output_path) {
+  DeAndroidSparse(partitions);
+  auto bpttool_input_json = BpttoolInput(partitions);
+  auto input_json_fd = JsonToFd(bpttool_input_json);
+  auto table_fd = BpttoolMakeTable(input_json_fd);
+  BptToolMakeDiskImage(partitions, table_fd, output_path);
 };
 
-void create_composite_disk(std::vector<ImagePartition> partitions,
-                           const std::string& header_file,
-                           const std::string& footer_file,
-                           const std::string& output_path) {
-  auto bpttool_input_json = bpttool_input(partitions);
-  auto table_fd = bpttool_make_table(json_to_fd(bpttool_input_json));
-  auto table = fd_to_json(table_fd);
-  auto partition_table_fd = bpttool_make_partition_table(json_to_fd(bpttool_input_json));
+void CreateCompositeDisk(std::vector<ImagePartition> partitions,
+                         const std::string& header_file,
+                         const std::string& footer_file,
+                         const std::string& output_composite_path) {
+  auto bpttool_input_json = BpttoolInput(partitions);
+  auto table_fd = BpttoolMakeTable(JsonToFd(bpttool_input_json));
+  auto table = FdToJson(table_fd);
+  auto partition_table_fd = BpttoolMakePartitionTable(JsonToFd(bpttool_input_json));
   CreateGptFiles(partition_table_fd, header_file, footer_file);
   auto composite_proto = MakeCompositeDiskSpec(table, partitions, header_file, footer_file);
-  std::ofstream output(output_path.c_str(), std::ios::binary | std::ios::trunc);
-  output << "composite_disk\x1d";
-  composite_proto.SerializeToOstream(&output);
-  output.flush();
+  std::ofstream composite(output_composite_path.c_str(), std::ios::binary | std::ios::trunc);
+  composite << "composite_disk\x1d";
+  composite_proto.SerializeToOstream(&composite);
+  composite.flush();
+}
+
+void CreateQcowOverlay(const std::string& crosvm_path,
+                       const std::string& backing_file,
+                       const std::string& output_overlay_path) {
+  cvd::Command crosvm_qcow2_cmd(crosvm_path);
+  crosvm_qcow2_cmd.AddParameter("create_qcow2");
+  crosvm_qcow2_cmd.AddParameter("--backing_file=", backing_file);
+  crosvm_qcow2_cmd.AddParameter(output_overlay_path);
+  int success = crosvm_qcow2_cmd.Start().Wait();
+  if (success != 0) {
+    LOG(FATAL) << "Unable to run crosvm create_qcow2. Exited with status " << success;
+  }
 }
diff --git a/host/commands/assemble_cvd/image_aggregator.h b/host/commands/assemble_cvd/image_aggregator.h
index a4394e8..d27931b 100644
--- a/host/commands/assemble_cvd/image_aggregator.h
+++ b/host/commands/assemble_cvd/image_aggregator.h
@@ -24,9 +24,12 @@
   std::string image_file_path;
 };
 
-void aggregate_image(const std::vector<ImagePartition>& partitions,
-                     const std::string& output_path);
-void create_composite_disk(std::vector<ImagePartition> partitions,
-                           const std::string& header_file,
-                           const std::string& footer_file,
-                           const std::string& output_path);
+void AggregateImage(const std::vector<ImagePartition>& partitions,
+                    const std::string& output_path);
+void CreateCompositeDisk(std::vector<ImagePartition> partitions,
+                         const std::string& header_file,
+                         const std::string& footer_file,
+                         const std::string& output_composite_path);
+void CreateQcowOverlay(const std::string& crosvm_path,
+                       const std::string& backing_file,
+                       const std::string& output_overlay_path);
diff --git a/host/commands/launch/launch_cvd.cc b/host/commands/launch/launch_cvd.cc
index 9d3b108..6cc7c93 100644
--- a/host/commands/launch/launch_cvd.cc
+++ b/host/commands/launch/launch_cvd.cc
@@ -39,6 +39,7 @@
  */
 DEFINE_bool(run_file_discovery, true,
             "Whether to run file discovery or get input files from stdin.");
+DEFINE_int32(num_instances, 1, "Number of Android guests to launch");
 
 namespace {
 
@@ -95,8 +96,8 @@
 
   gflags::HandleCommandLineHelpFlags();
 
-  cvd::SharedFD assembler_stdout, runner_stdin;
-  cvd::SharedFD::Pipe(&runner_stdin, &assembler_stdout);
+  cvd::SharedFD assembler_stdout, assembler_stdout_capture;
+  cvd::SharedFD::Pipe(&assembler_stdout_capture, &assembler_stdout);
 
   cvd::SharedFD launcher_report, assembler_stdin;
   bool should_generate_report = FLAGS_run_file_discovery;
@@ -109,13 +110,18 @@
   auto assemble_proc = StartAssembler(std::move(assembler_stdin),
                                       std::move(assembler_stdout),
                                       forwarder.ArgvForSubprocess(kAssemblerBin));
-  auto run_proc = StartRunner(std::move(runner_stdin),
-                              forwarder.ArgvForSubprocess(kRunnerBin));
 
   if (should_generate_report) {
     WriteFiles(AvailableFilesReport(), std::move(launcher_report));
   }
 
+  std::string assembler_output;
+  if (cvd::ReadAll(assembler_stdout_capture, &assembler_output) < 0) {
+    int error_num = errno;
+    LOG(ERROR) << "Read error getting output from assemble_cvd: " << strerror(error_num);
+    return -1;
+  }
+
   auto assemble_ret = assemble_proc.Wait();
   if (assemble_ret != 0) {
     LOG(ERROR) << "assemble_cvd returned " << assemble_ret;
@@ -124,11 +130,32 @@
     LOG(INFO) << "assemble_cvd exited successfully.";
   }
 
-  auto run_ret = run_proc.Wait();
-  if (run_ret != 0) {
-    LOG(ERROR) << "run_cvd returned " << run_ret;
-  } else {
-    LOG(INFO) << "run_cvd exited successfully.";
+  std::vector<cvd::Subprocess> runners;
+  for (int i = 0; i < FLAGS_num_instances; i++) {
+    cvd::SharedFD runner_stdin_in, runner_stdin_out;
+    cvd::SharedFD::Pipe(&runner_stdin_out, &runner_stdin_in);
+    std::string instance_name = std::to_string(i + vsoc::GetInstance());
+    setenv("CUTTLEFISH_INSTANCE", instance_name.c_str(), /* overwrite */ 1);
+
+    auto run_proc = StartRunner(std::move(runner_stdin_out),
+                                forwarder.ArgvForSubprocess(kRunnerBin));
+    runners.push_back(std::move(run_proc));
+    if (cvd::WriteAll(runner_stdin_in, assembler_output) < 0) {
+      int error_num = errno;
+      LOG(ERROR) << "Could not write to run_cvd: " << strerror(error_num);
+      return -1;
+    }
   }
-  return run_ret;
+
+  bool run_cvd_failure = false;
+  for (auto& run_proc : runners) {
+    auto run_ret = run_proc.Wait();
+    if (run_ret != 0) {
+      run_cvd_failure = true;
+      LOG(ERROR) << "run_cvd returned " << run_ret;
+    } else {
+      LOG(INFO) << "run_cvd exited successfully.";
+    }
+  }
+  return run_cvd_failure ? -1 : 0;
 }
diff --git a/host/commands/stop_cvd/main.cc b/host/commands/stop_cvd/main.cc
index 7d425a0..65dbeb7 100644
--- a/host/commands/stop_cvd/main.cc
+++ b/host/commands/stop_cvd/main.cc
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <dirent.h>
 #include <inttypes.h>
 #include <limits.h>
 #include <stdio.h>
@@ -35,6 +36,7 @@
 #include <string>
 #include <vector>
 
+#include <android-base/strings.h>
 #include <gflags/gflags.h>
 #include <glog/logging.h>
 
@@ -50,23 +52,57 @@
              "command. A value of zero means wait indefinetly");
 
 namespace {
+
+std::set<std::string> FallbackPaths() {
+  std::set<std::string> paths;
+  std::string parent_path = cvd::StringFromEnv("HOME", ".");
+  paths.insert(parent_path + "/cuttlefish_assembly");
+  paths.insert(parent_path + "/cuttlefish_assembly/*");
+
+  std::unique_ptr<DIR, int(*)(DIR*)> dir(opendir(parent_path.c_str()), closedir);
+  for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
+    std::string subdir(entity->d_name);
+    if (!android::base::StartsWith(subdir, "cuttlefish_runtime.")) {
+      continue;
+    }
+    auto instance_dir = parent_path + "/" + subdir;
+    // Add the instance directory
+    paths.insert(instance_dir);
+    // Add files in instance dir
+    paths.insert(instance_dir + "/*");
+    // Add files in the tombstone directory
+    paths.insert(instance_dir + "/tombstones/*");
+    // Add files in the internal directory
+    paths.insert(instance_dir + "/" + std::string(vsoc::kInternalDirName) + "/*");
+  }
+  return paths;
+}
+
+std::set<std::string> PathsForInstance(const vsoc::CuttlefishConfig& config,
+                                       const vsoc::CuttlefishConfig::InstanceSpecific instance) {
+  return {
+    config.assembly_dir(),
+    config.assembly_dir() + "/*",
+    instance.instance_dir(),
+    instance.PerInstancePath("*"),
+    instance.PerInstancePath("tombstones"),
+    instance.PerInstancePath("tombstones/*"),
+    instance.instance_internal_dir(),
+    instance.PerInstanceInternalPath("*"),
+  };
+}
+
 // Gets a set of the possible process groups of a previous launch
-std::set<pid_t> GetCandidateProcessGroups() {
-  std::string cmd = "lsof -t 2>/dev/null";
-  // Add the instance directory
-  auto instance_dir = cvd::StringFromEnv("HOME", ".") + "/cuttlefish_runtime";
-  cmd += " " + instance_dir;
-  // Add files in instance dir
-  cmd += " " + instance_dir + "/*";
-  // Add files in the tombstone directory
-  cmd += " " + instance_dir + "/tombstones/*";
-  // Add files in the internal directory
-  cmd += ((" " + instance_dir + "/") + vsoc::kInternalDirName) + "/*";
-  // Add the shared memory file
-  cmd += " " + vsoc::ForCurrentInstance("/dev/shm/cvd-");
-  std::shared_ptr<FILE> cmd_out(popen(cmd.c_str(), "r"), pclose);
+std::set<pid_t> GetCandidateProcessGroups(const std::set<std::string>& paths) {
+  std::stringstream cmd;
+  cmd << "lsof -t 2>/dev/null";
+  for (const auto& path : paths) {
+    cmd << " " << path;
+  }
+  std::string cmd_str = cmd.str();
+  std::shared_ptr<FILE> cmd_out(popen(cmd_str.c_str(), "r"), pclose);
   if (!cmd_out) {
-    LOG(ERROR) << "Unable to execute '" << cmd << "': " << strerror(errno);
+    LOG(ERROR) << "Unable to execute '" << cmd_str << "': " << strerror(errno);
     return {};
   }
   int64_t pid;
@@ -85,10 +121,10 @@
   return ret;
 }
 
-int FallBackStop() {
+int FallBackStop(const std::set<std::string>& paths) {
   auto exit_code = 1; // Having to fallback is an error
 
-  auto process_groups = GetCandidateProcessGroups();
+  auto process_groups = GetCandidateProcessGroups(paths);
   for (auto pgid: process_groups) {
     LOG(INFO) << "Sending SIGKILL to process group " << pgid;
     auto retval = killpg(pgid, SIGKILL);
@@ -101,37 +137,26 @@
 
   return exit_code;
 }
-}  // anonymous namespace
 
-int main(int argc, char** argv) {
-  ::android::base::InitLogging(argv, android::base::StderrLogger);
-  google::ParseCommandLineFlags(&argc, &argv, true);
-
-  auto config = vsoc::CuttlefishConfig::Get();
-  auto instance = config->ForDefaultInstance();
-  if (!config) {
-    LOG(ERROR) << "Failed to obtain config object";
-    return FallBackStop();
-  }
-
+bool CleanStopInstance(const vsoc::CuttlefishConfig::InstanceSpecific& instance) {
   auto monitor_path = instance.launcher_monitor_socket_path();
   if (monitor_path.empty()) {
     LOG(ERROR) << "No path to launcher monitor found";
-    return FallBackStop();
+    return false;
   }
   auto monitor_socket = cvd::SharedFD::SocketLocalClient(monitor_path.c_str(),
                                                          false, SOCK_STREAM);
   if (!monitor_socket->IsOpen()) {
     LOG(ERROR) << "Unable to connect to launcher monitor at " << monitor_path
                << ": " << monitor_socket->StrError();
-    return FallBackStop();
+    return false;
   }
   auto request = cvd::LauncherAction::kStop;
   auto bytes_sent = monitor_socket->Send(&request, sizeof(request), 0);
   if (bytes_sent < 0) {
     LOG(ERROR) << "Error sending launcher monitor the stop command: "
                << monitor_socket->StrError();
-    return FallBackStop();
+    return false;
   }
   // Perform a select with a timeout to guard against launcher hanging
   cvd::SharedFDSet read_set;
@@ -142,24 +167,53 @@
   if (selected < 0){
     LOG(ERROR) << "Failed communication with the launcher monitor: "
                << strerror(errno);
-    return FallBackStop();
+    return false;
   }
   if (selected == 0) {
     LOG(ERROR) << "Timeout expired waiting for launcher monitor to respond";
-    return FallBackStop();
+    return false;
   }
   cvd::LauncherResponse response;
   auto bytes_recv = monitor_socket->Recv(&response, sizeof(response), 0);
   if (bytes_recv < 0) {
     LOG(ERROR) << "Error receiving response from launcher monitor: "
                << monitor_socket->StrError();
-    return FallBackStop();
+    return false;
   }
   if (response != cvd::LauncherResponse::kSuccess) {
     LOG(ERROR) << "Received '" << static_cast<char>(response)
                << "' response from launcher monitor";
-    return FallBackStop();
+    return false;
   }
-  LOG(INFO) << "Successfully stopped device";
+  LOG(INFO) << "Successfully stopped device " << instance.adb_ip_and_port();
+  return true;
+}
+
+int StopInstance(const vsoc::CuttlefishConfig& config,
+                 const vsoc::CuttlefishConfig::InstanceSpecific& instance) {
+  bool res = CleanStopInstance(instance);
+  if (!res) {
+    return FallBackStop(PathsForInstance(config, instance));
+  }
   return 0;
 }
+
+}  // anonymous namespace
+
+int main(int argc, char** argv) {
+  ::android::base::InitLogging(argv, android::base::StderrLogger);
+  google::ParseCommandLineFlags(&argc, &argv, true);
+
+  auto config = vsoc::CuttlefishConfig::Get();
+  if (!config) {
+    LOG(ERROR) << "Failed to obtain config object";
+    return FallBackStop(FallbackPaths());
+  }
+
+  int ret = 0;
+  for (const auto& instance : config->Instances()) {
+    ret |= StopInstance(*config, instance);
+  }
+
+  return ret;
+}
diff --git a/host/libs/config/cuttlefish_config.cpp b/host/libs/config/cuttlefish_config.cpp
index 50bbe30..c133a62 100644
--- a/host/libs/config/cuttlefish_config.cpp
+++ b/host/libs/config/cuttlefish_config.cpp
@@ -339,21 +339,21 @@
   SetPath(kVendorRamdiskImagePath, vendor_ramdisk_image_path);
 }
 
-std::vector<std::string> CuttlefishConfig::virtual_disk_paths() const {
+std::vector<std::string> CuttlefishConfig::InstanceSpecific::virtual_disk_paths() const {
   std::vector<std::string> virtual_disks;
-  auto virtual_disks_json_obj = (*dictionary_)[kVirtualDiskPaths];
+  auto virtual_disks_json_obj = (*Dictionary())[kVirtualDiskPaths];
   for (const auto& disk : virtual_disks_json_obj) {
     virtual_disks.push_back(disk.asString());
   }
   return virtual_disks;
 }
-void CuttlefishConfig::set_virtual_disk_paths(
+void CuttlefishConfig::MutableInstanceSpecific::set_virtual_disk_paths(
     const std::vector<std::string>& virtual_disk_paths) {
   Json::Value virtual_disks_json_obj(Json::arrayValue);
   for (const auto& arg : virtual_disk_paths) {
     virtual_disks_json_obj.append(arg);
   }
-  (*dictionary_)[kVirtualDiskPaths] = virtual_disks_json_obj;
+  (*Dictionary())[kVirtualDiskPaths] = virtual_disks_json_obj;
 }
 
 std::string CuttlefishConfig::InstanceSpecific::kernel_log_pipe_name() const {
@@ -844,7 +844,10 @@
 CuttlefishConfig::CuttlefishConfig() : dictionary_(new Json::Value()) {}
 // Can't use '= default' on the header because the compiler complains of
 // Json::Value being an incomplete type
-CuttlefishConfig::~CuttlefishConfig() {}
+CuttlefishConfig::~CuttlefishConfig() = default;
+
+CuttlefishConfig::CuttlefishConfig(CuttlefishConfig&&) = default;
+CuttlefishConfig& CuttlefishConfig::operator=(CuttlefishConfig&&) = default;
 
 bool CuttlefishConfig::LoadFromFile(const char* file) {
   auto real_file_path = cvd::AbsolutePath(file);
@@ -895,14 +898,27 @@
   return ForCurrentInstance("cvd-");
 }
 
-CuttlefishConfig::MutableInstanceSpecific CuttlefishConfig::ForDefaultInstance() {
-  return MutableInstanceSpecific(this, std::to_string(GetInstance()));
+CuttlefishConfig::MutableInstanceSpecific CuttlefishConfig::ForInstance(int num) {
+  return MutableInstanceSpecific(this, std::to_string(num));
+}
+
+CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForInstance(int num) const {
+  return InstanceSpecific(this, std::to_string(num));
 }
 
 CuttlefishConfig::InstanceSpecific CuttlefishConfig::ForDefaultInstance() const {
   return InstanceSpecific(this, std::to_string(GetInstance()));
 }
 
+std::vector<CuttlefishConfig::InstanceSpecific> CuttlefishConfig::Instances() const {
+  const auto& json = (*dictionary_)[kInstances];
+  std::vector<CuttlefishConfig::InstanceSpecific> instances;
+  for (const auto& name : json.getMemberNames()) {
+    instances.push_back(CuttlefishConfig::InstanceSpecific(this, name));
+  }
+  return instances;
+}
+
 int GetInstance() {
   static int instance_id = InstanceFromEnvironment();
   return instance_id;
diff --git a/host/libs/config/cuttlefish_config.h b/host/libs/config/cuttlefish_config.h
index 04adc29..f78327d 100644
--- a/host/libs/config/cuttlefish_config.h
+++ b/host/libs/config/cuttlefish_config.h
@@ -18,6 +18,7 @@
 #include <memory>
 #include <string>
 #include <set>
+#include <vector>
 
 namespace Json {
 class Value;
@@ -51,7 +52,9 @@
   static const CuttlefishConfig* Get();
 
   CuttlefishConfig();
+  CuttlefishConfig(CuttlefishConfig&&);
   ~CuttlefishConfig();
+  CuttlefishConfig& operator=(CuttlefishConfig&&);
 
   // Saves the configuration object in a file, it can then be read in other
   // processes by passing the --config_file option.
@@ -135,9 +138,6 @@
   void set_vendor_ramdisk_image_path(const std::string&
     vendor_ramdisk_image_path);
 
-  std::vector<std::string> virtual_disk_paths() const;
-  void set_virtual_disk_paths(const std::vector<std::string>& disk_paths);
-
   bool deprecated_boot_completed() const;
   void set_deprecated_boot_completed(bool deprecated_boot_completed);
 
@@ -259,21 +259,22 @@
   class InstanceSpecific;
   class MutableInstanceSpecific;
 
-  MutableInstanceSpecific ForDefaultInstance();
+  MutableInstanceSpecific ForInstance(int instance_num);
+  InstanceSpecific ForInstance(int instance_num) const;
   InstanceSpecific ForDefaultInstance() const;
 
+  std::vector<InstanceSpecific> Instances() const;
+
   // A view into an existing CuttlefishConfig object for a particular instance.
   class InstanceSpecific {
     const CuttlefishConfig* config_;
     std::string id_;
+    friend InstanceSpecific CuttlefishConfig::ForInstance(int num) const;
     friend InstanceSpecific CuttlefishConfig::ForDefaultInstance() const;
+    friend std::vector<InstanceSpecific> CuttlefishConfig::Instances() const;
 
     InstanceSpecific(const CuttlefishConfig* config, const std::string& id)
         : config_(config), id_(id) {}
-    InstanceSpecific(const InstanceSpecific&) = delete;
-    InstanceSpecific(InstanceSpecific&&) = delete;
-    InstanceSpecific& operator=(const InstanceSpecific&) = delete;
-    InstanceSpecific& operator=(InstanceSpecific&&) = delete;
 
     Json::Value* Dictionary();
     const Json::Value* Dictionary() const;
@@ -290,6 +291,7 @@
     int vsock_guest_cid() const;
     std::string uuid() const;
     std::string instance_name() const;
+    std::vector<std::string> virtual_disk_paths() const;
 
     // Returns the path to a file with the given name in the instance directory..
     std::string PerInstancePath(const char* file_name) const;
@@ -322,14 +324,10 @@
   class MutableInstanceSpecific {
     CuttlefishConfig* config_;
     std::string id_;
-    friend MutableInstanceSpecific CuttlefishConfig::ForDefaultInstance();
+    friend MutableInstanceSpecific CuttlefishConfig::ForInstance(int num);
 
     MutableInstanceSpecific(CuttlefishConfig* config, const std::string& id)
         : config_(config), id_(id) {}
-    MutableInstanceSpecific(const MutableInstanceSpecific&) = delete;
-    MutableInstanceSpecific(MutableInstanceSpecific&&) = delete;
-    MutableInstanceSpecific& operator=(const MutableInstanceSpecific&) = delete;
-    MutableInstanceSpecific& operator=(MutableInstanceSpecific&&) = delete;
 
     Json::Value* Dictionary();
   public:
@@ -344,14 +342,12 @@
     void set_vsock_guest_cid(int vsock_guest_cid);
     void set_uuid(const std::string& uuid);
     void set_instance_dir(const std::string& instance_dir);
+    void set_virtual_disk_paths(const std::vector<std::string>& disk_paths);
   };
 
  private:
   std::unique_ptr<Json::Value> dictionary_;
 
-  InstanceSpecific ForInstance(int instance_num);
-  const InstanceSpecific ForInstance(int instance_num) const;
-
   void SetPath(const std::string& key, const std::string& path);
   bool LoadFromFile(const char* file);
   static CuttlefishConfig* BuildConfigImpl();
diff --git a/host/libs/vm_manager/cf_qemu.sh b/host/libs/vm_manager/cf_qemu.sh
index 5d88b3f..3eea194 100755
--- a/host/libs/vm_manager/cf_qemu.sh
+++ b/host/libs/vm_manager/cf_qemu.sh
@@ -105,7 +105,7 @@
     bootindex=""
   fi
   args+=(
-    -drive "file=${virtual_disk},format=raw,if=none,id=drive-virtio-disk${virtual_disk_index},aio=threads"
+    -drive "file=${virtual_disk},format=qcow2,if=none,id=drive-virtio-disk${virtual_disk_index},aio=threads"
     -device "virtio-blk-pci,scsi=off,drive=drive-virtio-disk${virtual_disk_index},id=virtio-disk${virtual_disk_index}${bootindex}"
   )
   virtual_disk_index=$((virtual_disk_index + 1))
diff --git a/host/libs/vm_manager/crosvm_manager.cpp b/host/libs/vm_manager/crosvm_manager.cpp
index 7cc1f14..63dd3ed 100644
--- a/host/libs/vm_manager/crosvm_manager.cpp
+++ b/host/libs/vm_manager/crosvm_manager.cpp
@@ -135,7 +135,7 @@
   crosvm_cmd.AddParameter("--mem=", config_->memory_mb());
   crosvm_cmd.AddParameter("--cpus=", config_->cpus());
   crosvm_cmd.AddParameter("--params=", kernel_cmdline_);
-  for (const auto& disk : config_->virtual_disk_paths()) {
+  for (const auto& disk : instance.virtual_disk_paths()) {
     crosvm_cmd.AddParameter("--rwdisk=", disk);
   }
   crosvm_cmd.AddParameter("--socket=", GetControlSocketPath(config_));
diff --git a/host/libs/vm_manager/qemu_manager.cpp b/host/libs/vm_manager/qemu_manager.cpp
index 643ffc5..af15f0d 100644
--- a/host/libs/vm_manager/qemu_manager.cpp
+++ b/host/libs/vm_manager/qemu_manager.cpp
@@ -140,7 +140,7 @@
   LogAndSetEnv("gdb_flag", config_->gdb_flag());
   LogAndSetEnv("ramdisk_image_path", config_->final_ramdisk_path());
   LogAndSetEnv("kernel_cmdline", kernel_cmdline_);
-  LogAndSetEnv("virtual_disk_paths", JoinString(config_->virtual_disk_paths(),
+  LogAndSetEnv("virtual_disk_paths", JoinString(instance.virtual_disk_paths(),
                                                 ";"));
   LogAndSetEnv("wifi_tap_name", instance.wifi_tap_name());
   LogAndSetEnv("mobile_tap_name", instance.mobile_tap_name());
diff --git a/shared/BoardConfig.mk b/shared/BoardConfig.mk
index 1bc3718..f2a8bea 100644
--- a/shared/BoardConfig.mk
+++ b/shared/BoardConfig.mk
@@ -52,7 +52,7 @@
 
 BOARD_USES_GENERIC_AUDIO := false
 USE_CAMERA_STUB := true
-TARGET_USERIMAGES_SPARSE_EXT_DISABLED := true
+TARGET_USERIMAGES_SPARSE_EXT_DISABLED := false
 
 # Hardware composer configuration
 TARGET_USES_HWC2 := true
@@ -62,7 +62,7 @@
 
 # Make the userdata partition 4.25G to accomodate ASAN and CTS
 BOARD_USERDATAIMAGE_PARTITION_SIZE := 4563402752
-TARGET_USERIMAGES_SPARSE_F2FS_DISABLED := true
+TARGET_USERIMAGES_SPARSE_F2FS_DISABLED := false
 BOARD_USERDATAIMAGE_FILE_SYSTEM_TYPE := f2fs
 TARGET_USERIMAGES_USE_F2FS := true