fastbootd: Add commands for logical partitions.

This patch adds the following new commands to fastbootd:

    getvar is_logical:<partition>
    create-logical-logical-partition <partition> <size>
    delete-logical-partition <partition>
    resize-logicallogical-partition <partition> <size>

All of these commands operate on logical partitions only, and require a
properly configured "super" partition to operate.

Bug: 78793464
Test: fastboot create-logical-partition example 4096
      fastboot create-logical-partition example 4096 returns error
      fastboot getvar is-logical:example returns "yes"
      fastboot getvar partition-size:example returns "1000"
      fastboot resize-logicalpartition example 8000
      fastboot getvar partition-size:example returns "2000"
      fastboot delete-logical-partition example
      fastboot getvar is-logical:example returns error
      fastboot getvar is-logical:system_a returns "no"

Change-Id: Iff323eabcf5c559fa04c7c92574650b01803eb1f
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index 9995052..3414d53 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -108,6 +108,7 @@
         "libbase",
         "libbootloader_message",
         "libcutils",
+        "libext2_uuid",
         "libext4_utils",
         "libfs_mgr",
         "libhidlbase",
diff --git a/fastboot/constants.h b/fastboot/constants.h
index fa17070..b780ba5 100644
--- a/fastboot/constants.h
+++ b/fastboot/constants.h
@@ -30,6 +30,9 @@
 #define FB_CMD_REBOOT_RECOVERY "reboot-recovery"
 #define FB_CMD_REBOOT_FASTBOOT "reboot-fastboot"
 #define FB_CMD_POWERDOWN "powerdown"
+#define FB_CMD_CREATE_PARTITION "create-logical-partition"
+#define FB_CMD_DELETE_PARTITION "delete-logical-partition"
+#define FB_CMD_RESIZE_PARTITION "resize-logical-partition"
 
 #define RESPONSE_OKAY "OKAY"
 #define RESPONSE_FAIL "FAIL"
@@ -53,3 +56,4 @@
 #define FB_VAR_PARTITION_SIZE "partition-size"
 #define FB_VAR_SLOT_SUCCESSFUL "slot-successful"
 #define FB_VAR_SLOT_UNBOOTABLE "slot-unbootable"
+#define FB_VAR_IS_LOGICAL "is-logical"
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index 7eaefe6..053d654 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -27,6 +27,9 @@
 #include <android-base/unique_fd.h>
 #include <cutils/android_reboot.h>
 #include <ext4_utils/wipe.h>
+#include <liblp/builder.h>
+#include <liblp/liblp.h>
+#include <uuid/uuid.h>
 
 #include "constants.h"
 #include "fastboot_device.h"
@@ -37,6 +40,7 @@
 using ::android::hardware::boot::V1_0::BoolResult;
 using ::android::hardware::boot::V1_0::CommandResult;
 using ::android::hardware::boot::V1_0::Slot;
+using namespace android::fs_mgr;
 
 bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args) {
     using VariableHandler = std::function<bool(FastbootDevice*, const std::vector<std::string>&)>;
@@ -54,7 +58,8 @@
             {FB_VAR_HAS_SLOT, GetHasSlot},
             {FB_VAR_SLOT_SUCCESSFUL, GetSlotSuccessful},
             {FB_VAR_SLOT_UNBOOTABLE, GetSlotUnbootable},
-            {FB_VAR_PARTITION_SIZE, GetPartitionSize}};
+            {FB_VAR_PARTITION_SIZE, GetPartitionSize},
+            {FB_VAR_IS_LOGICAL, GetPartitionIsLogical}};
 
     // args[0] is command name, args[1] is variable.
     auto found_variable = kVariableMap.find(args[1]);
@@ -209,3 +214,124 @@
     TEMP_FAILURE_RETRY(pause());
     return status;
 }
