[paver] Add support for determining ABR partition by partition UUID.

This will be used by depthcharge to tell Zircon about the currently
active boot slot.

Bug: 74800
Bug: 74933
Change-Id: I994bf1481b317ff2be756fb3fbf7585ca29a8c03
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/522821
Commit-Queue: Simon Shields <simonshields@google.com>
Reviewed-by: Suraj Malhotra <surajmalhotra@google.com>
diff --git a/docs/reference/kernel/kernel_cmdline.md b/docs/reference/kernel/kernel_cmdline.md
index cb25e2d..2990e74 100644
--- a/docs/reference/kernel/kernel_cmdline.md
+++ b/docs/reference/kernel/kernel_cmdline.md
@@ -627,6 +627,13 @@
 informs the paver that ABR is supported, and that it should update the ABR
 metadata.
 
+## zvb.boot-partition-uuid=\<UUID>
+
+An alternative to zvb.current_slot - makes Fuchsia aware of the slot booted by
+the bootloader by passing the UUID of the partition containing the Zircon kernel
+that was booted. Setting this also informs the paver that ABR is supported, and
+that it should update the ABR metadata.
+
 ## console.path=\<path>
 
 Specify console device path. If not specified device manager will open
diff --git a/src/storage/lib/paver/abr-client.cc b/src/storage/lib/paver/abr-client.cc
index bde05b6..4f50d61 100644
--- a/src/storage/lib/paver/abr-client.cc
+++ b/src/storage/lib/paver/abr-client.cc
@@ -4,51 +4,39 @@
 
 #include "src/storage/lib/paver/abr-client.h"
 
+#include <dirent.h>
 #include <endian.h>
 #include <fuchsia/boot/llcpp/fidl.h>
+#include <fuchsia/device/llcpp/fidl.h>
 #include <fuchsia/io/llcpp/fidl.h>
+#include <fuchsia/paver/llcpp/fidl.h>
 #include <lib/abr/abr.h>
 #include <lib/cksum.h>
+#include <lib/fdio/cpp/caller.h>
 #include <lib/fdio/directory.h>
+#include <lib/fidl/llcpp/wire_messaging.h>
+#include <lib/fit/defer.h>
 #include <lib/service/llcpp/service.h>
 #include <stdio.h>
 #include <string.h>
 #include <zircon/errors.h>
+#include <zircon/hw/gpt.h>
 #include <zircon/status.h>
 
 #include <string_view>
 
