Use the composite disk support in crosvm.

Depends on this:
https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/1667767

Performance characteristics:
Composite disk assembly on disk: 60 seconds
Composite disk assembly on tmpfs: 20 seconds
Composite disk support in crosvm: 0.3 seconds

bpttool creates "holes" between the partition table and the first disk
and between the last disk and the backup partition table. Android seems
to use these so this change creates separate temporary files for them.

Test: launch_cvd -composite_disk=composite.img
Bug: 135293952
Change-Id: I7b22839b7ea6b1bd85ea89cbc0f82a4838c822ef
diff --git a/host/commands/launch/Android.bp b/host/commands/launch/Android.bp
index bdac2db..9c8826a 100644
--- a/host/commands/launch/Android.bp
+++ b/host/commands/launch/Android.bp
@@ -13,6 +13,21 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+cc_library_host_shared {
+    name: "cdisk_spec",
+    srcs: [
+        "cdisk_spec.proto",
+    ],
+    proto: {
+        type: "full",
+        export_proto_headers: true,
+        include_dirs: [
+            "external/protobuf/src",
+        ],
+    },
+    defaults: ["cuttlefish_host_only"],
+}
+
 cc_binary_host {
     name: "launch_cvd",
     srcs: [
@@ -30,13 +45,15 @@
         "cuttlefish_glog",
     ],
     shared_libs: [
+        "cdisk_spec",
         "vsoc_lib",
         "libcuttlefish_fs",
         "libcuttlefish_strings",
         "libcuttlefish_utils",
         "cuttlefish_auto_resources",
         "libbase",
-        "libnl"
+        "libnl",
+        "libprotobuf-cpp-full",
     ],
     static_libs: [
         "libcuttlefish_host_config",
diff --git a/host/commands/launch/cdisk_spec.proto b/host/commands/launch/cdisk_spec.proto
new file mode 100644
index 0000000..771c7ce
--- /dev/null
+++ b/host/commands/launch/cdisk_spec.proto
@@ -0,0 +1,18 @@
+syntax = "proto3";
+
+enum ReadWriteCapability {
+  READ_ONLY = 0;
+  READ_WRITE = 1;
+}
+
+message ComponentDisk {
+  string file_path = 1;
+  uint64 offset = 2;
+  ReadWriteCapability read_write_capability = 3;
+}
+
+message CompositeDisk {
+  uint64 version = 1;
+  repeated ComponentDisk component_disks = 2;
+  uint64 length = 3;
+};
diff --git a/host/commands/launch/flags.cc b/host/commands/launch/flags.cc
index a966215..be600db 100644
--- a/host/commands/launch/flags.cc
+++ b/host/commands/launch/flags.cc
@@ -661,6 +661,12 @@
   if (FLAGS_composite_disk.empty()) {
     return false;
   }
+  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);
   for (auto& partition : disk_config()) {
     auto partition_age = cvd::FileModificationTime(partition.image_file_path);
@@ -688,11 +694,17 @@
   return true;
 }
 
-void CreateCompositeDisk() {
+void CreateCompositeDisk(const vsoc::CuttlefishConfig& config) {
   if (FLAGS_composite_disk.empty()) {
     LOG(FATAL) << "asked to create composite disk, but path was empty";
   }
-  aggregate_image(disk_config(), FLAGS_composite_disk);
+  if (FLAGS_vm_manager == vm_manager::CrosvmManager::name()) {
+    std::string header_path = config.PerInstancePath("gpt_header.img");
+    std::string footer_path = config.PerInstancePath("gpt_footer.img");
+    create_composite_disk(disk_config(), header_path, footer_path, FLAGS_composite_disk);
+  } else {
+    aggregate_image(disk_config(), FLAGS_composite_disk);
+  }
 }
 
 } // namespace
@@ -779,7 +791,7 @@
   }
 
   if (ShouldCreateCompositeDisk()) {
-    CreateCompositeDisk();
+    CreateCompositeDisk(*config);
   }
 
   // Check that the files exist
diff --git a/host/commands/launch/image_aggregator.cc b/host/commands/launch/image_aggregator.cc
index 2ff0b84..661ca44 100644
--- a/host/commands/launch/image_aggregator.cc
+++ b/host/commands/launch/image_aggregator.cc
@@ -16,19 +16,26 @@
 
 #include "host/commands/launch/image_aggregator.h"
 
+#include <fstream>
 #include <string>
 #include <vector>
 
 #include <glog/logging.h>
 #include <json/json.h>
+#include <google/protobuf/text_format.h>
 
+#include "common/libs/fs/shared_buf.h"
 #include "common/libs/fs/shared_fd.h"
 #include "common/libs/utils/files.h"
 #include "common/libs/utils/subprocess.h"
 #include "host/libs/config/cuttlefish_config.h"
