| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "device-partitioner.h" |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/boot/llcpp/fidl.h> |
| #include <fuchsia/device/llcpp/fidl.h> |
| #include <fuchsia/hardware/block/llcpp/fidl.h> |
| #include <fuchsia/hardware/block/partition/llcpp/fidl.h> |
| #include <fuchsia/hardware/skipblock/llcpp/fidl.h> |
| #include <fuchsia/sysinfo/llcpp/fidl.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/unsafe.h> |
| #include <lib/fdio/watcher.h> |
| #include <libgen.h> |
| #include <zircon/status.h> |
| |
| #include <array> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| |
| #include <chromeos-disk-setup/chromeos-disk-setup.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/function.h> |
| #include <fbl/span.h> |
| #include <fbl/string_buffer.h> |
| #include <fbl/string_printf.h> |
| #include <fs-management/fvm.h> |
| #include <gpt/cros.h> |
| #include <soc/aml-common/aml-guid.h> |
| #include <zxcrypt/volume.h> |
| |
| #include "partition-client.h" |
| #include "pave-logging.h" |
| #include "validation.h" |
| |
| namespace paver { |
| |
| namespace { |
| |
| namespace block = ::llcpp::fuchsia::hardware::block; |
| namespace device = ::llcpp::fuchsia::device; |
| namespace partition = ::llcpp::fuchsia::hardware::block::partition; |
| namespace skipblock = ::llcpp::fuchsia::hardware::skipblock; |
| |
| constexpr size_t kKibibyte = 1024; |
| constexpr size_t kMebibyte = kKibibyte * 1024; |
| constexpr size_t kGibibyte = kMebibyte * 1024; |
| |
| bool FilterByType(const gpt_partition_t& part, const uint8_t type[GPT_GUID_LEN]) { |
| return memcmp(part.type, type, GPT_GUID_LEN) == 0; |
| } |
| |
| bool FilterByTypeAndName(const gpt_partition_t& part, const uint8_t type[GPT_GUID_LEN], |
| fbl::StringPiece name) { |
| char cstring_name[GPT_NAME_LEN]; |
| utf16_to_cstring(cstring_name, reinterpret_cast<const uint16_t*>(part.name), GPT_NAME_LEN); |
| return memcmp(part.type, type, GPT_GUID_LEN) == 0 && |
| // We use a case-insenstive comparison to be compatible with the previous naming scheme. |
| // On a ChromeOS device, all of the kernel partitions share a common GUID type, so we |
| // distinguish Zircon kernel partitions based on name. |
| strncasecmp(cstring_name, name.data(), name.length()) == 0; |
| } |
| |
| bool IsFvmPartition(const gpt_partition_t& part) { |
| const uint8_t partition_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| return memcmp(part.type, partition_type, GPT_GUID_LEN) == 0; |
| } |
| |
| bool IsGigabootPartition(const gpt_partition_t& part) { |
| const uint8_t efi_type[GPT_GUID_LEN] = GUID_EFI_VALUE; |
| return memcmp(part.type, efi_type, GPT_GUID_LEN) == 0; |
| } |
| |
| // Returns true if the spec partition is Zircon A/B/R. |
| bool IsZirconPartitionSpec(const PartitionSpec& spec) { |
| return spec.partition == Partition::kZirconA || spec.partition == Partition::kZirconB || |
| spec.partition == Partition::kZirconR; |
| } |
| |
| constexpr size_t ReservedHeaderBlocks(size_t blk_size) { |
| constexpr size_t kReservedEntryBlocks = (16 * 1024); |
| return (kReservedEntryBlocks + 2 * blk_size) / blk_size; |
| } |
| |
| // Helper function to auto-deduce type. |
| template <typename T> |
| std::unique_ptr<T> WrapUnique(T* ptr) { |
| return std::unique_ptr<T>(ptr); |
| } |
| |
| // Unbinds a device, waiting for the operation to complete. |directory| is the parent device of |
| // the device being removed (assumes only one child device). |
| zx_status_t UnbindDevice(zx::channel device, fbl::unique_fd directory, zx_duration_t timeout) { |
| auto cb = [](int dirfd, int event, const char* filename, void* cookie) { |
| auto device = reinterpret_cast<zx::channel*>(cookie); |
| switch (event) { |
| case WATCH_EVENT_REMOVE_FILE: |
| return ZX_ERR_STOP; |
| case WATCH_EVENT_WAITING: { |
| auto resp = device::Controller::Call::ScheduleUnbind(device->borrow()); |
| if (resp.status() != ZX_OK) { |
| return resp.status(); |
| } |
| if (resp->result.is_err()) { |
| return resp->result.err(); |
| } |
| return ZX_OK; |
| } |
| default: |
| return ZX_OK; |
| } |
| }; |
| |
| zx_time_t deadline = zx_deadline_after(timeout); |
| zx_status_t status = fdio_watch_directory(directory.get(), cb, deadline, &device); |
| if (status == ZX_ERR_STOP) { |
| status = ZX_OK; |
| } |
| return status; |
| } |
| |
| // Unbinds the FVM driver from the given device. Assumes that the driver is either |
| // loaded or not (but not in the process of being loaded). |
| zx_status_t UnbindFvm(const fbl::unique_fd& devfs_root, const char* device) { |
| fbl::StringBuffer<PATH_MAX> name_buffer; |
| name_buffer.Append(device); |
| name_buffer.Append("/fvm"); |
| |
| zx::channel local, remote; |
| zx_status_t status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = fdio_service_connect(name_buffer.data(), remote.release()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| fbl::unique_fd dir_fd(openat(devfs_root.get(), device, O_RDONLY)); |
| if (!dir_fd) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| return UnbindDevice(std::move(local), std::move(dir_fd), ZX_SEC(3)); |
| } |
| |
| zx_status_t OpenPartition(const fbl::unique_fd& devfs_root, const char* path, |
| fbl::Function<bool(const zx::channel&)> should_filter_file, |
| zx_duration_t timeout, zx::channel* out_partition) { |
| ZX_ASSERT(path != nullptr); |
| |
| struct CallbackInfo { |
| zx::channel* out_partition; |
| fbl::Function<bool(const zx::channel&)> should_filter_file; |
| }; |
| |
| CallbackInfo info = { |
| .out_partition = out_partition, |
| .should_filter_file = std::move(should_filter_file), |
| }; |
| |
| auto cb = [](int dirfd, int event, const char* filename, void* cookie) { |
| if (event != WATCH_EVENT_ADD_FILE) { |
| return ZX_OK; |
| } |
| if ((strcmp(filename, ".") == 0) || strcmp(filename, "..") == 0) { |
| return ZX_OK; |
| } |
| fdio_cpp::UnownedFdioCaller caller(dirfd); |
| |
| zx::channel partition_local, partition_remote; |
| if (zx::channel::create(0, &partition_local, &partition_remote) != ZX_OK) { |
| return ZX_OK; |
| } |
| if (fdio_service_connect_at(caller.borrow_channel(), filename, partition_remote.release()) != |
| ZX_OK) { |
| return ZX_OK; |
| } |
| auto info = static_cast<CallbackInfo*>(cookie); |
| if (info->should_filter_file(partition_local)) { |
| return ZX_OK; |
| } |
| if (info->out_partition) { |
| *(info->out_partition) = std::move(partition_local); |
| } |
| return ZX_ERR_STOP; |
| }; |
| |
| fbl::unique_fd dir_fd(openat(devfs_root.get(), path, O_RDONLY)); |
| if (!dir_fd) { |
| return ZX_ERR_IO; |
| } |
| |
| zx_time_t deadline = zx_deadline_after(timeout); |
| if (fdio_watch_directory(dir_fd.get(), cb, deadline, &info) != ZX_ERR_STOP) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| return ZX_OK; |
| } |
| |
| constexpr char kBlockDevPath[] = "class/block/"; |
| |
| zx_status_t OpenBlockPartition(const fbl::unique_fd& devfs_root, const uint8_t* unique_guid, |
| const uint8_t* type_guid, zx_duration_t timeout, |
| zx::channel* out_partition) { |
| ZX_ASSERT(unique_guid || type_guid); |
| |
| auto cb = [&](const zx::channel& chan) { |
| if (type_guid) { |
| auto result = partition::Partition::Call::GetTypeGuid(zx::unowned(chan)); |
| if (!result.ok()) { |
| return true; |
| } |
| auto& response = result.value(); |
| if (response.status != ZX_OK || |
| memcmp(response.guid->value.data(), type_guid, partition::GUID_LENGTH) != 0) { |
| return true; |
| } |
| } |
| if (unique_guid) { |
| auto result = partition::Partition::Call::GetInstanceGuid(zx::unowned(chan)); |
| if (!result.ok()) { |
| return true; |
| } |
| const auto& response = result.value(); |
| if (response.status != ZX_OK || |
| memcmp(response.guid->value.data(), unique_guid, partition::GUID_LENGTH) != 0) { |
| return true; |
| } |
| } |
| return false; |
| }; |
| |
| return OpenPartition(devfs_root, kBlockDevPath, cb, timeout, out_partition); |
| } |
| |
| constexpr char kSkipBlockDevPath[] = "class/skip-block/"; |
| |
| zx_status_t OpenSkipBlockPartition(const fbl::unique_fd& devfs_root, const uint8_t* type_guid, |
| zx_duration_t timeout, zx::channel* out_partition) { |
| ZX_ASSERT(type_guid); |
| |
| auto cb = [&](const zx::channel& chan) { |
| auto result = skipblock::SkipBlock::Call::GetPartitionInfo(zx::unowned(chan)); |
| if (!result.ok()) { |
| return true; |
| } |
| const auto& response = result.value(); |
| if (response.status != ZX_OK || memcmp(response.partition_info.partition_guid.data(), type_guid, |
| skipblock::GUID_LEN) != 0) { |
| return true; |
| } |
| return false; |
| }; |
| |
| return OpenPartition(devfs_root, kSkipBlockDevPath, cb, timeout, out_partition); |
| } |
| |
| bool HasSkipBlockDevice(const fbl::unique_fd& devfs_root) { |
| // Our proxy for detected a skip-block device is by checking for the |
| // existence of a device enumerated under the skip-block class. |
| const uint8_t type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE; |
| return OpenSkipBlockPartition(devfs_root, type, ZX_SEC(1), nullptr) == ZX_OK; |
| } |
| |
| // Attempts to open and overwrite the first block of the underlying |
| // partition. Does not rebind partition drivers. |
| // |
| // At most one of |unique_guid| and |type_guid| may be nullptr. |
| zx_status_t WipeBlockPartition(const fbl::unique_fd& devfs_root, const uint8_t* unique_guid, |
| const uint8_t* type_guid) { |
| zx::channel chan; |
| zx_status_t status = OpenBlockPartition(devfs_root, unique_guid, type_guid, ZX_SEC(3), &chan); |
| if (status != ZX_OK) { |
| ERROR("Warning: Could not open partition to wipe: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Overwrite the first block to (hackily) ensure the destroyed partition |
| // doesn't "reappear" in place. |
| BlockPartitionClient block_partition(std::move(chan)); |
| size_t block_size; |
| status = block_partition.GetBlockSize(&block_size); |
| if (status != ZX_OK) { |
| ERROR("Warning: Could not get block size of partition: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Rely on vmos being 0 initialized. |
| zx::vmo vmo; |
| status = zx::vmo::create(fbl::round_up(block_size, ZX_PAGE_SIZE), 0, &vmo); |
| if (status != ZX_OK) { |
| ERROR("Warning: Could not create vmo: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| status = block_partition.Write(vmo, block_size); |
| if (status != ZX_OK) { |
| ERROR("Warning: Could not write to block device: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| if ((status = block_partition.Flush()) != ZX_OK) { |
| ERROR("Warning: Failed to synchronize block device: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t IsBoard(const fbl::unique_fd& devfs_root, fbl::StringPiece board_name) { |
| zx::channel local, remote; |
| zx_status_t status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| fdio_cpp::UnownedFdioCaller caller(devfs_root.get()); |
| status = fdio_service_connect_at(caller.borrow_channel(), "sys/platform", remote.release()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| auto result = ::llcpp::fuchsia::sysinfo::SysInfo::Call::GetBoardName(zx::unowned(local)); |
| status = result.ok() ? result->status : result.status(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (strncmp(result->name.data(), board_name.data(), result->name.size()) == 0) { |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t IsBootloader(const fbl::unique_fd& devfs_root, fbl::StringPiece vendor) { |
| zx::channel local, remote; |
| zx_status_t status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| fdio_cpp::UnownedFdioCaller caller(devfs_root.get()); |
| status = fdio_service_connect_at(caller.borrow_channel(), "sys/platform", remote.release()); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| auto result = ::llcpp::fuchsia::sysinfo::SysInfo::Call::GetBootloaderVendor(zx::unowned(local)); |
| status = result.ok() ? result->status : result.status(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (strncmp(result->vendor.data(), vendor.data(), result->vendor.size()) == 0) { |
| return ZX_OK; |
| } |
| |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| void utf16_to_cstring(char* dst, const uint8_t* src, size_t charcount) { |
| while (charcount > 0) { |
| *dst++ = *src; |
| src += 2; |
| charcount -= 2; |
| } |
| } |
| |
| zx_status_t GptPartitionType(Partition type, uint8_t guid[GPT_GUID_LEN]) { |
| switch (type) { |
| case Partition::kBootloader: { |
| const uint8_t efi_type[GPT_GUID_LEN] = GUID_EFI_VALUE; |
| memcpy(guid, efi_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kZirconA: { |
| const uint8_t zircon_a_type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE; |
| memcpy(guid, zircon_a_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kZirconB: { |
| const uint8_t zircon_b_type[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE; |
| memcpy(guid, zircon_b_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kZirconR: { |
| const uint8_t zircon_r_type[GPT_GUID_LEN] = GUID_ZIRCON_R_VALUE; |
| memcpy(guid, zircon_r_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kVbMetaA: { |
| const uint8_t vbmeta_a_type[GPT_GUID_LEN] = GUID_VBMETA_A_VALUE; |
| memcpy(guid, vbmeta_a_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kVbMetaB: { |
| const uint8_t vbmeta_b_type[GPT_GUID_LEN] = GUID_VBMETA_B_VALUE; |
| memcpy(guid, vbmeta_b_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kVbMetaR: { |
| const uint8_t vbmeta_r_type[GPT_GUID_LEN] = GUID_VBMETA_R_VALUE; |
| memcpy(guid, vbmeta_r_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kAbrMeta: { |
| const uint8_t abr_meta_type[GPT_GUID_LEN] = GUID_ABR_META_VALUE; |
| memcpy(guid, abr_meta_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| case Partition::kFuchsiaVolumeManager: { |
| const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| memcpy(guid, fvm_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| default: |
| ERROR("Partition type is invalid\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| } |
| |
| zx_status_t CrosPartitionType(Partition type, uint8_t guid[GPT_GUID_LEN]) { |
| switch (type) { |
| case Partition::kZirconA: |
| case Partition::kZirconB: |
| case Partition::kZirconR: { |
| const uint8_t cros_kernel_type[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| memcpy(guid, cros_kernel_type, GPT_GUID_LEN); |
| return ZX_OK; |
| } |
| default: |
| return GptPartitionType(type, guid); |
| } |
| } |
| |
| bool SpecMatches(const PartitionSpec& a, const PartitionSpec& b) { |
| return a.partition == b.partition && a.content_type == b.content_type; |
| } |
| |
| } // namespace |
| |
| const char* PartitionName(Partition type) { |
| switch (type) { |
| case Partition::kBootloader: |
| return GUID_EFI_NAME; |
| case Partition::kZirconA: |
| return GUID_ZIRCON_A_NAME; |
| case Partition::kZirconB: |
| return GUID_ZIRCON_B_NAME; |
| case Partition::kZirconR: |
| return GUID_ZIRCON_R_NAME; |
| case Partition::kVbMetaA: |
| return GUID_VBMETA_A_NAME; |
| case Partition::kVbMetaB: |
| return GUID_VBMETA_B_NAME; |
| case Partition::kVbMetaR: |
| return GUID_VBMETA_R_NAME; |
| case Partition::kAbrMeta: |
| return GUID_ABR_META_NAME; |
| case Partition::kFuchsiaVolumeManager: |
| return GUID_FVM_NAME; |
| default: |
| return "Unknown"; |
| } |
| } |
| |
| fbl::String PartitionSpec::ToString() const { |
| if (content_type.data() == nullptr) { |
| return PartitionName(partition); |
| } |
| return fbl::StringPrintf("%s (%.*s)", PartitionName(partition), |
| static_cast<int>(content_type.size()), content_type.data()); |
| } |
| |
| std::unique_ptr<DevicePartitioner> DevicePartitioner::Create(fbl::unique_fd devfs_root, |
| zx::channel svc_root, Arch arch, |
| zx::channel block_device) { |
| std::optional<fbl::unique_fd> block_dev; |
| std::optional<fbl::unique_fd> block_dev_dup; |
| std::optional<fbl::unique_fd> block_dev_dup2; |
| if (block_device) { |
| int fd; |
| zx_status_t status = fdio_fd_create(block_device.release(), &fd); |
| if (status != ZX_OK) { |
| ERROR( |
| "Unable to create fd from block_device channel. Does it implement fuchsia.io.Node?: %s\n", |
| zx_status_get_string(status)); |
| return nullptr; |
| } |
| block_dev.emplace(fd); |
| block_dev_dup = block_dev->duplicate(); |
| block_dev_dup2 = block_dev->duplicate(); |
| } |
| std::unique_ptr<DevicePartitioner> device_partitioner; |
| if ((AstroPartitioner::Initialize(devfs_root.duplicate(), &device_partitioner) == ZX_OK) || |
| (As370Partitioner::Initialize(devfs_root.duplicate(), &device_partitioner) == ZX_OK) || |
| (SherlockPartitioner::Initialize(devfs_root.duplicate(), std::move(block_dev_dup2), |
| &device_partitioner) == ZX_OK) || |
| (CrosDevicePartitioner::Initialize(devfs_root.duplicate(), arch, std::move(block_dev_dup), |
| &device_partitioner) == ZX_OK) || |
| (EfiDevicePartitioner::Initialize(devfs_root.duplicate(), arch, std::move(block_dev), |
| &device_partitioner) == ZX_OK) || |
| (FixedDevicePartitioner::Initialize(std::move(devfs_root), &device_partitioner) == ZX_OK)) { |
| return device_partitioner; |
| } |
| return nullptr; |
| } |
| |
| /*====================================================* |
| * GPT Common * |
| *====================================================*/ |
| |
| bool GptDevicePartitioner::FindGptDevices(const fbl::unique_fd& devfs_root, GptDevices* out) { |
| 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 = fbl::MakeAutoCall([&]() { closedir(d); }); |
| |
| struct dirent* de; |
| GptDevices found_devices; |
| 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 = block::Block::Call::GetInfo(caller.channel()); |
| if (!result.ok()) { |
| continue; |
| } |
| const auto& response = result.value(); |
| if (response.status != ZX_OK) { |
| continue; |
| } |
| if (response.info->flags & BLOCK_FLAG_REMOVABLE) { |
| continue; |
| } |
| auto result2 = device::Controller::Call::GetTopologicalPath(caller.channel()); |
| if (result2.status() != ZX_OK) { |
| continue; |
| } |
| const auto& response2 = result2.value(); |
| if (response2.result.is_err()) { |
| continue; |
| } |
| |
| std::string path_str(response2.result.response().path.data(), |
| static_cast<size_t>(response2.result.response().path.size())); |
| |
| // The GPT which will be a non-removable block device that isn't a partition itself. |
| if (path_str.find("part-") == std::string::npos) { |
| found_devices.push_back(std::make_pair(path_str, caller.release())); |
| } |
| } |
| |
| if (found_devices.empty()) { |
| ERROR("No candidate GPT found\n"); |
| return false; |
| } |
| |
| *out = std::move(found_devices); |
| return true; |
| } |
| |
| zx_status_t GptDevicePartitioner::InitializeProvidedGptDevice( |
| fbl::unique_fd devfs_root, fbl::unique_fd gpt_device, |
| std::unique_ptr<GptDevicePartitioner>* gpt_out) { |
| fdio_cpp::UnownedFdioCaller caller(gpt_device.get()); |
| auto result = block::Block::Call::GetInfo(caller.channel()); |
| if (!result.ok()) { |
| ERROR("Warning: Could not acquire GPT block info: %s\n", zx_status_get_string(result.status())); |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.status != ZX_OK) { |
| ERROR("Warning: Could not acquire GPT block info: %s\n", zx_status_get_string(response.status)); |
| return response.status; |
| } |
| |
| std::unique_ptr<GptDevice> gpt; |
| if (GptDevice::Create(gpt_device.get(), response.info->block_size, response.info->block_count, |
| &gpt) != ZX_OK) { |
| ERROR("Failed to get GPT info\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (!gpt->Valid()) { |
| ERROR("Located GPT is invalid; Attempting to initialize\n"); |
| if (gpt->RemoveAllPartitions() != ZX_OK) { |
| ERROR("Failed to create empty GPT\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| if (gpt->Sync() != ZX_OK) { |
| ERROR("Failed to sync empty GPT\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| auto result = ::llcpp::fuchsia::device::Controller::Call::Rebind( |
| caller.channel(), fidl::StringView("/boot/driver/gpt.so")); |
| if (!result.ok() || result->result.is_err()) { |
| ERROR("Failed to re-read GPT\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| printf("Rebound GPT driver succesfully\n"); |
| } |
| |
| *gpt_out = WrapUnique(new GptDevicePartitioner(devfs_root.duplicate(), std::move(gpt_device), |
| std::move(gpt), *(response.info))); |
| return ZX_OK; |
| } |
| |
| zx_status_t GptDevicePartitioner::InitializeGpt(fbl::unique_fd devfs_root, |
| std::optional<fbl::unique_fd> block_device, |
| std::unique_ptr<GptDevicePartitioner>* gpt_out) { |
| if (block_device) { |
| return InitializeProvidedGptDevice(std::move(devfs_root), *std::move(block_device), gpt_out); |
| } |
| |
| GptDevices gpt_devices; |
| if (!FindGptDevices(devfs_root, &gpt_devices)) { |
| ERROR("Failed to find GPT\n"); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| std::unique_ptr<GptDevicePartitioner> gpt_partitioner; |
| for (auto& [_, gpt_device] : gpt_devices) { |
| fdio_cpp::UnownedFdioCaller caller(gpt_device.get()); |
| auto result = block::Block::Call::GetInfo(caller.channel()); |
| if (!result.ok()) { |
| ERROR("Warning: Could not acquire GPT block info: %s\n", |
| zx_status_get_string(result.status())); |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.status != ZX_OK) { |
| ERROR("Warning: Could not acquire GPT block info: %s\n", |
| zx_status_get_string(response.status)); |
| return response.status; |
| } |
| |
| std::unique_ptr<GptDevice> gpt; |
| if (GptDevice::Create(gpt_device.get(), response.info->block_size, response.info->block_count, |
| &gpt) != ZX_OK) { |
| ERROR("Failed to get GPT info\n"); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| if (!gpt->Valid()) { |
| continue; |
| } |
| |
| auto partitioner = WrapUnique(new GptDevicePartitioner( |
| devfs_root.duplicate(), std::move(gpt_device), std::move(gpt), *(response.info))); |
| |
| if (partitioner->FindPartition(IsFvmPartition, nullptr, nullptr) != ZX_OK) { |
| continue; |
| } |
| |
| if (gpt_partitioner) { |
| ERROR("Found multiple block devices with valid GPTs. Unsuppported.\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| gpt_partitioner = std::move(partitioner); |
| } |
| |
| if (gpt_partitioner) { |
| *gpt_out = std::move(gpt_partitioner); |
| return ZX_OK; |
| } |
| |
| ERROR( |
| "Unable to find a valid GPT on this device with the expected partitions. " |
| "Please run *one* of the following command(s):\n"); |
| |
| for (const auto& [gpt_path, _] : gpt_devices) { |
| ERROR("install-disk-image init-partition-tables --block-device %s\n", gpt_path.c_str()); |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| struct PartitionPosition { |
| size_t start; // Block, inclusive |
| size_t length; // In Blocks |
| }; |
| |
| zx_status_t GptDevicePartitioner::FindFirstFit(size_t bytes_requested, size_t* start_out, |
| size_t* length_out) const { |
| LOG("Looking for space\n"); |
| // Gather GPT-related information. |
| size_t blocks_requested = (bytes_requested + block_info_.block_size - 1) / block_info_.block_size; |
| |
| // Sort all partitions by starting block. |
| // For simplicity, include the 'start' and 'end' reserved spots as |
| // partitions. |
| size_t partition_count = 0; |
| PartitionPosition partitions[gpt::kPartitionCount + 2]; |
| const size_t reserved_blocks = ReservedHeaderBlocks(block_info_.block_size); |
| partitions[partition_count].start = 0; |
| partitions[partition_count++].length = reserved_blocks; |
| partitions[partition_count].start = block_info_.block_count - reserved_blocks; |
| partitions[partition_count++].length = reserved_blocks; |
| |
| for (uint32_t i = 0; i < gpt::kPartitionCount; i++) { |
| const gpt_partition_t* p = gpt_->GetPartition(i); |
| if (!p) { |
| continue; |
| } |
| partitions[partition_count].start = p->first; |
| partitions[partition_count].length = p->last - p->first + 1; |
| LOG("Partition seen with start %zu, end %zu (length %zu)\n", p->first, p->last, |
| partitions[partition_count].length); |
| partition_count++; |
| } |
| LOG("Sorting\n"); |
| qsort(partitions, partition_count, sizeof(PartitionPosition), [](const void* p1, const void* p2) { |
| ssize_t s1 = static_cast<ssize_t>(static_cast<const PartitionPosition*>(p1)->start); |
| ssize_t s2 = static_cast<ssize_t>(static_cast<const PartitionPosition*>(p2)->start); |
| return static_cast<int>(s1 - s2); |
| }); |
| |
| // Look for space between the partitions. Since the reserved spots of the |
| // GPT were included in |partitions|, all available space will be located |
| // "between" partitions. |
| for (size_t i = 0; i < partition_count - 1; i++) { |
| const size_t next = partitions[i].start + partitions[i].length; |
| LOG("Partition[%zu] From Block [%zu, %zu) ... (next partition starts at block %zu)\n", i, |
| partitions[i].start, next, partitions[i + 1].start); |
| |
| if (next > partitions[i + 1].start) { |
| ERROR("Corrupted GPT\n"); |
| return ZX_ERR_IO; |
| } |
| const size_t free_blocks = partitions[i + 1].start - next; |
| LOG(" There are %zu free blocks (%zu requested)\n", free_blocks, blocks_requested); |
| if (free_blocks >= blocks_requested) { |
| *start_out = next; |
| *length_out = free_blocks; |
| return ZX_OK; |
| } |
| } |
| ERROR("No GPT space found\n"); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| zx_status_t GptDevicePartitioner::CreateGptPartition(const char* name, const uint8_t* type, |
| uint64_t offset, uint64_t blocks, |
| uint8_t* out_guid) const { |
| zx_cprng_draw(out_guid, GPT_GUID_LEN); |
| |
| zx_status_t status; |
| if ((status = gpt_->AddPartition(name, type, out_guid, offset, blocks, 0)) != ZX_OK) { |
| ERROR("Failed to add partition\n"); |
| return ZX_ERR_IO; |
| } |
| if ((status = gpt_->Sync()) != ZX_OK) { |
| ERROR("Failed to sync GPT\n"); |
| return ZX_ERR_IO; |
| } |
| if ((status = gpt_->ClearPartition(offset, 1)) != ZX_OK) { |
| ERROR("Failed to clear first block of new partition\n"); |
| return status; |
| } |
| auto result = ::llcpp::fuchsia::device::Controller::Call::Rebind( |
| Channel(), fidl::StringView("/boot/driver/gpt.so")); |
| if (!result.ok()) { |
| ERROR("Failed to rebind GPT\n"); |
| return result.status(); |
| } |
| if (result->result.is_err()) { |
| ERROR("Failed to rebind GPT\n"); |
| return result->result.err(); |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t GptDevicePartitioner::AddPartition( |
| const char* name, const uint8_t* type, size_t minimum_size_bytes, size_t optional_reserve_bytes, |
| std::unique_ptr<PartitionClient>* out_partition) const { |
| uint64_t start, length; |
| zx_status_t status; |
| if ((status = FindFirstFit(minimum_size_bytes, &start, &length)) != ZX_OK) { |
| ERROR("Couldn't find fit\n"); |
| return status; |
| } |
| LOG("Found space in GPT - OK %zu @ %zu\n", length, start); |
| |
| if (optional_reserve_bytes) { |
| // If we can fulfill the requested size, and we still have space for the |
| // optional reserve section, then we should shorten the amount of blocks |
| // we're asking for. |
| // |
| // This isn't necessary, but it allows growing the GPT later, if necessary. |
| const size_t optional_reserve_blocks = optional_reserve_bytes / block_info_.block_size; |
| if (length - optional_reserve_bytes > (minimum_size_bytes / block_info_.block_size)) { |
| LOG("Space for reserve - OK\n"); |
| length -= optional_reserve_blocks; |
| } |
| } else { |
| length = fbl::round_up(minimum_size_bytes, block_info_.block_size) / block_info_.block_size; |
| } |
| LOG("Final space in GPT - OK %zu @ %zu\n", length, start); |
| |
| uint8_t guid[GPT_GUID_LEN]; |
| if ((status = CreateGptPartition(name, type, start, length, guid)) != ZX_OK) { |
| return status; |
| } |
| LOG("Added partition, waiting for bind\n"); |
| |
| zx::channel chan; |
| if ((status = OpenBlockPartition(devfs_root_, guid, type, ZX_SEC(15), &chan)) != ZX_OK) { |
| ERROR("Added partition, waiting for bind - NOT FOUND\n"); |
| return status; |
| } |
| |
| if (out_partition) { |
| out_partition->reset(new BlockPartitionClient(std::move(chan))); |
| } |
| |
| LOG("Added partition, waiting for bind - OK\n"); |
| return ZX_OK; |
| } |
| |
| zx_status_t GptDevicePartitioner::FindPartition(FilterCallback filter, |
| std::unique_ptr<PartitionClient>* out_partition, |
| gpt_partition_t** out) const { |
| for (uint32_t i = 0; i < gpt::kPartitionCount; i++) { |
| gpt_partition_t* p = gpt_->GetPartition(i); |
| if (!p) { |
| continue; |
| } |
| |
| if (filter(*p)) { |
| LOG("Found partition in GPT, partition %u\n", i); |
| if (out) { |
| *out = p; |
| } |
| if (out_partition) { |
| zx_status_t status; |
| zx::channel chan; |
| status = OpenBlockPartition(devfs_root_, p->guid, p->type, ZX_SEC(5), &chan); |
| if (status != ZX_OK) { |
| ERROR("Couldn't open partition\n"); |
| return status; |
| } |
| out_partition->reset(new BlockPartitionClient(std::move(chan))); |
| } |
| return ZX_OK; |
| } |
| } |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t GptDevicePartitioner::WipePartitions(FilterCallback filter) const { |
| bool modify = false; |
| for (uint32_t i = 0; i < gpt::kPartitionCount; i++) { |
| const gpt_partition_t* p = gpt_->GetPartition(i); |
| if (!p) { |
| continue; |
| } |
| if (!filter(*p)) { |
| continue; |
| } |
| |
| modify = true; |
| |
| // Ignore the return status; wiping is a best-effort approach anyway. |
| WipeBlockPartition(devfs_root_, p->guid, p->type); |
| |
| if (gpt_->RemovePartition(p->guid) != ZX_OK) { |
| ERROR("Warning: Could not remove partition\n"); |
| } else { |
| // If we successfully clear the partition, then all subsequent |
| // partitions get shifted down. If we just deleted partition 'i', |
| // we now need to look at partition 'i' again, since it's now |
| // occupied by what was in 'i+1'. |
| i--; |
| } |
| } |
| if (modify) { |
| gpt_->Sync(); |
| LOG("Immediate reboot strongly recommended\n"); |
| } |
| ::llcpp::fuchsia::device::Controller::Call::Rebind(Channel(), |
| fidl::StringView("/boot/driver/gpt.so")); |
| return ZX_OK; |
| } |
| |
| zx_status_t GptDevicePartitioner::WipeFvm() const { return WipePartitions(IsFvmPartition); } |
| |
| zx_status_t GptDevicePartitioner::WipePartitionTables() const { |
| return WipePartitions([](const gpt_partition_t&) { return true; }); |
| } |
| |
| /*====================================================* |
| * EFI SPECIFIC * |
| *====================================================*/ |
| |
| zx_status_t EfiDevicePartitioner::Initialize(fbl::unique_fd devfs_root, Arch arch, |
| std::optional<fbl::unique_fd> block_device, |
| std::unique_ptr<DevicePartitioner>* partitioner) { |
| if (arch != Arch::kX64) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| std::unique_ptr<GptDevicePartitioner> gpt; |
| zx_status_t status = |
| GptDevicePartitioner::InitializeGpt(std::move(devfs_root), std::move(block_device), &gpt); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| LOG("Successfully initialized EFI Device Partitioner\n"); |
| *partitioner = WrapUnique(new EfiDevicePartitioner(arch, std::move(gpt))); |
| return ZX_OK; |
| } |
| |
| bool EfiDevicePartitioner::SupportsPartition(const PartitionSpec& spec) const { |
| const PartitionSpec supported_specs[] = {PartitionSpec(paver::Partition::kBootloader), |
| PartitionSpec(paver::Partition::kZirconA), |
| PartitionSpec(paver::Partition::kZirconB), |
| PartitionSpec(paver::Partition::kZirconR), |
| PartitionSpec(paver::Partition::kVbMetaA), |
| PartitionSpec(paver::Partition::kVbMetaB), |
| PartitionSpec(paver::Partition::kVbMetaR), |
| PartitionSpec(paver::Partition::kAbrMeta), |
| PartitionSpec(paver::Partition::kFuchsiaVolumeManager)}; |
| |
| for (const auto& supported : supported_specs) { |
| if (SpecMatches(spec, supported)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| zx_status_t EfiDevicePartitioner::AddPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // NOTE: If you update the minimum sizes of partitions, please update the |
| // EfiDevicePartitionerTests.InitPartitionTables test. |
| size_t minimum_size_bytes = 0; |
| switch (spec.partition) { |
| case Partition::kBootloader: |
| minimum_size_bytes = 16 * kMebibyte; |
| break; |
| case Partition::kZirconA: |
| minimum_size_bytes = 128 * kMebibyte; |
| break; |
| case Partition::kZirconB: |
| minimum_size_bytes = 128 * kMebibyte; |
| break; |
| case Partition::kZirconR: |
| minimum_size_bytes = 192 * kMebibyte; |
| break; |
| case Partition::kVbMetaA: |
| minimum_size_bytes = 64 * kKibibyte; |
| break; |
| case Partition::kVbMetaB: |
| minimum_size_bytes = 64 * kKibibyte; |
| break; |
| case Partition::kVbMetaR: |
| minimum_size_bytes = 64 * kKibibyte; |
| break; |
| case Partition::kAbrMeta: |
| minimum_size_bytes = 4 * kKibibyte; |
| break; |
| case Partition::kFuchsiaVolumeManager: |
| minimum_size_bytes = 16 * kGibibyte; |
| break; |
| default: |
| ERROR("EFI partitioner cannot add unknown partition type\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| const char* name = PartitionName(spec.partition); |
| uint8_t type[GPT_GUID_LEN]; |
| zx_status_t status = GptPartitionType(spec.partition, type); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return gpt_->AddPartition(name, type, minimum_size_bytes, /*optional_reserve_bytes*/ 0, |
| out_partition); |
| } |
| |
| zx_status_t EfiDevicePartitioner::FindPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| switch (spec.partition) { |
| case Partition::kBootloader: { |
| return gpt_->FindPartition(IsGigabootPartition, out_partition); |
| } |
| case Partition::kZirconA: |
| case Partition::kZirconB: |
| case Partition::kZirconR: |
| case Partition::kVbMetaA: |
| case Partition::kVbMetaB: |
| case Partition::kVbMetaR: |
| case Partition::kAbrMeta: { |
| const auto filter = [&spec](const gpt_partition_t& part) { |
| uint8_t type[GPT_GUID_LEN]; |
| return GptPartitionType(spec.partition, type) == ZX_OK && FilterByType(part, type); |
| }; |
| return gpt_->FindPartition(filter, out_partition); |
| } |
| case Partition::kFuchsiaVolumeManager: |
| return gpt_->FindPartition(IsFvmPartition, out_partition); |
| default: |
| ERROR("EFI partitioner cannot find unknown partition type\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t EfiDevicePartitioner::FinalizePartition(const PartitionSpec& spec) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return gpt_->GetGpt()->Sync(); |
| } |
| |
| zx_status_t EfiDevicePartitioner::WipeFvm() const { return gpt_->WipeFvm(); } |
| |
| zx_status_t EfiDevicePartitioner::InitPartitionTables() const { |
| const std::array<Partition, 9> partitions_to_add{ |
| Partition::kBootloader, Partition::kZirconA, Partition::kZirconB, |
| Partition::kZirconR, Partition::kVbMetaA, Partition::kVbMetaB, |
| Partition::kVbMetaR, Partition::kAbrMeta, Partition::kFuchsiaVolumeManager, |
| }; |
| |
| // Wipe partitions. |
| // EfiDevicePartitioner operates on partition types. |
| zx_status_t status = gpt_->WipePartitions([&partitions_to_add](const gpt_partition_t& part) { |
| for (auto& partition : partitions_to_add) { |
| // Get the partition type GUID, and compare it. |
| std::array<uint8_t, GPT_GUID_LEN> type{}; |
| zx_status_t status = GptPartitionType(partition, type.data()); |
| if (status != ZX_OK || memcmp(part.type, type.data(), GPT_GUID_LEN) != 0) { |
| continue; |
| } |
| // If we are wiping any non-bootloader partition, we are done. |
| if (partition != Partition::kBootloader) { |
| return true; |
| } |
| // If we are wiping the bootloader partition, only do so if it is the |
| // Fuchsia-installed bootloader partition. This is to allow dual-booting. |
| char cstring_name[GPT_NAME_LEN] = {}; |
| utf16_to_cstring(cstring_name, part.name, GPT_NAME_LEN); |
| if (strncmp(cstring_name, GUID_EFI_NAME, GPT_NAME_LEN) == 0) { |
| return true; |
| } |
| } |
| return false; |
| }); |
| if (status != ZX_OK) { |
| ERROR("Failed to wipe partitions: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Add partitions with default content_type. |
| for (auto type : partitions_to_add) { |
| status = AddPartition(PartitionSpec(type), nullptr); |
| if (status == ZX_ERR_ALREADY_BOUND) { |
| ERROR("Warning: Skipping existing partition \"%s\"\n", PartitionName(type)); |
| } else if (status != ZX_OK) { |
| ERROR("Failed to create partition \"%s\": %s\n", PartitionName(type), |
| zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| LOG("Successfully initialized GPT\n"); |
| return ZX_OK; |
| } // namespace paver |
| |
| zx_status_t EfiDevicePartitioner::WipePartitionTables() const { |
| return gpt_->WipePartitionTables(); |
| } |
| |
| zx_status_t EfiDevicePartitioner::ValidatePayload(const PartitionSpec& spec, |
| fbl::Span<const uint8_t> data) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (IsZirconPartitionSpec(spec)) { |
| return IsValidKernelZbi(arch_, data) ? ZX_OK : ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| |
| /*====================================================* |
| * CROS SPECIFIC * |
| *====================================================*/ |
| |
| zx_status_t CrosDevicePartitioner::Initialize(fbl::unique_fd devfs_root, Arch arch, |
| std::optional<fbl::unique_fd> block_device, |
| std::unique_ptr<DevicePartitioner>* partitioner) { |
| if (arch != Arch::kX64) { |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t status = IsBootloader(devfs_root, "coreboot"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| std::unique_ptr<GptDevicePartitioner> gpt_partitioner; |
| status = GptDevicePartitioner::InitializeGpt(std::move(devfs_root), std::move(block_device), |
| &gpt_partitioner); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| GptDevice* gpt = gpt_partitioner->GetGpt(); |
| block::BlockInfo info; |
| gpt_partitioner->GetBlockInfo(&info); |
| |
| if (!is_ready_to_pave(gpt, reinterpret_cast<fuchsia_hardware_block_BlockInfo*>(&info), |
| SZ_ZX_PART)) { |
| status = config_cros_for_fuchsia( |
| gpt, reinterpret_cast<fuchsia_hardware_block_BlockInfo*>(&info), SZ_ZX_PART); |
| if (status != ZX_OK) { |
| ERROR("Failed to configure CrOS for Fuchsia.\n"); |
| return status; |
| } |
| if ((status = gpt->Sync()) != ZX_OK) { |
| ERROR("Failed to sync CrOS for Fuchsia.\n"); |
| return status; |
| } |
| llcpp::fuchsia::device::Controller::Call::Rebind(gpt_partitioner->Channel(), |
| fidl::StringView("/boot/driver/gpt.so")); |
| } |
| |
| LOG("Successfully initialized CrOS Device Partitioner\n"); |
| *partitioner = WrapUnique(new CrosDevicePartitioner(std::move(gpt_partitioner))); |
| return ZX_OK; |
| } |
| |
| bool CrosDevicePartitioner::SupportsPartition(const PartitionSpec& spec) const { |
| const PartitionSpec supported_specs[] = {PartitionSpec(paver::Partition::kZirconA), |
| PartitionSpec(paver::Partition::kZirconB), |
| PartitionSpec(paver::Partition::kZirconR), |
| PartitionSpec(paver::Partition::kFuchsiaVolumeManager)}; |
| |
| for (const auto& supported : supported_specs) { |
| if (SpecMatches(spec, supported)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| zx_status_t CrosDevicePartitioner::AddPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // NOTE: If you update the minimum sizes of partitions, please update the |
| // CrosDevicePartitionerTests.InitPartitionTables test. |
| size_t minimum_size_bytes = 0; |
| switch (spec.partition) { |
| case Partition::kZirconA: |
| minimum_size_bytes = 64 * kMebibyte; |
| break; |
| case Partition::kZirconB: |
| minimum_size_bytes = 64 * kMebibyte; |
| break; |
| case Partition::kZirconR: |
| // NOTE(abdulla): is_ready_to_pave() is called with SZ_ZX_PART, which requires all kernel |
| // partitions to be the same size. |
| minimum_size_bytes = 64 * kMebibyte; |
| break; |
| case Partition::kFuchsiaVolumeManager: |
| minimum_size_bytes = 16 * kGibibyte; |
| break; |
| default: |
| ERROR("Cros partitioner cannot add unknown partition type\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| const char* name = PartitionName(spec.partition); |
| uint8_t type[GPT_GUID_LEN]; |
| zx_status_t status = CrosPartitionType(spec.partition, type); |
| if (status != ZX_OK) { |
| return status; |
| } |
| return gpt_->AddPartition(name, type, minimum_size_bytes, /*optional_reserve_bytes*/ 0, |
| out_partition); |
| } |
| |
| zx_status_t CrosDevicePartitioner::FindPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| switch (spec.partition) { |
| case Partition::kZirconA: |
| case Partition::kZirconB: |
| case Partition::kZirconR: { |
| const auto filter = [&spec](const gpt_partition_t& part) { |
| const char* name = PartitionName(spec.partition); |
| uint8_t type[GPT_GUID_LEN]; |
| return CrosPartitionType(spec.partition, type) == ZX_OK && |
| FilterByTypeAndName(part, type, name); |
| }; |
| return gpt_->FindPartition(filter, out_partition); |
| } |
| case Partition::kFuchsiaVolumeManager: |
| return gpt_->FindPartition(IsFvmPartition, out_partition); |
| default: |
| ERROR("Cros partitioner cannot find unknown partition type\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t CrosDevicePartitioner::FinalizePartition(const PartitionSpec& spec) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| // Special partition finalization is only necessary for Zircon partitions. |
| if (spec.partition != Partition::kZirconA) { |
| return ZX_OK; |
| } |
| |
| // Find the Zircon A kernel partition. |
| const uint8_t cros_kernel_type[GPT_GUID_LEN] = GUID_CROS_KERNEL_VALUE; |
| const char* name = PartitionName(Partition::kZirconA); |
| const auto filter = [cros_kernel_type, name](const gpt_partition_t& part) { |
| return FilterByTypeAndName(part, cros_kernel_type, name); |
| }; |
| gpt_partition_t* zircon_a_partition; |
| zx_status_t status = gpt_->FindPartition(filter, nullptr, &zircon_a_partition); |
| if (status != ZX_OK) { |
| ERROR("Cannot find %s partition\n", name); |
| return status; |
| } |
| |
| // Find the highest priority kernel partition. |
| uint8_t top_priority = 0; |
| for (uint32_t i = 0; i < gpt::kPartitionCount; ++i) { |
| const gpt_partition_t* part = gpt_->GetGpt()->GetPartition(i); |
| if (part == NULL) { |
| continue; |
| } |
| const uint8_t priority = gpt_cros_attr_get_priority(part->flags); |
| // Ignore anything not of type CROS KERNEL. |
| if (memcmp(part->type, cros_kernel_type, GPT_GUID_LEN)) { |
| continue; |
| } |
| |
| // Ignore ourself. |
| if (part == zircon_a_partition) { |
| continue; |
| } |
| |
| if (priority > top_priority) { |
| top_priority = priority; |
| } |
| } |
| |
| // Priority for Zircon A set to higher priority than all other kernels. |
| if (top_priority == UINT8_MAX) { |
| ERROR("Cannot set CrOS partition priority higher than other kernels\n"); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| int ret = gpt_cros_attr_set_priority(&zircon_a_partition->flags, |
| static_cast<uint8_t>(top_priority + 1)); |
| if (ret != 0) { |
| ERROR("Cannot set CrOS partition priority for ZIRCON-A\n"); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| |
| // TODO(raggi): when other (B/R) partitions are paved, set their priority |
| // appropriately as well. |
| |
| // Successful set to 'true' to encourage the bootloader to |
| // use this partition. |
| gpt_cros_attr_set_successful(&zircon_a_partition->flags, true); |
| // Maximize the number of attempts to boot this partition before |
| // we fall back to a different kernel. |
| ret = gpt_cros_attr_set_tries(&zircon_a_partition->flags, 15); |
| if (ret != 0) { |
| ERROR("Cannot set CrOS partition 'tries' for ZIRCON-A\n"); |
| return ZX_ERR_OUT_OF_RANGE; |
| } |
| status = gpt_->GetGpt()->Sync(); |
| if (status != ZX_OK) { |
| ERROR("Failed to sync CrOS partition 'tries' for ZIRCON-A\n"); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CrosDevicePartitioner::WipeFvm() const { return gpt_->WipeFvm(); } |
| |
| zx_status_t CrosDevicePartitioner::InitPartitionTables() const { |
| // Wipe partitions. |
| // CrosDevicePartitioner operates on partition names. |
| const std::set<std::string_view> partitions_to_wipe{ |
| GUID_ZIRCON_A_NAME, |
| GUID_ZIRCON_B_NAME, |
| GUID_ZIRCON_R_NAME, |
| GUID_FVM_NAME, |
| // These additional partition names are based on the previous naming scheme. |
| "ZIRCON-A", |
| "ZIRCON-B", |
| "ZIRCON-R", |
| "fvm", |
| }; |
| zx_status_t status = gpt_->WipePartitions([&partitions_to_wipe](const gpt_partition_t& part) { |
| char cstring_name[GPT_NAME_LEN] = {}; |
| utf16_to_cstring(cstring_name, part.name, GPT_NAME_LEN); |
| return partitions_to_wipe.find(cstring_name) != partitions_to_wipe.end(); |
| }); |
| if (status != ZX_OK) { |
| ERROR("Failed to wipe partitions: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Add partitions with default content type. |
| const std::array<PartitionSpec, 4> partitions_to_add = { |
| PartitionSpec(Partition::kZirconA), |
| PartitionSpec(Partition::kZirconB), |
| PartitionSpec(Partition::kZirconR), |
| PartitionSpec(Partition::kFuchsiaVolumeManager), |
| }; |
| for (auto spec : partitions_to_add) { |
| status = AddPartition(spec, nullptr); |
| if (status != ZX_OK) { |
| ERROR("Failed to create partition \"%s\": %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| LOG("Successfully initialized GPT\n"); |
| return ZX_OK; |
| } |
| |
| zx_status_t CrosDevicePartitioner::WipePartitionTables() const { |
| return gpt_->WipePartitionTables(); |
| } |
| |
| zx_status_t CrosDevicePartitioner::ValidatePayload(const PartitionSpec& spec, |
| fbl::Span<const uint8_t> data) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| if (IsZirconPartitionSpec(spec)) { |
| return IsValidChromeOSKernel(data) ? ZX_OK : ZX_ERR_BAD_STATE; |
| } |
| |
| return ZX_OK; |
| } |
| |
| /*====================================================* |
| * FIXED PARTITION MAP * |
| *====================================================*/ |
| |
| zx_status_t FixedDevicePartitioner::Initialize(fbl::unique_fd devfs_root, |
| std::unique_ptr<DevicePartitioner>* partitioner) { |
| if (HasSkipBlockDevice(devfs_root)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| LOG("Successfully initialized FixedDevicePartitioner Device Partitioner\n"); |
| *partitioner = WrapUnique(new FixedDevicePartitioner(std::move(devfs_root))); |
| return ZX_OK; |
| } |
| |
| bool FixedDevicePartitioner::SupportsPartition(const PartitionSpec& spec) const { |
| const PartitionSpec supported_specs[] = {PartitionSpec(paver::Partition::kBootloader), |
| PartitionSpec(paver::Partition::kZirconA), |
| PartitionSpec(paver::Partition::kZirconB), |
| PartitionSpec(paver::Partition::kZirconR), |
| PartitionSpec(paver::Partition::kVbMetaA), |
| PartitionSpec(paver::Partition::kVbMetaB), |
| PartitionSpec(paver::Partition::kVbMetaR), |
| PartitionSpec(paver::Partition::kAbrMeta), |
| PartitionSpec(paver::Partition::kFuchsiaVolumeManager)}; |
| |
| for (const auto& supported : supported_specs) { |
| if (SpecMatches(spec, supported)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| zx_status_t FixedDevicePartitioner::AddPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| ERROR("Cannot add partitions to a fixed-map partition device\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t FixedDevicePartitioner::FindPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| uint8_t type[GPT_GUID_LEN]; |
| |
| switch (spec.partition) { |
| case Partition::kBootloader: { |
| const uint8_t bootloader_type[GPT_GUID_LEN] = GUID_BOOTLOADER_VALUE; |
| memcpy(type, bootloader_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kZirconA: { |
| const uint8_t zircon_a_type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE; |
| memcpy(type, zircon_a_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kZirconB: { |
| const uint8_t zircon_b_type[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE; |
| memcpy(type, zircon_b_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kZirconR: { |
| const uint8_t zircon_r_type[GPT_GUID_LEN] = GUID_ZIRCON_R_VALUE; |
| memcpy(type, zircon_r_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kVbMetaA: { |
| const uint8_t vbmeta_a_type[GPT_GUID_LEN] = GUID_VBMETA_A_VALUE; |
| memcpy(type, vbmeta_a_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kVbMetaB: { |
| const uint8_t vbmeta_b_type[GPT_GUID_LEN] = GUID_VBMETA_B_VALUE; |
| memcpy(type, vbmeta_b_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kVbMetaR: { |
| const uint8_t vbmeta_r_type[GPT_GUID_LEN] = GUID_VBMETA_R_VALUE; |
| memcpy(type, vbmeta_r_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kAbrMeta: { |
| const uint8_t abr_meta_type[GPT_GUID_LEN] = GUID_ABR_META_VALUE; |
| memcpy(type, abr_meta_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kFuchsiaVolumeManager: { |
| const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| memcpy(type, fvm_type, GPT_GUID_LEN); |
| break; |
| } |
| default: |
| ERROR("partition_type is invalid!\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx::channel chan; |
| zx_status_t status = OpenBlockPartition(devfs_root_, nullptr, type, ZX_SEC(5), &chan); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| out_partition->reset(new BlockPartitionClient(std::move(chan))); |
| return ZX_OK; |
| } |
| |
| zx_status_t FixedDevicePartitioner::WipeFvm() const { |
| const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| zx_status_t status; |
| if ((status = WipeBlockPartition(devfs_root_, nullptr, fvm_type)) != ZX_OK) { |
| ERROR("Failed to wipe FVM.\n"); |
| } else { |
| LOG("Wiped FVM successfully.\n"); |
| } |
| LOG("Immediate reboot strongly recommended\n"); |
| return ZX_OK; |
| } |
| |
| zx_status_t FixedDevicePartitioner::InitPartitionTables() const { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t FixedDevicePartitioner::WipePartitionTables() const { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t FixedDevicePartitioner::ValidatePayload(const PartitionSpec& spec, |
| fbl::Span<const uint8_t> data) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| /*====================================================* |
| * SHERLOCK * |
| *====================================================*/ |
| |
| zx_status_t SherlockPartitioner::Initialize(fbl::unique_fd devfs_root, |
| std::optional<fbl::unique_fd> block_device, |
| std::unique_ptr<DevicePartitioner>* partitioner) { |
| zx_status_t status = IsBoard(devfs_root, "sherlock"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| std::unique_ptr<GptDevicePartitioner> gpt; |
| status = |
| GptDevicePartitioner::InitializeGpt(std::move(devfs_root), std::move(block_device), &gpt); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| LOG("Successfully initialized SherlockPartitioner Device Partitioner\n"); |
| *partitioner = WrapUnique(new SherlockPartitioner(std::move(gpt))); |
| return ZX_OK; |
| } |
| |
| // Sherlock bootloader types: |
| // |
| // -- default [deprecated] -- |
| // The combined BL2 + TPL image. |
| // |
| // This was never actually added to any update packages, because older |
| // SherlockBootloaderPartitionClient implementations had a bug where they would |
| // write this image to the wrong place in flash which would overwrite critical |
| // metadata and brick the device on reboot. |
| // |
| // In order to prevent this from happening when updating older devices, never |
| // use this bootloader type on Sherlock. |
| // |
| // -- "skip_metadata" -- |
| // The combined BL2 + TPL image. |
| // |
| // The image itself is identical to the default, but adding the "skip_metadata" |
| // type ensures that older pavers will ignore this image, and only newer |
| // implementations which properly skip the metadata section will write it. |
| bool SherlockPartitioner::SupportsPartition(const PartitionSpec& spec) const { |
| const PartitionSpec supported_specs[] = { |
| PartitionSpec(paver::Partition::kBootloader, "skip_metadata"), |
| PartitionSpec(paver::Partition::kZirconA), |
| PartitionSpec(paver::Partition::kZirconB), |
| PartitionSpec(paver::Partition::kZirconR), |
| PartitionSpec(paver::Partition::kVbMetaA), |
| PartitionSpec(paver::Partition::kVbMetaB), |
| PartitionSpec(paver::Partition::kVbMetaR), |
| PartitionSpec(paver::Partition::kAbrMeta), |
| PartitionSpec(paver::Partition::kFuchsiaVolumeManager)}; |
| |
| for (const auto& supported : supported_specs) { |
| if (SpecMatches(spec, supported)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| zx_status_t SherlockPartitioner::AddPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| ERROR("Cannot add partitions to a sherlock device\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t SherlockPartitioner::FindPartition( |
| const PartitionSpec& spec, std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| uint8_t type[GPT_GUID_LEN]; |
| |
| switch (spec.partition) { |
| case Partition::kBootloader: { |
| const uint8_t boot0_type[GPT_GUID_LEN] = GUID_EMMC_BOOT1_VALUE; |
| zx::channel chan; |
| zx_status_t status = |
| OpenBlockPartition(gpt_->devfs_root(), nullptr, boot0_type, ZX_SEC(5), &chan); |
| if (status != ZX_OK) { |
| return status; |
| } |
| auto boot0 = std::make_unique<SherlockBootloaderPartitionClient>(std::move(chan)); |
| |
| const uint8_t boot1_type[GPT_GUID_LEN] = GUID_EMMC_BOOT2_VALUE; |
| status = OpenBlockPartition(gpt_->devfs_root(), nullptr, boot1_type, ZX_SEC(5), &chan); |
| if (status != ZX_OK) { |
| return status; |
| } |
| auto boot1 = std::make_unique<SherlockBootloaderPartitionClient>(std::move(chan)); |
| |
| std::vector<std::unique_ptr<PartitionClient>> partitions; |
| partitions.push_back(std::move(boot0)); |
| partitions.push_back(std::move(boot1)); |
| *out_partition = std::make_unique<PartitionCopyClient>(std::move(partitions)); |
| |
| return ZX_OK; |
| } |
| case Partition::kZirconA: { |
| const uint8_t zircon_a_type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE; |
| memcpy(type, zircon_a_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kZirconB: { |
| const uint8_t zircon_b_type[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE; |
| memcpy(type, zircon_b_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kZirconR: { |
| const uint8_t zircon_r_type[GPT_GUID_LEN] = GUID_ZIRCON_R_VALUE; |
| memcpy(type, zircon_r_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kVbMetaA: { |
| const uint8_t vbmeta_a_type[GPT_GUID_LEN] = GUID_VBMETA_A_VALUE; |
| memcpy(type, vbmeta_a_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kVbMetaB: { |
| const uint8_t vbmeta_b_type[GPT_GUID_LEN] = GUID_VBMETA_B_VALUE; |
| memcpy(type, vbmeta_b_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kVbMetaR: { |
| const uint8_t vbmeta_r_type[GPT_GUID_LEN] = GUID_VBMETA_R_VALUE; |
| memcpy(type, vbmeta_r_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kAbrMeta: { |
| const uint8_t abr_meta_type[GPT_GUID_LEN] = GUID_ABR_META_VALUE; |
| memcpy(type, abr_meta_type, GPT_GUID_LEN); |
| break; |
| } |
| case Partition::kFuchsiaVolumeManager: { |
| const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| memcpy(type, fvm_type, GPT_GUID_LEN); |
| break; |
| } |
| default: |
| ERROR("Partition type is invalid\n"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| const auto filter = [type](const gpt_partition_t& part) { |
| return memcmp(part.type, type, GPT_GUID_LEN) == 0; |
| }; |
| return gpt_->FindPartition(std::move(filter), out_partition); |
| } |
| |
| zx_status_t SherlockPartitioner::WipeFvm() const { return gpt_->WipeFvm(); } |
| |
| zx_status_t SherlockPartitioner::InitPartitionTables() const { |
| struct Partition { |
| const char* name; |
| uint8_t type[GPT_GUID_LEN]; |
| size_t min_size; |
| }; |
| |
| const auto add_partitions = [&](fbl::Span<const Partition> partitions) { |
| for (const auto& part : partitions) { |
| std::unique_ptr<PartitionClient> out; |
| if (auto status = gpt_->AddPartition(part.name, part.type, part.min_size, 0, &out); |
| status != ZX_OK) { |
| return status; |
| } |
| } |
| return ZX_OK; |
| }; |
| |
| const char* partitions_to_wipe[] = { |
| "recovery", |
| "boot", |
| "system", |
| "fvm", |
| GUID_FVM_NAME, |
| "cache", |
| "fct", |
| GUID_SYS_CONFIG_NAME, |
| GUID_ABR_META_NAME, |
| GUID_VBMETA_A_NAME, |
| GUID_VBMETA_B_NAME, |
| GUID_VBMETA_R_NAME, |
| "migration", |
| "buf", |
| "buffer", |
| }; |
| const auto wipe = [&partitions_to_wipe](const gpt_partition_t& part) { |
| char cstring_name[GPT_NAME_LEN] = {}; |
| utf16_to_cstring(cstring_name, part.name, GPT_NAME_LEN); |
| |
| for (const auto& partition_name : fbl::Span(partitions_to_wipe)) { |
| if (strncmp(cstring_name, partition_name, GPT_NAME_LEN) == 0) { |
| return true; |
| } |
| } |
| return false; |
| }; |
| |
| if (auto status = gpt_->WipePartitions(wipe); status != ZX_OK) { |
| return status; |
| } |
| |
| const Partition partitions_to_add[] = { |
| { |
| "recovery", |
| GUID_ZIRCON_R_VALUE, |
| 32 * kMebibyte, |
| }, |
| { |
| "boot", |
| GUID_ZIRCON_A_VALUE, |
| 32 * kMebibyte, |
| }, |
| { |
| "system", |
| GUID_ZIRCON_B_VALUE, |
| 32 * kMebibyte, |
| }, |
| { |
| GUID_FVM_NAME, |
| GUID_FVM_VALUE, |
| 3280 * kMebibyte, |
| }, |
| { |
| "fct", |
| GUID_AMLOGIC_VALUE, |
| 64 * kMebibyte, |
| }, |
| { |
| GUID_SYS_CONFIG_NAME, |
| GUID_SYS_CONFIG_VALUE, |
| 828 * kKibibyte, |
| }, |
| { |
| GUID_ABR_META_NAME, |
| GUID_ABR_META_VALUE, |
| 4 * kKibibyte, |
| }, |
| { |
| GUID_VBMETA_A_NAME, |
| GUID_VBMETA_A_VALUE, |
| 64 * kKibibyte, |
| }, |
| { |
| GUID_VBMETA_B_NAME, |
| GUID_VBMETA_B_VALUE, |
| 64 * kKibibyte, |
| }, |
| { |
| GUID_VBMETA_R_NAME, |
| GUID_VBMETA_R_VALUE, |
| 64 * kKibibyte, |
| }, |
| { |
| "migration", |
| GUID_AMLOGIC_VALUE, |
| 7 * kMebibyte, |
| }, |
| { |
| "buffer", |
| GUID_AMLOGIC_VALUE, |
| 48 * kMebibyte, |
| }, |
| }; |
| |
| if (auto status = add_partitions(fbl::Span(partitions_to_add)); status != ZX_OK) { |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SherlockPartitioner::WipePartitionTables() const { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t SherlockPartitioner::ValidatePayload(const PartitionSpec& spec, |
| fbl::Span<const uint8_t> data) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| /*====================================================* |
| * SKIP BLOCK SPECIFIC * |
| *====================================================*/ |
| |
| zx_status_t SkipBlockDevicePartitioner::FindPartition( |
| const uint8_t* type, std::unique_ptr<PartitionClient>* out_partition) const { |
| zx::channel chan; |
| zx_status_t status = OpenSkipBlockPartition(devfs_root_, type, ZX_SEC(5), &chan); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| out_partition->reset(new SkipBlockPartitionClient(std::move(chan))); |
| return ZX_OK; |
| } |
| |
| zx_status_t SkipBlockDevicePartitioner::FindFvmPartition( |
| std::unique_ptr<PartitionClient>* out_partition) const { |
| const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| // FVM partition is managed so it should expose a normal block device. |
| zx::channel chan; |
| zx_status_t status = OpenBlockPartition(devfs_root_, nullptr, fvm_type, ZX_SEC(5), &chan); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| out_partition->reset(new BlockPartitionClient(std::move(chan))); |
| return ZX_OK; |
| } |
| |
| zx_status_t SkipBlockDevicePartitioner::WipeFvm() const { |
| const uint8_t fvm_type[GPT_GUID_LEN] = GUID_FVM_VALUE; |
| zx::channel chan; |
| zx_status_t status = OpenBlockPartition(devfs_root_, nullptr, fvm_type, ZX_SEC(3), &chan); |
| if (status != ZX_OK) { |
| ERROR("Warning: Could not open partition to wipe: %s\n", zx_status_get_string(status)); |
| return ZX_OK; |
| } |
| |
| device::Controller::SyncClient block_client(std::move(chan)); |
| |
| auto result = block_client.GetTopologicalPath(); |
| if (!result.ok()) { |
| ERROR("Warning: Could not get name for partition: %s\n", zx_status_get_string(result.status())); |
| return result.status(); |
| } |
| const auto& response = result.value(); |
| if (response.result.is_err()) { |
| ERROR("Warning: Could not get name for partition: %s\n", |
| zx_status_get_string(response.result.err())); |
| return response.result.err(); |
| } |
| |
| fbl::StringBuffer<PATH_MAX> name_buffer; |
| name_buffer.Append(response.result.response().path.data(), |
| static_cast<size_t>(response.result.response().path.size())); |
| |
| status = UnbindFvm(devfs_root_, name_buffer.data()); |
| if (status != ZX_OK) { |
| // The driver may refuse to bind to a corrupt volume. |
| ERROR("Warning: Failed to unbind FVM: %s\n", zx_status_get_string(status)); |
| } |
| |
| // TODO(39761): Clean this up. |
| const char* parent = dirname(name_buffer.data()); |
| constexpr char kDevRoot[] = "/dev/"; |
| constexpr size_t kDevRootLen = sizeof(kDevRoot) - 1; |
| if (strncmp(parent, kDevRoot, kDevRootLen) != 0) { |
| ERROR("Warning: Unrecognized partition name: %s\n", parent); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| parent += kDevRootLen; |
| |
| zx::channel local, remote; |
| status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| ERROR("Warning: Failed to create channel pair: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| fdio_cpp::UnownedFdioCaller caller(devfs_root_.get()); |
| status = fdio_service_connect_at(caller.borrow_channel(), parent, remote.release()); |
| if (status != ZX_OK) { |
| ERROR("Warning: Unable to open block parent device: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| block::Ftl::SyncClient client(std::move(local)); |
| auto result2 = client.Format(); |
| |
| return result2.ok() ? result2.value().status : result2.status(); |
| } |
| |
| zx_status_t AstroPartitioner::Initialize(fbl::unique_fd devfs_root, |
| std::unique_ptr<DevicePartitioner>* partitioner) { |
| zx_status_t status = IsBoard(devfs_root, "astro"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| LOG("Successfully initialized AstroPartitioner Device Partitioner\n"); |
| std::unique_ptr<SkipBlockDevicePartitioner> skip_block( |
| new SkipBlockDevicePartitioner(std::move(devfs_root))); |
| |
| *partitioner = WrapUnique(new AstroPartitioner(std::move(skip_block))); |
| return ZX_OK; |
| } |
| |
| bool AstroPartitioner::SupportsPartition(const PartitionSpec& spec) const { |
| // TODO(35747): add support for new bootloader content_type to differentiate |
| // between BL2 and TPL images. |
| const PartitionSpec supported_specs[] = {PartitionSpec(paver::Partition::kBootloader), |
| PartitionSpec(paver::Partition::kZirconA), |
| PartitionSpec(paver::Partition::kZirconB), |
| PartitionSpec(paver::Partition::kZirconR), |
| PartitionSpec(paver::Partition::kVbMetaA), |
| PartitionSpec(paver::Partition::kVbMetaB), |
| PartitionSpec(paver::Partition::kVbMetaR), |
| PartitionSpec(paver::Partition::kAbrMeta), |
| PartitionSpec(paver::Partition::kFuchsiaVolumeManager)}; |
| |
| for (const auto& supported : supported_specs) { |
| if (SpecMatches(spec, supported)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| zx_status_t AstroPartitioner::AddPartition(const PartitionSpec& spec, |
| std::unique_ptr<PartitionClient>* out_partition) const { |
| ERROR("Cannot add partitions to an astro.\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t AstroPartitioner::FindPartition(const PartitionSpec& spec, |
| std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| switch (spec.partition) { |
| case Partition::kBootloader: { |
| const uint8_t bl2_type[GPT_GUID_LEN] = GUID_BL2_VALUE; |
| std::unique_ptr<PartitionClient> bl2_skip_block; |
| if (auto status = skip_block_->FindPartition(bl2_type, &bl2_skip_block); status != ZX_OK) { |
| return status; |
| } |
| // Upgrade this into a more specialized partition client. |
| auto bl2 = std::make_unique<Bl2PartitionClient>(bl2_skip_block->GetChannel()); |
| |
| const uint8_t tpl_type[GPT_GUID_LEN] = GUID_BOOTLOADER_VALUE; |
| std::unique_ptr<PartitionClient> tpl; |
| if (auto status = skip_block_->FindPartition(tpl_type, &tpl); status != ZX_OK) { |
| return status; |
| } |
| |
| *out_partition = |
| std::make_unique<AstroBootloaderPartitionClient>(std::move(bl2), std::move(tpl)); |
| return ZX_OK; |
| } |
| case Partition::kZirconA: { |
| const uint8_t zircon_a_type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE; |
| return skip_block_->FindPartition(zircon_a_type, out_partition); |
| } |
| case Partition::kZirconB: { |
| const uint8_t zircon_b_type[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE; |
| return skip_block_->FindPartition(zircon_b_type, out_partition); |
| } |
| case Partition::kZirconR: { |
| const uint8_t zircon_r_type[GPT_GUID_LEN] = GUID_ZIRCON_R_VALUE; |
| return skip_block_->FindPartition(zircon_r_type, out_partition); |
| } |
| case Partition::kVbMetaA: |
| case Partition::kVbMetaB: |
| case Partition::kVbMetaR: |
| case Partition::kAbrMeta: { |
| const auto type = [&]() { |
| switch (spec.partition) { |
| case Partition::kVbMetaA: |
| return sysconfig::SyncClient::PartitionType::kVerifiedBootMetadataA; |
| case Partition::kVbMetaB: |
| return sysconfig::SyncClient::PartitionType::kVerifiedBootMetadataB; |
| case Partition::kVbMetaR: |
| return sysconfig::SyncClient::PartitionType::kVerifiedBootMetadataR; |
| case Partition::kAbrMeta: |
| return sysconfig::SyncClient::PartitionType::kABRMetadata; |
| default: |
| break; |
| } |
| ZX_ASSERT(false); |
| }(); |
| std::optional<sysconfig::SyncClient> client; |
| zx_status_t status = sysconfig::SyncClient::Create(skip_block_->devfs_root(), &client); |
| if (status != ZX_OK) { |
| return status; |
| } |
| out_partition->reset(new SysconfigPartitionClient(*std::move(client), type)); |
| return ZX_OK; |
| } |
| case Partition::kFuchsiaVolumeManager: { |
| return skip_block_->FindFvmPartition(out_partition); |
| } |
| default: |
| ERROR("partition_type is invalid!\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t AstroPartitioner::WipeFvm() const { return skip_block_->WipeFvm(); } |
| |
| zx_status_t AstroPartitioner::InitPartitionTables() const { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t AstroPartitioner::WipePartitionTables() const { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t AstroPartitioner::ValidatePayload(const PartitionSpec& spec, |
| fbl::Span<const uint8_t> data) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t As370Partitioner::Initialize(fbl::unique_fd devfs_root, |
| std::unique_ptr<DevicePartitioner>* partitioner) { |
| zx_status_t status = IsBoard(devfs_root, "visalia"); |
| if (status != ZX_OK) { |
| return status; |
| } |
| LOG("Successfully initialized As370Partitioner Device Partitioner\n"); |
| |
| std::unique_ptr<SkipBlockDevicePartitioner> skip_block( |
| new SkipBlockDevicePartitioner(std::move(devfs_root))); |
| *partitioner = WrapUnique(new As370Partitioner(std::move(skip_block))); |
| return ZX_OK; |
| } |
| |
| bool As370Partitioner::SupportsPartition(const PartitionSpec& spec) const { |
| const PartitionSpec supported_specs[] = { |
| PartitionSpec(paver::Partition::kBootloader), PartitionSpec(paver::Partition::kZirconA), |
| PartitionSpec(paver::Partition::kZirconB), PartitionSpec(paver::Partition::kZirconR), |
| PartitionSpec(paver::Partition::kFuchsiaVolumeManager)}; |
| |
| for (const auto& supported : supported_specs) { |
| if (SpecMatches(spec, supported)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| zx_status_t As370Partitioner::AddPartition(const PartitionSpec& spec, |
| std::unique_ptr<PartitionClient>* out_partition) const { |
| ERROR("Cannot add partitions to an as370.\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| zx_status_t As370Partitioner::FindPartition(const PartitionSpec& spec, |
| std::unique_ptr<PartitionClient>* out_partition) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| switch (spec.partition) { |
| case Partition::kBootloader: { |
| const uint8_t bootloader_type[GPT_GUID_LEN] = GUID_BOOTLOADER_VALUE; |
| return skip_block_->FindPartition(bootloader_type, out_partition); |
| } |
| case Partition::kZirconA: { |
| const uint8_t zircon_a_type[GPT_GUID_LEN] = GUID_ZIRCON_A_VALUE; |
| return skip_block_->FindPartition(zircon_a_type, out_partition); |
| } |
| case Partition::kZirconB: { |
| const uint8_t zircon_b_type[GPT_GUID_LEN] = GUID_ZIRCON_B_VALUE; |
| return skip_block_->FindPartition(zircon_b_type, out_partition); |
| } |
| case Partition::kZirconR: { |
| const uint8_t zircon_r_type[GPT_GUID_LEN] = GUID_ZIRCON_R_VALUE; |
| return skip_block_->FindPartition(zircon_r_type, out_partition); |
| } |
| case Partition::kFuchsiaVolumeManager: { |
| return skip_block_->FindFvmPartition(out_partition); |
| } |
| default: |
| ERROR("partition_type is invalid!\n"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| zx_status_t As370Partitioner::WipeFvm() const { return skip_block_->WipeFvm(); } |
| |
| zx_status_t As370Partitioner::InitPartitionTables() const { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t As370Partitioner::WipePartitionTables() const { return ZX_ERR_NOT_SUPPORTED; } |
| |
| zx_status_t As370Partitioner::ValidatePayload(const PartitionSpec& spec, |
| fbl::Span<const uint8_t> data) const { |
| if (!SupportsPartition(spec)) { |
| ERROR("Unsupported partition %s\n", spec.ToString().c_str()); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return ZX_OK; |
| } |
| |
| } // namespace paver |