[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) {