fastbootd: Implement getvar all.

This implements getvar all by invoking each callback and writing an INFO
status for each result. For commands that take arguments, the variable
handler can specify a function that returns all possible arguments.
Currently this only applies to partition variables.

Bug: 78793464
Test: fastboot getvar all works
Change-Id: I1cf84e06bf67614b6f56171c0ee6ca5d7ac383c9
diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp
index eb18141..b1c2958 100644
--- a/fastboot/device/commands.cpp
+++ b/fastboot/device/commands.cpp
@@ -42,26 +42,66 @@
 using ::android::hardware::boot::V1_0::Slot;
 using namespace android::fs_mgr;
 
+struct VariableHandlers {
+    // Callback to retrieve the value of a single variable.
+    std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)> get;
+    // Callback to retrieve all possible argument combinations, for getvar all.
+    std::function<std::vector<std::vector<std::string>>(FastbootDevice*)> get_all_args;
+};
+
+static void GetAllVars(FastbootDevice* device, const std::string& name,
+                       const VariableHandlers& handlers) {
+    if (!handlers.get_all_args) {
+        std::string message;
+        if (!handlers.get(device, std::vector<std::string>(), &message)) {
+            return;
+        }
+        device->WriteInfo(android::base::StringPrintf("%s:%s", name.c_str(), message.c_str()));
+        return;
+    }
+
+    auto all_args = handlers.get_all_args(device);
+    for (const auto& args : all_args) {
+        std::string message;
+        if (!handlers.get(device, args, &message)) {
+            continue;
+        }
+        std::string arg_string = android::base::Join(args, ":");
+        device->WriteInfo(android::base::StringPrintf("%s:%s:%s", name.c_str(), arg_string.c_str(),
+                                                      message.c_str()));
+    }
+}
+
 bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args) {
-    using VariableHandler =
-            std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)>;
-    const std::unordered_map<std::string, VariableHandler> kVariableMap = {
-            {FB_VAR_VERSION, GetVersion},
-            {FB_VAR_VERSION_BOOTLOADER, GetBootloaderVersion},
-            {FB_VAR_VERSION_BASEBAND, GetBasebandVersion},
-            {FB_VAR_PRODUCT, GetProduct},
-            {FB_VAR_SERIALNO, GetSerial},
-            {FB_VAR_SECURE, GetSecure},
-            {FB_VAR_UNLOCKED, GetUnlocked},
-            {FB_VAR_MAX_DOWNLOAD_SIZE, GetMaxDownloadSize},
-            {FB_VAR_CURRENT_SLOT, ::GetCurrentSlot},
-            {FB_VAR_SLOT_COUNT, GetSlotCount},
-            {FB_VAR_HAS_SLOT, GetHasSlot},
-            {FB_VAR_SLOT_SUCCESSFUL, GetSlotSuccessful},
-            {FB_VAR_SLOT_UNBOOTABLE, GetSlotUnbootable},
-            {FB_VAR_PARTITION_SIZE, GetPartitionSize},
-            {FB_VAR_IS_LOGICAL, GetPartitionIsLogical},
-            {FB_VAR_IS_USERSPACE, GetIsUserspace}};
+    const std::unordered_map<std::string, VariableHandlers> kVariableMap = {
+            {FB_VAR_VERSION, {GetVersion, nullptr}},
+            {FB_VAR_VERSION_BOOTLOADER, {GetBootloaderVersion, nullptr}},
+            {FB_VAR_VERSION_BASEBAND, {GetBasebandVersion, nullptr}},
+            {FB_VAR_PRODUCT, {GetProduct, nullptr}},
+            {FB_VAR_SERIALNO, {GetSerial, nullptr}},
+            {FB_VAR_SECURE, {GetSecure, nullptr}},
+            {FB_VAR_UNLOCKED, {GetUnlocked, nullptr}},
+            {FB_VAR_MAX_DOWNLOAD_SIZE, {GetMaxDownloadSize, nullptr}},
+            {FB_VAR_CURRENT_SLOT, {::GetCurrentSlot, nullptr}},
+            {FB_VAR_SLOT_COUNT, {GetSlotCount, nullptr}},
+            {FB_VAR_HAS_SLOT, {GetHasSlot, GetAllPartitionArgsNoSlot}},
+            {FB_VAR_SLOT_SUCCESSFUL, {GetSlotSuccessful, nullptr}},
+            {FB_VAR_SLOT_UNBOOTABLE, {GetSlotUnbootable, nullptr}},
+            {FB_VAR_PARTITION_SIZE, {GetPartitionSize, GetAllPartitionArgsWithSlot}},
+            {FB_VAR_IS_LOGICAL, {GetPartitionIsLogical, GetAllPartitionArgsWithSlot}},
+            {FB_VAR_IS_USERSPACE, {GetIsUserspace, nullptr}}};
+
+    if (args.size() < 2) {
+        return device->WriteFail("Missing argument");
+    }
+
+    // Special case: return all variables that we can.
+    if (args[1] == "all") {
+        for (const auto& [name, handlers] : kVariableMap) {
+            GetAllVars(device, name, handlers);
+        }
+        return device->WriteOkay("");
+    }
 
     // args[0] is command name, args[1] is variable.
     auto found_variable = kVariableMap.find(args[1]);