+#include "device/google/cuttlefish_common/host/commands/launch/cdisk_spec.pb.h"
 
 namespace {
 
+const int GPT_HEADER_SIZE = 512 * 34;
+const int GPT_FOOTER_SIZE = 512 * 33;
+
 const std::string BPTTOOL_FILE_PATH = "bin/cf_bpttool";
 
 Json::Value bpttool_input(const std::vector<ImagePartition>& partitions) {
@@ -58,6 +65,64 @@
   return bpttool_input_json;
 }
 
+std::string create_file(size_t len) {
+  char file_template[] = "/tmp/diskXXXXXX";
+  int fd = mkstemp(file_template);
+  if (fd < 0) {
+    LOG(FATAL) << "not able to create disk hole temp file";
+  }
+  char data[4096];
+  for (size_t i = 0; i < sizeof(data); i++) {
+    data[i] = '\0';
+  }
+  for (size_t i = 0; i < len + 2 * sizeof(data); i+= sizeof(data)) {
+    if (write(fd, data, sizeof(data)) < (ssize_t) sizeof(data)) {
+      LOG(FATAL) << "not able to write to disk hole temp file";
+    }
+  }
+  close(fd);
+  return std::string(file_template);
+}
+
+CompositeDisk MakeCompositeDiskSpec(const Json::Value& bpt_file,
+                                    const std::vector<ImagePartition>& partitions,
+                                    const std::string& header_file,
+                                    const std::string& footer_file) {
+  CompositeDisk disk;
+  disk.set_version(1);
+  ComponentDisk* header = disk.add_component_disks();
+  header->set_file_path(header_file);
+  header->set_offset(0);
+  size_t previous_end = GPT_HEADER_SIZE;
+  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_offset(previous_end);
+    }
+    ComponentDisk* component = disk.add_component_disks();
+    for (auto& partition : partitions) {
+      if (bpt_partition["label"] == partition.label) {
+        component->set_file_path(partition.image_file_path);
+      }
+    }
+    component->set_offset(bpt_partition["offset"].asUInt64());
+    component->set_read_write_capability(ReadWriteCapability::READ_WRITE);
+    previous_end = bpt_partition["offset"].asUInt64() + bpt_partition["size"].asUInt64();
+  }
+  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_offset(previous_end);
+  }
+  ComponentDisk* footer = disk.add_component_disks();
+  footer->set_file_path(footer_file);
+  footer->set_offset(bpt_file["settings"]["disk_size"].asUInt64() - GPT_FOOTER_SIZE);
+  disk.set_length(bpt_file["settings"]["disk_size"].asUInt64());
+  return disk;
+}
+
 cvd::SharedFD json_to_fd(const Json::Value& json) {
   Json::FastWriter json_writer;
   std::string json_string = json_writer.write(json);
@@ -72,6 +137,17 @@
   return pipe[0];
 }
 
+Json::Value fd_to_json(cvd::SharedFD fd) {
+  std::string contents;
+  cvd::ReadAll(fd, &contents);
+  Json::Reader reader;
+  Json::Value json;
+  if (!reader.parse(contents, json)) {
+    LOG(FATAL) << "Could not parse json: " << reader.getFormattedErrorMessages();
+  }
+  return json;
+}
+
 cvd::SharedFD bpttool_make_table(const cvd::SharedFD& input) {
   auto bpttool_path = vsoc::DefaultHostArtifactsPath(BPTTOOL_FILE_PATH);
   cvd::Command bpttool_cmd(bpttool_path);
@@ -89,6 +165,44 @@
   return output_pipe[0];
 }
 
+cvd::SharedFD bpttool_make_partition_table(cvd::SharedFD input) {
+  auto bpttool_path = vsoc::DefaultHostArtifactsPath(BPTTOOL_FILE_PATH);
+  cvd::Command bpttool_cmd(bpttool_path);
+  bpttool_cmd.AddParameter("make_table");
+  bpttool_cmd.AddParameter("--input=/dev/stdin");
+  bpttool_cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdIn, input);
+  bpttool_cmd.AddParameter("--output_gpt=/dev/stdout");
+  cvd::SharedFD output_pipe[2];
+  cvd::SharedFD::Pipe(&output_pipe[0], &output_pipe[1]);
+  bpttool_cmd.RedirectStdIO(cvd::Subprocess::StdIOChannel::kStdOut, output_pipe[1]);
+  int success = bpttool_cmd.Start().Wait();
+  if (success != 0) {
+    LOG(FATAL) << "Unable to run bpttool. Exited with status " << success;
+  }
+  return output_pipe[0];
+}
+
+void CreateGptFiles(cvd::SharedFD gpt, const std::string& header_file,
+                    const std::string& footer_file) {
+  std::string content;
+  content.resize(GPT_HEADER_SIZE);
+  if (cvd::ReadExact(gpt, &content) < GPT_HEADER_SIZE) {
+    LOG(FATAL) << "Unable to run read full gpt. Errno is " << gpt->GetErrno();
+  }
+  auto header_fd = cvd::SharedFD::Open(header_file.c_str(), O_CREAT | O_RDWR, 0755);
+  if (cvd::WriteAll(header_fd, content) < GPT_HEADER_SIZE) {
+    LOG(FATAL) << "Unable to run write full gpt. Errno is " << gpt->GetErrno();
+  }
+  content.resize(GPT_FOOTER_SIZE);
+  if (cvd::ReadExact(gpt, &content) < GPT_FOOTER_SIZE) {
+    LOG(FATAL) << "Unable to run read full gpt. Errno is " << gpt->GetErrno();
+  }
+  auto footer_fd = cvd::SharedFD::Open(footer_file.c_str(), O_CREAT | O_RDWR, 0755);
+  if (cvd::WriteAll(footer_fd, content) < GPT_FOOTER_SIZE) {
+    LOG(FATAL) << "Unable to run write full gpt. Errno is " << gpt->GetErrno();
+  }
+}
+
 void bpttool_make_disk_image(const std::vector<ImagePartition>& partitions,
                              cvd::SharedFD table, const std::string& output) {
   auto bpttool_path = vsoc::DefaultHostArtifactsPath(BPTTOOL_FILE_PATH);
@@ -116,3 +230,19 @@
   auto table_fd = bpttool_make_table(input_json_fd);
   bpttool_make_disk_image(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));
+  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();
+}
diff --git a/host/commands/launch/image_aggregator.h b/host/commands/launch/image_aggregator.h
index 2534323..a4394e8 100644
--- a/host/commands/launch/image_aggregator.h
+++ b/host/commands/launch/image_aggregator.h
@@ -26,3 +26,7 @@
 
 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);