fastbootd: Support flashall with logical partitions.

Currently, fastboot flashall will try to flash every partition in the
image list. For devices with logical partitions, this will cause those
partitions to be flashed twice (once via super.img, and again for each
individual image). Additionally, super.img is needed to synchronize the
logical partition list, but it is only generated with "make dist",
making it inconvenient for most workflows.

This patch removes automatic flashing of super.img. Instead, devices
with a super partition must now generate a "super_empty.img"
describing the super geometry and logical partition layout. Before
flashing any other partitions, this image is used to ensure the device
has created all necessary logical partitions.

Additionally, before flashing (but after updating super), all logical
partitions that will be flashed are resized to 0. This ensures that
logical partitions will receive more optimal space allocation.

Finally, during flashing of individual images, logical partitions are
resized to the image size, since they do not have hardcoded sizes in the
build.

Bug: 78793464
Test: fastboot flashall can flash vendor, product, product_services
      without a make dist/super.img.

Change-Id: I18c2291e0d0cb10de729c5646b75ac84dde8c871
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index dc94952..8623666 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -104,9 +104,10 @@
     void* data;
     int64_t sz;
     int fd;
+    int64_t image_size;
 };
 
-static struct {
+struct Image {
     const char* nickname;
     const char* img_name;
     const char* sig_name;
@@ -114,7 +115,9 @@
     bool optional_if_no_image;
     bool optional_if_no_partition;
     bool IsSecondary() const { return nickname == nullptr; }
-} images[] = {
+};
+
+static Image images[] = {
         // clang-format off
     { "boot",     "boot.img",         "boot.sig",     "boot",     false, false },
     { nullptr,    "boot_other.img",   "boot.sig",     "boot",     true,  false },
@@ -128,7 +131,6 @@
                                                       "product_services",
                                                                   true,  true  },
     { "recovery", "recovery.img",     "recovery.sig", "recovery", true,  false },
-    { "super",    "super.img",        "super.sig",    "super",    true,  true  },
     { "system",   "system.img",       "system.sig",   "system",   false, true  },
     { nullptr,    "system_other.img", "system.sig",   "system",   true,  false },
     { "vbmeta",   "vbmeta.img",       "vbmeta.sig",   "vbmeta",   true,  false },
@@ -773,6 +775,13 @@
         return false;
     }
 
+    if (sparse_file* s = sparse_file_import_auto(fd, false, false)) {
+        buf->image_size = sparse_file_len(s, false, false);
+        sparse_file_destroy(s);
+    } else {
+        buf->image_size = sz;
+    }
+
     lseek64(fd, 0, SEEK_SET);
     int64_t limit = get_sparse_limit(sz);
     if (limit) {
@@ -1044,6 +1053,11 @@
     }
 }
 
+static bool is_userspace_fastboot() {
+    std::string value;
+    return fb_getvar("is-userspace", &value) && value == "yes";
+}
+
 static bool if_partition_exists(const std::string& partition, const std::string& slot) {
     std::string has_slot;
     std::string partition_name = partition;
@@ -1158,7 +1172,42 @@
     fb_queue_command("signature", "installing signature");
 }
 
-static void do_flashall(const std::string& slot_override, bool skip_secondary) {
+static bool is_logical(const std::string& partition) {
+    std::string value;
+    return fb_getvar("is-logical:" + partition, &value) && value == "yes";
+}
+
+static void update_super_partition(bool force_wipe) {
+    if (!if_partition_exists("super", "")) {
+        return;
+    }
+    std::string image = find_item_given_name("super_empty.img");
+    if (access(image.c_str(), R_OK) < 0) {
+        return;
+    }
+
+    if (!is_userspace_fastboot()) {
+        die("Must have userspace fastboot to flash logical partitions");
+    }
+
+    int fd = open(image.c_str(), O_RDONLY);
+    if (fd < 0) {
+        die("could not open '%s': %s", image.c_str(), strerror(errno));
+    }
+    fb_queue_download_fd("super", fd, get_file_size(fd));
+
+    std::string command = "update-super:super";
+    if (force_wipe) {
+        command += ":wipe";
+    }
+    fb_queue_command(command, "Updating super partition");
+
+    // We need these commands to have finished before proceeding, since
+    // otherwise "getvar is-logical" may not return a correct answer below.
+    fb_execute_queue();
+}
+
+static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) {
     std::string fname;
     queue_info_dump();
 
@@ -1188,6 +1237,10 @@
         }
     }
 
+    update_super_partition(wipe);
+
+    // List of partitions to flash and their slots.
+    std::vector<std::pair<const Image*, std::string>> entries;
     for (size_t i = 0; i < arraysize(images); i++) {
         const char* slot = NULL;
         if (images[i].IsSecondary()) {
@@ -1196,21 +1249,38 @@
             slot = slot_override.c_str();
         }
         if (!slot) continue;
-        fname = find_item_given_name(images[i].img_name);
+        entries.emplace_back(&images[i], slot);
+
+        // Resize any logical partition to 0, so each partition is reset to 0
+        // extents, and will achieve more optimal allocation.
+        auto resize_partition = [](const std::string& partition) -> void {
+            if (is_logical(partition)) {
+                fb_queue_resize_partition(partition, "0");
+            }
+        };
+        do_for_partitions(images[i].part_name, slot, resize_partition, false);
+    }
+
+    // Flash each partition in the list if it has a corresponding image.
+    for (const auto& [image, slot] : entries) {
+        fname = find_item_given_name(image->img_name);
         fastboot_buffer buf;
         if (!load_buf(fname.c_str(), &buf)) {
-            if (images[i].optional_if_no_image) continue;
-            die("could not load '%s': %s", images[i].img_name, strerror(errno));
+            if (image->optional_if_no_image) continue;
+            die("could not load '%s': %s", image->img_name, strerror(errno));
         }
-        if (images[i].optional_if_no_partition &&
-            !if_partition_exists(images[i].part_name, slot)) {
+        if (image->optional_if_no_partition &&
+            !if_partition_exists(image->part_name, slot)) {
             continue;
         }
         auto flashall = [&](const std::string &partition) {
             do_send_signature(fname.c_str());
+            if (is_logical(partition)) {
+                fb_queue_resize_partition(partition, std::to_string(buf.image_size));
+            }
             flash_buf(partition.c_str(), &buf);
         };
-        do_for_partitions(images[i].part_name, slot, flashall, false);
+        do_for_partitions(image->part_name, slot, flashall, false);
     }
 
     if (slot_override == "all") {
@@ -1648,9 +1718,9 @@
         } else if (command == "flashall") {
             if (slot_override == "all") {
                 fprintf(stderr, "Warning: slot set to 'all'. Secondary slots will not be flashed.\n");
-                do_flashall(slot_override, true);
+                do_flashall(slot_override, true, wants_wipe);
             } else {
-                do_flashall(slot_override, skip_secondary);
+                do_flashall(slot_override, skip_secondary, wants_wipe);
             }
             wants_reboot = true;
         } else if (command == "update") {