Merge "fastboot: Avoid reboots to userspace when using flashall/update."
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index a801900..9512e7e 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -286,6 +286,7 @@
         "fastboot.cpp",
         "fs.cpp",
         "socket.cpp",
+        "super_flash_helper.cpp",
         "tcp.cpp",
         "udp.cpp",
         "util.cpp",
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 7a5a782..8f7cced 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -63,6 +63,7 @@
 #include <build/version.h>
 #include <libavb/libavb.h>
 #include <liblp/liblp.h>
+#include <liblp/super_layout_builder.h>
 #include <platform_tools_version.h>
 #include <sparse/sparse.h>
 #include <ziparchive/zip_archive.h>
@@ -72,6 +73,7 @@
 #include "diagnose_usb.h"
 #include "fastboot_driver.h"
 #include "fs.h"
+#include "super_flash_helper.h"
 #include "tcp.h"
 #include "transport.h"
 #include "udp.h"
@@ -1405,20 +1407,30 @@
 
   private:
     void CheckRequirements();
-    void DetermineSecondarySlot();
+    void DetermineSlot();
     void CollectImages();
     void FlashImages(const std::vector<std::pair<const Image*, std::string>>& images);
     void FlashImage(const Image& image, const std::string& slot, fastboot_buffer* buf);
     void UpdateSuperPartition();
+    bool OptimizedFlashSuper();
+
+    // If the image uses the default slot, or the user specified "all", then
+    // the paired string will be empty. If the image requests a specific slot
+    // (for example, system_other) it is specified instead.
+    using ImageEntry = std::pair<const Image*, std::string>;
+
+    std::string GetPartitionName(const ImageEntry& entry);
 
     const ImageSource& source_;
     std::string slot_override_;
     bool skip_secondary_;
     bool wipe_;
     bool force_flash_;
+    std::string current_slot_;
     std::string secondary_slot_;
-    std::vector<std::pair<const Image*, std::string>> boot_images_;
-    std::vector<std::pair<const Image*, std::string>> os_images_;
+
+    std::vector<ImageEntry> boot_images_;
+    std::vector<ImageEntry> os_images_;
 };
 
 FlashAllTool::FlashAllTool(const ImageSource& source, const std::string& slot_override,
@@ -1441,7 +1453,7 @@
         set_active(slot_override_);
     }
 
-    DetermineSecondarySlot();
+    DetermineSlot();
     CollectImages();
 
     CancelSnapshotIfNeeded();
@@ -1450,24 +1462,92 @@
     // or in bootloader fastboot.
     FlashImages(boot_images_);
 
-    // Sync the super partition. This will reboot to userspace fastboot if needed.
-    UpdateSuperPartition();
+    if (!OptimizedFlashSuper()) {
+        // Sync the super partition. This will reboot to userspace fastboot if needed.
+        UpdateSuperPartition();
 
-    // Resize any logical partition to 0, so each partition is reset to 0
-    // extents, and will achieve more optimal allocation.
-    for (const auto& [image, slot] : os_images_) {
-        auto resize_partition = [](const std::string& partition) -> void {
-            if (is_logical(partition)) {
-                fb->ResizePartition(partition, "0");
-            }
-        };
-        do_for_partitions(image->part_name, slot, resize_partition, false);
+        // Resize any logical partition to 0, so each partition is reset to 0
+        // extents, and will achieve more optimal allocation.
+        for (const auto& [image, slot] : os_images_) {
+            auto resize_partition = [](const std::string& partition) -> void {
+                if (is_logical(partition)) {
+                    fb->ResizePartition(partition, "0");
+                }
+            };
+            do_for_partitions(image->part_name, slot, resize_partition, false);
+        }
     }
 
     // Flash OS images, resizing logical partitions as needed.
     FlashImages(os_images_);
 }
 