+#include "src/lib/uuid/uuid.h"
 #include "src/storage/lib/paver/device-partitioner.h"
 #include "src/storage/lib/paver/partition-client.h"
 #include "src/storage/lib/paver/pave-logging.h"
 
 namespace abr {
 
+namespace partition = fuchsia_hardware_block_partition;
 using fuchsia_paver::wire::Asset;
 using fuchsia_paver::wire::Configuration;
 
-zx::status<Configuration> QueryBootConfig(fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root) {
-  auto endpoints = fidl::CreateEndpoints<fuchsia_boot::Arguments>();
-  if (endpoints.is_error()) {
-    return endpoints.take_error();
-  }
-  auto client_end = service::ConnectAt<fuchsia_boot::Arguments>(svc_root);
-  if (!client_end.is_ok()) {
-    return client_end.take_error();
-  }
-  auto client = fidl::BindSyncClient(std::move(*client_end));
-  auto result = client.GetString(::fidl::StringView{"zvb.current_slot"});
-  if (!result.ok()) {
-    return zx::error(result.status());
-  }
-
-  const auto response = result.Unwrap();
-  if (response->value.is_null()) {
-    ERROR("Kernel cmdline param zvb.current_slot not found!\n");
-    return zx::error(ZX_ERR_NOT_SUPPORTED);
-  }
-
-  auto slot = std::string_view{response->value.data(), response->value.size()};
+zx::status<Configuration> CurrentSlotToConfiguration(std::string_view slot) {
   // Some bootloaders prefix slot with dash or underscore. We strip them for consistency.
   slot.remove_prefix(std::min(slot.find_first_not_of("_-"), slot.size()));
   if (slot.compare("a") == 0) {
@@ -63,10 +51,134 @@
   return zx::error(ZX_ERR_NOT_SUPPORTED);
 }
 
+bool FindPartitionLabelByGuid(const fbl::unique_fd& devfs_root, const uint8_t* guid,
+                              std::string& out) {
+  constexpr char kBlockDevPath[] = "class/block/";
+  out.clear();
+  fbl::unique_fd d_fd(openat(devfs_root.get(), kBlockDevPath, O_RDONLY));
+  if (!d_fd) {
+    ERROR("Cannot inspect block devices\n");
+    return false;
+  }
+
+  DIR* d = fdopendir(d_fd.release());
+  if (d == nullptr) {
+    ERROR("Cannot inspect block devices\n");
+    return false;
+  }
+  const auto closer = fit::defer([&]() { closedir(d); });
+
+  struct dirent* de;
+  while ((de = readdir(d)) != nullptr) {
+    fbl::unique_fd fd(openat(dirfd(d), de->d_name, O_RDWR));
+    if (!fd) {
+      continue;
+    }
+    fdio_cpp::FdioCaller caller(std::move(fd));
+
+    auto result = fidl::WireCall<partition::Partition>(caller.channel()).GetInstanceGuid();
+    if (!result.ok()) {
+      continue;
+    }
+    const auto& response = result.value();
+    if (response.status != ZX_OK) {
+      continue;
+    }
+    if (memcmp(response.guid->value.data_, guid, GPT_GUID_LEN) != 0) {
+      continue;
+    }
+
+    auto result2 = fidl::WireCall<partition::Partition>(caller.channel()).GetName();
+    if (!result2.ok()) {
+      continue;
+    }
+
+    const auto& response2 = result2.value();
+    if (response2.status != ZX_OK) {
+      continue;
+    }
+    std::string_view name(response2.name.data(), response2.name.size());
+    out.append(name);
+    return true;
+  }
+
+  return false;
+}
+
+zx::status<Configuration> PartitionUuidToConfiguration(const fbl::unique_fd& devfs_root,
+                                                       uuid::Uuid uuid) {
+  std::string name;
+  auto result = FindPartitionLabelByGuid(devfs_root, uuid.bytes(), name);
+  if (!result) {
+    return zx::error(ZX_ERR_NOT_SUPPORTED);
+  }
+
+  // Partition label should be zircon-<slot> or zircon_<slot>. This is case insensitive.
+  static const size_t kZirconLength = sizeof("zircon") - 1;  // no null terminator.
+  // Partition must start with "zircon".
+  if (strncasecmp(name.data(), "zircon", kZirconLength) != 0) {
+    return zx::error(ZX_ERR_NOT_SUPPORTED);
+  }
+
+  name.erase(0, kZirconLength);
+
+  if (name[0] != '-' && name[0] != '_') {
+    return zx::error(ZX_ERR_NOT_SUPPORTED);
+  }
+
+  switch (name[1]) {
+    case 'a':
+    case 'A':
+      return zx::ok(Configuration::kA);
+    case 'b':
+    case 'B':
+      return zx::ok(Configuration::kB);
+    case 'r':
+    case 'R':
+      return zx::ok(Configuration::kRecovery);
+  }
+
+  return zx::error(ZX_ERR_NOT_SUPPORTED);
+}
+
+zx::status<Configuration> QueryBootConfig(const fbl::unique_fd& devfs_root,
+                                          fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root) {
+  auto client_end = service::ConnectAt<fuchsia_boot::Arguments>(svc_root);
+  if (!client_end.is_ok()) {
+    return client_end.take_error();
+  }
+  auto client = fidl::BindSyncClient(std::move(*client_end));
+  std::array<fidl::StringView, 2> arguments{
+      fidl::StringView{"zvb.current_slot"},
+      fidl::StringView{"zvb.boot-partition-uuid"},
+  };
+  auto result = client.GetStrings(fidl::VectorView<fidl::StringView>::FromExternal(arguments));
+  if (!result.ok()) {
+    return zx::error(result.status());
+  }
+
+  const auto response = result.Unwrap();
+  if (!response->values[0].is_null()) {
+    return CurrentSlotToConfiguration(response->values[0].get());
+  }
+  if (!response->values[1].is_null()) {
+    auto uuid = uuid::Uuid::FromString(response->values[1].get());
+    if (uuid == std::nullopt) {
+      return zx::error(ZX_ERR_NOT_SUPPORTED);
+    }
+
+    return PartitionUuidToConfiguration(devfs_root, uuid.value());
+  }
+
+  ERROR("Kernel cmdline param zvb.current_slot and zvb.boot-partition-uuid not found!\n");
+  return zx::error(ZX_ERR_NOT_SUPPORTED);
+}
+
 namespace {
 
-zx::status<> SupportsVerifiedBoot(fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root) {
-  return zx::make_status(QueryBootConfig(svc_root).status_value());
+zx::status<> SupportsVerifiedBoot(const fbl::unique_fd& devfs_root,
+                                  fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root) {
+  return zx::make_status(QueryBootConfig(devfs_root, svc_root).status_value());
 }
 }  // namespace
 
@@ -123,7 +235,7 @@
 zx::status<std::unique_ptr<abr::Client>> ClientFactory::Create(
     fbl::unique_fd devfs_root, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root,
     std::shared_ptr<paver::Context> context) {
-  if (auto status = SupportsVerifiedBoot(svc_root); status.is_error()) {
+  if (auto status = SupportsVerifiedBoot(devfs_root, svc_root); status.is_error()) {
     return status.take_error();
   }
 
diff --git a/src/storage/lib/paver/abr-client.h b/src/storage/lib/paver/abr-client.h
index 3e6fe6f..5a7bf6b 100644
--- a/src/storage/lib/paver/abr-client.h
+++ b/src/storage/lib/paver/abr-client.h
@@ -16,14 +16,21 @@
 
 #include <fbl/unique_fd.h>
 
+#include "src/lib/uuid/uuid.h"
 #include "src/storage/lib/paver/partition-client.h"
 #include "src/storage/lib/paver/paver-context.h"
 #include "zircon/errors.h"
 
 namespace abr {
 
+// For testing only.
+zx::status<fuchsia_paver::wire::Configuration> PartitionUuidToConfiguration(
+    const fbl::unique_fd& devfs_root, uuid::Uuid uuid);
+// For testing only.
+zx::status<fuchsia_paver::wire::Configuration> CurrentSlotToConfiguration(std::string_view slot);
+
 zx::status<fuchsia_paver::wire::Configuration> QueryBootConfig(
-    fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root);
+    const fbl::unique_fd& devfs_root, fidl::UnownedClientEnd<fuchsia_io::Directory> svc_root);
 
 // Interface for interacting with ABR data.
 class Client {
diff --git a/src/storage/lib/paver/chromebook-x64.cc b/src/storage/lib/paver/chromebook-x64.cc
index 6df57b7..a7b964a 100644
--- a/src/storage/lib/paver/chromebook-x64.cc
+++ b/src/storage/lib/paver/chromebook-x64.cc
@@ -89,7 +89,7 @@
   }
 
   // Determine if the firmware supports A/B in Zircon.
-  auto active_slot = abr::QueryBootConfig(svc_root);
+  auto active_slot = abr::QueryBootConfig(gpt_partitioner->devfs_root(), svc_root);
   bool supports_abr = active_slot.is_ok();
 
   auto partitioner =
diff --git a/src/storage/lib/paver/paver.cc b/src/storage/lib/paver/paver.cc
index f64589a..c6c91cd 100644
--- a/src/storage/lib/paver/paver.cc
+++ b/src/storage/lib/paver/paver.cc
@@ -880,7 +880,7 @@
 void BootManager::Bind(async_dispatcher_t* dispatcher, fbl::unique_fd devfs_root,
                        fidl::ClientEnd<fuchsia_io::Directory> svc_root,
                        std::shared_ptr<Context> context, zx::channel server) {
-  auto status = abr::ClientFactory::Create(std::move(devfs_root), svc_root, context);
+  auto status = abr::ClientFactory::Create(devfs_root.duplicate(), svc_root, context);
   if (status.is_error()) {
     ERROR("Failed to get ABR client: %s\n", status.status_string());
     fidl_epitaph_write(server.get(), status.error_value());
@@ -888,13 +888,14 @@
   }
   auto& abr_client = status.value();
 
-  auto boot_manager = std::make_unique<BootManager>(std::move(abr_client), std::move(svc_root));
+  auto boot_manager = std::make_unique<BootManager>(std::move(abr_client), std::move(devfs_root),
+                                                    std::move(svc_root));
   fidl::BindSingleInFlightOnly(dispatcher, std::move(server), std::move(boot_manager));
 }
 
 void BootManager::QueryCurrentConfiguration(QueryCurrentConfigurationRequestView request,
                                             QueryCurrentConfigurationCompleter::Sync& completer) {
-  zx::status<Configuration> status = abr::QueryBootConfig(svc_root_);
+  zx::status<Configuration> status = abr::QueryBootConfig(devfs_root_, svc_root_);
   if (status.is_error()) {
     completer.ReplyError(status.status_value());
     return;
diff --git a/src/storage/lib/paver/paver.h b/src/storage/lib/paver/paver.h
index 4282b63..3f10e74 100644
--- a/src/storage/lib/paver/paver.h
+++ b/src/storage/lib/paver/paver.h
@@ -200,9 +200,11 @@
 
 class BootManager : public fidl::WireServer<fuchsia_paver::BootManager> {
  public:
-  BootManager(std::unique_ptr<abr::Client> abr_client,
+  BootManager(std::unique_ptr<abr::Client> abr_client, fbl::unique_fd devfs_root,
               fidl::ClientEnd<fuchsia_io::Directory> svc_root)
-      : abr_client_(std::move(abr_client)), svc_root_(std::move(svc_root)) {}
+      : abr_client_(std::move(abr_client)),
+        devfs_root_(std::move(devfs_root)),
+        svc_root_(std::move(svc_root)) {}
 
   static void Bind(async_dispatcher_t* dispatcher, fbl::unique_fd devfs_root,
                    fidl::ClientEnd<fuchsia_io::Directory> svc_root,
@@ -232,6 +234,7 @@
 
  private:
   std::unique_ptr<abr::Client> abr_client_;
+  fbl::unique_fd devfs_root_;
   fidl::ClientEnd<fuchsia_io::Directory> svc_root_;
 };
 
diff --git a/src/storage/lib/paver/test/BUILD.gn b/src/storage/lib/paver/test/BUILD.gn
index bae5bab..529651d 100644
--- a/src/storage/lib/paver/test/BUILD.gn
+++ b/src/storage/lib/paver/test/BUILD.gn
@@ -43,6 +43,7 @@
     "//src/firmware/lib/abr",
     "//src/lib/storage/fs_management",
     "//src/lib/storage/vfs/cpp",
+    "//src/lib/uuid",
     "//src/storage/fvm",
     "//src/storage/gpt",
     "//src/storage/lib/paver",
diff --git a/src/storage/lib/paver/test/abr-test.cc b/src/storage/lib/paver/test/abr-test.cc
index 045e657..53f254a 100644
--- a/src/storage/lib/paver/test/abr-test.cc
+++ b/src/storage/lib/paver/test/abr-test.cc
@@ -25,6 +25,7 @@
 #include "gpt/cros.h"
 #include "src/lib/storage/vfs/cpp/pseudo_dir.h"
 #include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
+#include "src/lib/uuid/uuid.h"
 #include "src/storage/lib/paver/abr-client-vboot.h"
 #include "src/storage/lib/paver/abr-client.h"
 #include "src/storage/lib/paver/astro.h"
@@ -32,6 +33,7 @@
 #include "src/storage/lib/paver/luis.h"
 #include "src/storage/lib/paver/sherlock.h"
 #include "src/storage/lib/paver/test/test-utils.h"
+#include "src/storage/lib/paver/utils.h"
 #include "src/storage/lib/paver/x64.h"
 
 namespace {
@@ -234,4 +236,128 @@
   ASSERT_TRUE(info->is_marked_successful);
   ASSERT_EQ(info->num_tries_remaining, 0);
 }
+
+class CurrentSlotUuidTest : public zxtest::Test {
+ protected:
+  static constexpr int kBlockSize = 512;
+  static constexpr int kDiskBlocks = 1024;
+  static constexpr uint8_t kEmptyType[GPT_GUID_LEN] = GUID_EMPTY_VALUE;
+  static constexpr uint8_t kZirconType[GPT_GUID_LEN] = GPT_ZIRCON_ABR_TYPE_GUID;
+  static constexpr uint8_t kTestUuid[GPT_GUID_LEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
+                                                      0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb,
+                                                      0xcc, 0xdd, 0xee, 0xff};
+  CurrentSlotUuidTest() {
+    IsolatedDevmgr::Args args;
+    args.driver_search_paths.push_back("/boot/driver");
+    args.disable_block_watcher = true;
+
+    ASSERT_OK(IsolatedDevmgr::Create(&args, &devmgr_));
+    fbl::unique_fd fd;
+    ASSERT_OK(RecursiveWaitForFile(devmgr_.devfs_root(), "misc/ramctl", &fd));
+    ASSERT_NO_FATAL_FAILURES(
+        BlockDevice::Create(devmgr_.devfs_root(), kEmptyType, kDiskBlocks, kBlockSize, &disk_));
+  }
+
+  void CreateDiskWithPartition(const char* partition) {
+    ASSERT_OK(gpt::GptDevice::Create(disk_->fd(), /*blocksize=*/disk_->block_size(),
+                                     /*blocks=*/disk_->block_count(), &gpt_));
+    ASSERT_OK(gpt_->Sync());
+    ASSERT_OK(gpt_->AddPartition(partition, kZirconType, kTestUuid,
+                                 2 + gpt_->EntryArrayBlockCount(), 10, 0));
+    ASSERT_OK(gpt_->Sync());
+
+    fdio_cpp::UnownedFdioCaller caller(disk_->fd());
+    auto result = fidl::WireCall<fuchsia_device::Controller>(caller.channel())
+                      .Rebind(fidl::StringView("/boot/driver/gpt.so"));
+    ASSERT_TRUE(result.ok());
+    ASSERT_FALSE(result->result.is_err());
+  }
+
+  fidl::ClientEnd<fuchsia_io::Directory> GetSvcRoot() {
+    auto fshost_root = devmgr_.fshost_outgoing_dir();
+    auto local = service::ConnectAt<fuchsia_io::Directory>(fshost_root, "svc");
+    if (!local.is_ok()) {
+      std::cout << "Failed to connect to fshost svc dir: " << local.status_string() << std::endl;
+      return fidl::ClientEnd<fuchsia_io::Directory>();
+    }
+    return std::move(*local);
+  }
+
+  IsolatedDevmgr devmgr_;
+  std::unique_ptr<BlockDevice> disk_;
+  std::unique_ptr<gpt::GptDevice> gpt_;
+};
+
+TEST_F(CurrentSlotUuidTest, TestZirconAIsSlotA) {
+  ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("zircon-a"));
+
+  auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
+}
+
+TEST_F(CurrentSlotUuidTest, TestZirconAWithUnderscore) {
+  ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("zircon_a"));
+
+  auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
+}
+
+TEST_F(CurrentSlotUuidTest, TestZirconAMixedCase) {
+  ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("ZiRcOn-A"));
+
+  auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
+}
+
+TEST_F(CurrentSlotUuidTest, TestZirconB) {
+  ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("zircon_b"));
+
+  auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kB);
+}
+
+TEST_F(CurrentSlotUuidTest, TestZirconR) {
+  ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("ZIRCON-R"));
+
+  auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kRecovery);
+}
+
+TEST_F(CurrentSlotUuidTest, TestInvalid) {
+  ASSERT_NO_FATAL_FAILURES(CreateDiskWithPartition("ZERCON-R"));
+
+  auto result = abr::PartitionUuidToConfiguration(devmgr_.devfs_root(), uuid::Uuid(kTestUuid));
+  ASSERT_TRUE(result.is_error());
+  ASSERT_EQ(result.error_value(), ZX_ERR_NOT_SUPPORTED);
+}
+
+TEST(CurrentSlotTest, TestA) {
+  auto result = abr::CurrentSlotToConfiguration("_a");
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kA);
+}
+
+TEST(CurrentSlotTest, TestB) {
+  auto result = abr::CurrentSlotToConfiguration("_b");
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kB);
+}
+
+TEST(CurrentSlotTest, TestR) {
+  auto result = abr::CurrentSlotToConfiguration("_r");
+  ASSERT_TRUE(result.is_ok());
+  ASSERT_EQ(result.value(), fuchsia_paver::wire::Configuration::kRecovery);
+}
+
+TEST(CurrentSlotTest, TestInvalid) {
+  auto result = abr::CurrentSlotToConfiguration("_x");
+  ASSERT_TRUE(result.is_error());
+  ASSERT_EQ(result.error_value(), ZX_ERR_NOT_SUPPORTED);
+}
+
 }  // namespace