+
+// Helper class for opening a handle to a MetadataBuilder and writing the new
+// partition table to the same place it was read.
+class PartitionBuilder {
+  public:
+    explicit PartitionBuilder(FastbootDevice* device);
+
+    bool Write();
+    bool Valid() const { return !!builder_; }
+    MetadataBuilder* operator->() const { return builder_.get(); }
+
+  private:
+    std::string super_device_;
+    uint32_t slot_number_;
+    std::unique_ptr<MetadataBuilder> builder_;
+};
+
+PartitionBuilder::PartitionBuilder(FastbootDevice* device) {
+    auto super_device = FindPhysicalPartition(LP_METADATA_PARTITION_NAME);
+    if (!super_device) {
+        return;
+    }
+    super_device_ = *super_device;
+
+    std::string slot = device->GetCurrentSlot();
+    slot_number_ = SlotNumberForSlotSuffix(slot);
+    builder_ = MetadataBuilder::New(super_device_, slot_number_);
+}
+
+bool PartitionBuilder::Write() {
+    std::unique_ptr<LpMetadata> metadata = builder_->Export();
+    if (!metadata) {
+        return false;
+    }
+    return UpdatePartitionTable(super_device_, *metadata.get(), slot_number_);
+}
+
+bool CreatePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+    if (args.size() < 3) {
+        return device->WriteFail("Invalid partition name and size");
+    }
+
+    uint64_t partition_size;
+    std::string partition_name = args[1];
+    if (!android::base::ParseUint(args[2].c_str(), &partition_size)) {
+        return device->WriteFail("Invalid partition size");
+    }
+
+    PartitionBuilder builder(device);
+    if (!builder.Valid()) {
+        return device->WriteFail("Could not open super partition");
+    }
+    // TODO(112433293) Disallow if the name is in the physical table as well.
+    if (builder->FindPartition(partition_name)) {
+        return device->WriteFail("Partition already exists");
+    }
+
+    // Make a random UUID, since they're not currently used.
+    uuid_t uuid;
+    char uuid_str[37];
+    uuid_generate_random(uuid);
+    uuid_unparse(uuid, uuid_str);
+
+    Partition* partition = builder->AddPartition(partition_name, uuid_str, 0);
+    if (!partition) {
+        return device->WriteFail("Failed to add partition");
+    }
+    if (!builder->ResizePartition(partition, partition_size)) {
+        builder->RemovePartition(partition_name);
+        return device->WriteFail("Not enough space for partition");
+    }
+    if (!builder.Write()) {
+        return device->WriteFail("Failed to write partition table");
+    }
+    return device->WriteOkay("Partition created");
+}
+
+bool DeletePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+    if (args.size() < 2) {
+        return device->WriteFail("Invalid partition name and size");
+    }
+
+    PartitionBuilder builder(device);
+    if (!builder.Valid()) {
+        return device->WriteFail("Could not open super partition");
+    }
+    builder->RemovePartition(args[1]);
+    if (!builder.Write()) {
+        return device->WriteFail("Failed to write partition table");
+    }
+    return device->WriteOkay("Partition deleted");
+}
+
+bool ResizePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args) {
+    if (args.size() < 3) {
+        return device->WriteFail("Invalid partition name and size");
+    }
+
+    uint64_t partition_size;
+    std::string partition_name = args[1];
+    if (!android::base::ParseUint(args[2].c_str(), &partition_size)) {
+        return device->WriteFail("Invalid partition size");
+    }
+
+    PartitionBuilder builder(device);
+    if (!builder.Valid()) {
+        return device->WriteFail("Could not open super partition");
+    }
+
+    Partition* partition = builder->FindPartition(partition_name);
+    if (!partition) {
+        return device->WriteFail("Partition does not exist");
+    }
+    if (!builder->ResizePartition(partition, partition_size)) {
+        return device->WriteFail("Not enough space to resize partition");
+    }
+    if (!builder.Write()) {
+        return device->WriteFail("Failed to write partition table");
+    }
+    return device->WriteOkay("Partition resized");
+}
diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h
index 830eb55..f67df91 100644
--- a/fastboot/device/commands.h
+++ b/fastboot/device/commands.h
@@ -41,3 +41,6 @@
 bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool EraseHandler(FastbootDevice* device, const std::vector<std::string>& args);
 bool FlashHandler(FastbootDevice* device, const std::vector<std::string>& args);
+bool CreatePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args);
+bool DeletePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args);
+bool ResizePartitionHandler(FastbootDevice* device, const std::vector<std::string>& args);
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index b94fbb0..c306e67 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -43,6 +43,9 @@
               {FB_CMD_REBOOT_RECOVERY, RebootRecoveryHandler},
               {FB_CMD_ERASE, EraseHandler},
               {FB_CMD_FLASH, FlashHandler},