+bool FlashAllTool::OptimizedFlashSuper() {
+    if (!supports_AB()) {
+        LOG(VERBOSE) << "Cannot optimize flashing super on non-AB device";
+        return false;
+    }
+    if (slot_override_ == "all") {
+        LOG(VERBOSE) << "Cannot optimize flashing super for all slots";
+        return false;
+    }
+
+    // Does this device use dynamic partitions at all?
+    unique_fd fd = source_.OpenFile("super_empty.img");
+    if (fd < 0) {
+        LOG(VERBOSE) << "could not open super_empty.img";
+        return false;
+    }
+
+    // Try to find whether there is a super partition.
+    std::string super_name;
+    if (fb->GetVar("super-partition-name", &super_name) != fastboot::SUCCESS) {
+        super_name = "super";
+    }
+    std::string partition_size_str;
+    if (fb->GetVar("partition-size:" + super_name, &partition_size_str) != fastboot::SUCCESS) {
+        LOG(VERBOSE) << "Cannot optimize super flashing: could not determine super partition";
+        return false;
+    }
+
+    SuperFlashHelper helper(source_);
+    if (!helper.Open(fd)) {
+        return false;
+    }
+
+    for (const auto& entry : os_images_) {
+        auto partition = GetPartitionName(entry);
+        auto image = entry.first;
+
+        if (!helper.AddPartition(partition, image->img_name, image->optional_if_no_image)) {
+            return false;
+        }
+    }
+
+    auto s = helper.GetSparseLayout();
+    if (!s) {
+        return false;
+    }
+
+    std::vector<SparsePtr> files;
+    if (int limit = get_sparse_limit(sparse_file_len(s.get(), false, false))) {
+        files = resparse_file(s.get(), limit);
+    } else {
+        files.emplace_back(std::move(s));
+    }
+
+    // Send the data to the device.
+    flash_partition_files(super_name, files);
+
+    // Remove images that we already flashed, just in case we have non-dynamic OS images.
+    auto remove_if_callback = [&, this](const ImageEntry& entry) -> bool {
+        return helper.WillFlash(GetPartitionName(entry));
+    };
+    os_images_.erase(std::remove_if(os_images_.begin(), os_images_.end(), remove_if_callback),
+                     os_images_.end());
+    return true;
+}
+
 void FlashAllTool::CheckRequirements() {
     std::vector<char> contents;
     if (!source_.ReadFile("android-info.txt", &contents)) {
@@ -1476,7 +1556,13 @@
     ::CheckRequirements({contents.data(), contents.size()}, force_flash_);
 }
 
-void FlashAllTool::DetermineSecondarySlot() {
+void FlashAllTool::DetermineSlot() {
+    if (slot_override_.empty()) {
+        current_slot_ = get_current_slot();
+    } else {
+        current_slot_ = slot_override_;
+    }
+
     if (skip_secondary_) {
         return;
     }
@@ -1575,6 +1661,20 @@
     }
 }
 
+std::string FlashAllTool::GetPartitionName(const ImageEntry& entry) {
+    auto slot = entry.second;
+    if (slot.empty()) {
+        slot = current_slot_;
+    }
+    if (slot.empty()) {
+        return entry.first->part_name;
+    }
+    if (slot == "all") {
+        LOG(FATAL) << "Cannot retrieve a singular name when using all slots";
+    }
+    return entry.first->part_name + "_" + slot;
+}
+
 class ZipImageSource final : public ImageSource {
   public:
     explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {}
diff --git a/fastboot/super_flash_helper.cpp b/fastboot/super_flash_helper.cpp
new file mode 100644
index 0000000..b617ce8
--- /dev/null
+++ b/fastboot/super_flash_helper.cpp
@@ -0,0 +1,125 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "super_flash_helper.h"
+
+#include <android-base/logging.h>
+
+#include "util.h"
+
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+using android::fs_mgr::SuperImageExtent;
+
+SuperFlashHelper::SuperFlashHelper(const ImageSource& source) : source_(source) {}
+
+bool SuperFlashHelper::Open(borrowed_fd fd) {
+    if (!builder_.Open(fd)) {
+        LOG(VERBOSE) << "device does not support optimized super flashing";
+        return false;
+    }
+
+    base_metadata_ = builder_.Export();
+    return !!base_metadata_;
+}
+
+bool SuperFlashHelper::IncludeInSuper(const std::string& partition) {
+    return should_flash_in_userspace(*base_metadata_.get(), partition);
+}
+
+bool SuperFlashHelper::AddPartition(const std::string& partition, const std::string& image_name,
+                                    bool optional) {
+    if (!IncludeInSuper(partition)) {
+        return true;
+    }
+    auto iter = image_fds_.find(image_name);
+    if (iter == image_fds_.end()) {
+        unique_fd fd = source_.OpenFile(image_name);
+        if (fd < 0) {
+            if (!optional) {
+                LOG(VERBOSE) << "could not find partition image: " << image_name;
+                return false;
+            }
+            return true;
+        }
+        if (is_sparse_file(fd)) {
+            LOG(VERBOSE) << "cannot optimize dynamic partitions with sparse images";
+            return false;
+        }
+        iter = image_fds_.emplace(image_name, std::move(fd)).first;
+    }
+
+    if (!builder_.AddPartition(partition, image_name, get_file_size(iter->second))) {
+        return false;
+    }
+
+    will_flash_.emplace(partition);
+    return true;
+}
+
+SparsePtr SuperFlashHelper::GetSparseLayout() {
+    // Cache extents since the sparse ptr depends on data pointers.
+    if (extents_.empty()) {
+        extents_ = builder_.GetImageLayout();
+        if (extents_.empty()) {
+            LOG(VERBOSE) << "device does not support optimized super flashing";
+            return {nullptr, nullptr};
+        }
+    }
+
+    unsigned int block_size = base_metadata_->geometry.logical_block_size;
+    int64_t flashed_size = extents_.back().offset + extents_.back().size;
+    SparsePtr s(sparse_file_new(block_size, flashed_size), sparse_file_destroy);
+
+    for (const auto& extent : extents_) {
+        if (extent.offset / block_size > UINT_MAX) {
+            // Super image is too big to send via sparse files (>8TB).
+            LOG(VERBOSE) << "super image is too big to flash";
+            return {nullptr, nullptr};
+        }
+        unsigned int block = extent.offset / block_size;
+
+        int rv = 0;
+        switch (extent.type) {
+            case SuperImageExtent::Type::DONTCARE:
+                break;
+            case SuperImageExtent::Type::ZERO:
+                rv = sparse_file_add_fill(s.get(), 0, extent.size, block);
+                break;
+            case SuperImageExtent::Type::DATA:
+                rv = sparse_file_add_data(s.get(), extent.blob->data(), extent.size, block);
+                break;
+            case SuperImageExtent::Type::PARTITION: {
+                auto iter = image_fds_.find(extent.image_name);
+                if (iter == image_fds_.end()) {
+                    LOG(FATAL) << "image added but not found: " << extent.image_name;
+                    return {nullptr, nullptr};
+                }
+                rv = sparse_file_add_fd(s.get(), iter->second.get(), extent.image_offset,
+                                        extent.size, block);
+                break;
+            }
+            default:
+                LOG(VERBOSE) << "unrecognized extent type in super image layout";
+                return {nullptr, nullptr};
+        }
+        if (rv) {
+            LOG(VERBOSE) << "sparse failure building super image layout";
+            return {nullptr, nullptr};
+        }
+    }
+    return s;
+}
diff --git a/fastboot/super_flash_helper.h b/fastboot/super_flash_helper.h
new file mode 100644
index 0000000..29c15d0
--- /dev/null
+++ b/fastboot/super_flash_helper.h
@@ -0,0 +1,56 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <android-base/unique_fd.h>
+#include <liblp/liblp.h>
+#include <liblp/super_layout_builder.h>
+
+#include "util.h"
+
+class SuperFlashHelper final {
+  public:
+    explicit SuperFlashHelper(const ImageSource& source);
+
+    bool Open(android::base::borrowed_fd fd);
+    bool IncludeInSuper(const std::string& partition);
+    bool AddPartition(const std::string& partition, const std::string& image_name, bool optional);
+
+    // Note: the SparsePtr if non-null should not outlive SuperFlashHelper, since
+    // it depends on open fds and data pointers.
+    SparsePtr GetSparseLayout();
+
+    bool WillFlash(const std::string& partition) const {
+        return will_flash_.find(partition) != will_flash_.end();
+    }
+
+  private:
+    const ImageSource& source_;
+    android::fs_mgr::SuperLayoutBuilder builder_;
+    std::unique_ptr<android::fs_mgr::LpMetadata> base_metadata_;
+    std::vector<android::fs_mgr::SuperImageExtent> extents_;
+
+    // Cache open image fds. This keeps them alive while we flash the sparse
+    // file.
+    std::unordered_map<std::string, android::base::unique_fd> image_fds_;
+    std::unordered_set<std::string> will_flash_;
+};