@@ -71,7 +111,7 @@
 
     std::string message;
     std::vector<std::string> getvar_args(args.begin() + 2, args.end());
-    if (!found_variable->second(device, getvar_args, &message)) {
+    if (!found_variable->second.get(device, getvar_args, &message)) {
         return device->WriteFail(message);
     }
     return device->WriteOkay(message);
diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp
index 6ed6d32..55aca9c 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -137,3 +137,7 @@
 bool FastbootDevice::WriteFail(const std::string& message) {
     return WriteStatus(FastbootResult::FAIL, message);
 }
+
+bool FastbootDevice::WriteInfo(const std::string& message) {
+    return WriteStatus(FastbootResult::INFO, message);
+}
diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h
index addc2ef..171e7ae 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -39,9 +39,10 @@
     bool HandleData(bool read, std::vector<char>* data);
     std::string GetCurrentSlot();
 
-    // Shortcuts for writing OKAY and FAIL status results.
+    // Shortcuts for writing status results.
     bool WriteOkay(const std::string& message);
     bool WriteFail(const std::string& message);
+    bool WriteInfo(const std::string& message);
 
     std::vector<char>& download_data() { return download_data_; }
     Transport* get_transport() { return transport_.get(); }
diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp
index ec84576..0157e7f 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -16,6 +16,11 @@
 
 #include "utility.h"
 
+#include <dirent.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
 #include <android-base/logging.h>
 #include <fs_mgr_dm_linear.h>
 #include <liblp/liblp.h>
@@ -123,3 +128,33 @@
     *number = slot[0] - 'a';
     return true;
 }
+
+std::vector<std::string> ListPartitions(FastbootDevice* device) {
+    std::vector<std::string> partitions;
+
+    // First get physical partitions.
+    struct dirent* de;
+    std::unique_ptr<DIR, decltype(&closedir)> by_name(opendir("/dev/block/by-name"), closedir);
+    while ((de = readdir(by_name.get())) != nullptr) {
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
+            continue;
+        }
+        struct stat s;
+        std::string path = "/dev/block/by-name/" + std::string(de->d_name);
+        if (!stat(path.c_str(), &s) && S_ISBLK(s.st_mode)) {
+            partitions.emplace_back(de->d_name);
+        }
+    }
+
+    // Next get logical partitions.
+    if (auto path = FindPhysicalPartition(LP_METADATA_PARTITION_NAME)) {
+        uint32_t slot_number = SlotNumberForSlotSuffix(device->GetCurrentSlot());
+        if (auto metadata = ReadMetadata(path->c_str(), slot_number)) {
+            for (const auto& partition : metadata->partitions) {
+                std::string partition_name = GetPartitionName(partition);
+                partitions.emplace_back(partition_name);
+            }
+        }
+    }
+    return partitions;
+}
diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h
index 0931fc3..4f0d079 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -56,5 +56,5 @@
 bool LogicalPartitionExists(const std::string& name, const std::string& slot_suffix,
                             bool* is_zero_length = nullptr);
 bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle);
-
 bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number);
+std::vector<std::string> ListPartitions(FastbootDevice* device);
diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp
index 8eeda98..9f3fa75 100644
--- a/fastboot/device/variables.cpp
+++ b/fastboot/device/variables.cpp
@@ -222,3 +222,37 @@
     *message = "yes";
     return true;
 }
+
+std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device) {
+    std::vector<std::vector<std::string>> args;
+    auto partitions = ListPartitions(device);
+    for (const auto& partition : partitions) {
+        args.emplace_back(std::initializer_list<std::string>{partition});
+    }
+    return args;
+}
+
+std::vector<std::vector<std::string>> GetAllPartitionArgsNoSlot(FastbootDevice* device) {
+    auto partitions = ListPartitions(device);
+
+    std::string slot_suffix = device->GetCurrentSlot();
+    if (!slot_suffix.empty()) {
+        auto names = std::move(partitions);
+        for (const auto& name : names) {
+            std::string slotless_name = name;
+            if (android::base::EndsWith(name, "_a") || android::base::EndsWith(name, "_b")) {
+                slotless_name = name.substr(0, name.rfind("_"));
+            }
+            if (std::find(partitions.begin(), partitions.end(), slotless_name) ==
+                partitions.end()) {
+                partitions.emplace_back(slotless_name);
+            }
+        }
+    }
+
+    std::vector<std::vector<std::string>> args;
+    for (const auto& partition : partitions) {
+        args.emplace_back(std::initializer_list<std::string>{partition});
+    }
+    return args;
+}
diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h
index b643bbd..c3a64cf 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -48,3 +48,7 @@
                            std::string* message);
 bool GetIsUserspace(FastbootDevice* device, const std::vector<std::string>& args,
                     std::string* message);
+
+// Helpers for getvar all.
+std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device);
+std::vector<std::vector<std::string>> GetAllPartitionArgsNoSlot(FastbootDevice* device);