+              {FB_CMD_CREATE_PARTITION, CreatePartitionHandler},
+              {FB_CMD_DELETE_PARTITION, DeletePartitionHandler},
+              {FB_CMD_RESIZE_PARTITION, ResizePartitionHandler},
       }),
       transport_(std::make_unique<ClientUsbTransport>()),
       boot_control_hal_(IBootControl::getService()) {}
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index b51b985..70e4bcc 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -148,3 +148,20 @@
     uint64_t size = get_block_device_size(handle.fd());
     return device->WriteOkay(android::base::StringPrintf("%" PRIX64, size));
 }
+
+bool GetPartitionIsLogical(FastbootDevice* device, const std::vector<std::string>& args) {
+    if (args.size() < 1) {
+        return device->WriteFail("Missing argument");
+    }
+    // Note: if a partition name is in both the GPT and the super partition, we
+    // return "true", to be consistent with prefering to flash logical partitions
+    // over physical ones.
+    std::string partition_name = args[0];
+    if (LogicalPartitionExists(partition_name, device->GetCurrentSlot())) {
+        return device->WriteOkay("yes");
+    }
+    if (FindPhysicalPartition(partition_name)) {
+        return device->WriteOkay("no");
+    }
+    return device->WriteFail("Partition not found");
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index 88947e0..0c706d7 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -35,3 +35,4 @@
 bool GetUnlocked(FastbootDevice* device, const std::vector<std::string>& args);
 bool GetHasSlot(FastbootDevice* device, const std::vector<std::string>& args);
 bool GetPartitionSize(FastbootDevice* device, const std::vector<std::string>& args);
+bool GetPartitionIsLogical(FastbootDevice* device, const std::vector<std::string>& args);
diff --git a/fastboot/engine.cpp b/fastboot/engine.cpp
index 63ee2af..6890643 100644
--- a/fastboot/engine.cpp
+++ b/fastboot/engine.cpp
@@ -155,6 +155,21 @@
                                         total);
 }
 
+void fb_queue_create_partition(const std::string& partition, const std::string& size) {
+    Action& a = queue_action(OP_COMMAND, FB_CMD_CREATE_PARTITION ":" + partition + ":" + size);
+    a.msg = "Creating '" + partition + "'";
+}
+
+void fb_queue_delete_partition(const std::string& partition) {
+    Action& a = queue_action(OP_COMMAND, FB_CMD_DELETE_PARTITION ":" + partition);
+    a.msg = "Deleting '" + partition + "'";
+}
+
+void fb_queue_resize_partition(const std::string& partition, const std::string& size) {
+    Action& a = queue_action(OP_COMMAND, FB_CMD_RESIZE_PARTITION ":" + partition + ":" + size);
+    a.msg = "Resizing '" + partition + "'";
+}
+
 static int match(const char* str, const char** value, unsigned count) {
     unsigned n;
 
diff --git a/fastboot/engine.h b/fastboot/engine.h
index 74aaa6a..8aebdd7 100644
--- a/fastboot/engine.h
+++ b/fastboot/engine.h
@@ -69,6 +69,9 @@
 void fb_queue_upload(const std::string& outfile);
 void fb_queue_notice(const std::string& notice);
 void fb_queue_wait_for_disconnect(void);
+void fb_queue_create_partition(const std::string& partition, const std::string& size);
+void fb_queue_delete_partition(const std::string& partition);
+void fb_queue_resize_partition(const std::string& partition, const std::string& size);
 int64_t fb_execute_queue();
 void fb_set_active(const std::string& slot);
 
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index d652c0d..dc94952 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -1691,6 +1691,17 @@
             } else {
                 syntax_error("unknown 'flashing' command %s", args[0].c_str());
             }
+        } else if (command == "create-logical-partition") {
+            std::string partition = next_arg(&args);
+            std::string size = next_arg(&args);
+            fb_queue_create_partition(partition, size);
+        } else if (command == "delete-logical-partition") {
+            std::string partition = next_arg(&args);
+            fb_queue_delete_partition(partition);
+        } else if (command == "resize-logical-partition") {
+            std::string partition = next_arg(&args);
+            std::string size = next_arg(&args);
+            fb_queue_resize_partition(partition, size);
         } else {
             syntax_error("unknown command %s", command.c_str());
         }