diff --git a/src/storage/lib/paver/test/paversvc-test.cc b/src/storage/lib/paver/test/paversvc-test.cc
index 9a8d3bb..01648d6 100644
--- a/src/storage/lib/paver/test/paversvc-test.cc
+++ b/src/storage/lib/paver/test/paversvc-test.cc
@@ -35,6 +35,7 @@
 #include <soc/aml-common/aml-guid.h>
 #include <zxtest/zxtest.h>
 
+#include "lib/fidl/llcpp/vector_view.h"
 #include "src/lib/storage/vfs/cpp/pseudo_dir.h"
 #include "src/lib/storage/vfs/cpp/service.h"
 #include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
@@ -181,12 +182,16 @@
 
 class FakeBootArgs : public fidl::WireServer<fuchsia_boot::Arguments> {
  public:
-  void GetString(GetStringRequestView request, GetStringCompleter::Sync& completer) override {
-    completer.Reply(fidl::StringView::FromExternal(arg_response_));
-  }
+  void GetString(GetStringRequestView request, GetStringCompleter::Sync& completer) override {}
 
   // Stubs
-  void GetStrings(GetStringsRequestView request, GetStringsCompleter::Sync& completer) override {}
+  void GetStrings(GetStringsRequestView request, GetStringsCompleter::Sync& completer) override {
+    std::vector<fidl::StringView> response = {
+        fidl::StringView::FromExternal(arg_response_),
+        fidl::StringView(),
+    };
+    completer.Reply(fidl::VectorView<fidl::StringView>::FromExternal(response));
+  }
   void GetBool(GetBoolRequestView request, GetBoolCompleter::Sync& completer) override {
     if (strncmp(request->key.data(), "astro.sysconfig.abr-wear-leveling",
                 sizeof("astro.sysconfig.abr-wear-leveling")) == 0) {