| // 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 "paver.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fidl-async/cpp/bind.h> |
| #include <lib/fidl/epitaph.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/time.h> |
| #include <lib/zx/vmo.h> |
| #include <libgen.h> |
| #include <stddef.h> |
| #include <string.h> |
| #include <zircon/device/block.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| #include <zircon/syscalls.h> |
| |
| #include <memory> |
| #include <string_view> |
| #include <utility> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/span.h> |
| #include <fbl/unique_fd.h> |
| #include <fs-management/fvm.h> |
| #include <fs-management/mount.h> |
| #include <zxcrypt/fdio-volume.h> |
| |
| #include "fvm.h" |
| #include "pave-logging.h" |
| #include "stream-reader.h" |
| #include "vmo-reader.h" |
| |
| #define ZXCRYPT_DRIVER_LIB "/boot/driver/zxcrypt.so" |
| |
| namespace paver { |
| namespace { |
| |
| using ::llcpp::fuchsia::paver::Asset; |
| using ::llcpp::fuchsia::paver::Configuration; |
| using ::llcpp::fuchsia::paver::WriteFirmwareResult; |
| |
| // Get the architecture of the currently running platform. |
| inline constexpr Arch GetCurrentArch() { |
| #if defined(__x86_64__) |
| return Arch::kX64; |
| #elif defined(__aarch64__) |
| return Arch::kArm64; |
| #else |
| #error "Unknown arch" |
| #endif |
| } |
| |
| Partition PartitionType(Configuration configuration, Asset asset) { |
| switch (asset) { |
| case Asset::KERNEL: { |
| switch (configuration) { |
| case Configuration::A: |
| return Partition::kZirconA; |
| case Configuration::B: |
| return Partition::kZirconB; |
| case Configuration::RECOVERY: |
| return Partition::kZirconR; |
| }; |
| break; |
| } |
| case Asset::VERIFIED_BOOT_METADATA: { |
| switch (configuration) { |
| case Configuration::A: |
| return Partition::kVbMetaA; |
| case Configuration::B: |
| return Partition::kVbMetaB; |
| case Configuration::RECOVERY: |
| return Partition::kVbMetaR; |
| }; |
| break; |
| } |
| }; |
| return Partition::kUnknown; |
| } |
| |
| // Best effort attempt to see if payload contents match what is already inside |
| // of the partition. |
| bool CheckIfSame(PartitionClient* partition, const zx::vmo& vmo, size_t payload_size, |
| size_t block_size) { |
| const size_t payload_size_aligned = fbl::round_up(payload_size, block_size); |
| zx::vmo read_vmo; |
| auto status = zx::vmo::create(fbl::round_up(payload_size_aligned, ZX_PAGE_SIZE), 0, &read_vmo); |
| if (status != ZX_OK) { |
| ERROR("Failed to create VMO: %s\n", zx_status_get_string(status)); |
| return false; |
| } |
| |
| if ((status = partition->Read(read_vmo, payload_size_aligned)) != ZX_OK) { |
| return false; |
| } |
| |
| fzl::VmoMapper first_mapper; |
| fzl::VmoMapper second_mapper; |
| |
| status = first_mapper.Map(vmo, 0, 0, ZX_VM_PERM_READ); |
| if (status != ZX_OK) { |
| ERROR("Error mapping vmo: %s\n", zx_status_get_string(status)); |
| return false; |
| } |
| |
| status = second_mapper.Map(read_vmo, 0, 0, ZX_VM_PERM_READ); |
| if (status != ZX_OK) { |
| ERROR("Error mapping vmo: %s\n", zx_status_get_string(status)); |
| return false; |
| } |
| return memcmp(first_mapper.start(), second_mapper.start(), payload_size) == 0; |
| } |
| |
| // Returns a client for the FVM partition. If the FVM volume doesn't exist, a new |
| // volume will be created, without any associated children partitions. |
| zx_status_t GetFvmPartition(const DevicePartitioner& partitioner, |
| std::unique_ptr<PartitionClient>* client) { |
| // FVM doesn't need content type support, use the default. |
| const PartitionSpec spec(Partition::kFuchsiaVolumeManager); |
| zx_status_t status = partitioner.FindPartition(spec, client); |
| if (status != ZX_OK) { |
| if (status != ZX_ERR_NOT_FOUND) { |
| ERROR("Failure looking for FVM partition: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| LOG("Could not find FVM Partition on device. Attemping to add new partition\n"); |
| |
| if ((status = partitioner.AddPartition(spec, client)) != ZX_OK) { |
| ERROR("Failure creating FVM partition: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| } else { |
| LOG("FVM Partition already exists\n"); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t FvmPave(const fbl::unique_fd& devfs_root, const DevicePartitioner& partitioner, |
| std::unique_ptr<fvm::ReaderInterface> payload) { |
| LOG("Paving FVM partition.\n"); |
| std::unique_ptr<PartitionClient> partition; |
| zx_status_t status = GetFvmPartition(partitioner, &partition); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (partitioner.IsFvmWithinFtl()) { |
| LOG("Attempting to format FTL...\n"); |
| status = partitioner.WipeFvm(); |
| if (status != ZX_OK) { |
| ERROR("Failed to format FTL: %s\n", zx_status_get_string(status)); |
| } else { |
| LOG("Formatted partition successfully!\n"); |
| } |
| } |
| LOG("Streaming partitions to FVM...\n"); |
| status = FvmStreamPartitions(devfs_root, std::move(partition), std::move(payload)); |
| if (status != ZX_OK) { |
| ERROR("Failed to stream partitions to FVM: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| LOG("Completed FVM paving successfully\n"); |
| return ZX_OK; |
| } |
| |
| // Formats the FVM partition and returns a channel to the new volume. |
| zx_status_t FormatFvm(const fbl::unique_fd& devfs_root, const DevicePartitioner& partitioner, |
| zx::channel* channel) { |
| std::unique_ptr<PartitionClient> partition; |
| zx_status_t status = GetFvmPartition(partitioner, &partition); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // TODO(39753): Configuration values should come from the build or environment. |
| fvm::sparse_image_t header = {}; |
| header.slice_size = 1 << 20; |
| |
| fbl::unique_fd fvm_fd( |
| FvmPartitionFormat(devfs_root, partition->block_fd(), header, BindOption::Reformat)); |
| if (!fvm_fd) { |
| ERROR("Couldn't format FVM partition\n"); |
| return ZX_ERR_IO; |
| } |
| |
| status = AllocateEmptyPartitions(devfs_root, fvm_fd); |
| if (status != ZX_OK) { |
| ERROR("Couldn't allocate empty partitions\n"); |
| return status; |
| } |
| |
| status = fdio_get_service_handle(fvm_fd.release(), channel->reset_and_get_address()); |
| if (status != ZX_OK) { |
| ERROR("Couldn't get fvm handle\n"); |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| // Reads an image from disk into a vmo. |
| zx_status_t PartitionRead(const DevicePartitioner& partitioner, const PartitionSpec& spec, |
| zx::vmo* out_vmo, size_t* out_vmo_size) { |
| LOG("Reading partition \"%s\".\n", spec.ToString().c_str()); |
| |
| std::unique_ptr<PartitionClient> partition; |
| if (zx_status_t status = partitioner.FindPartition(spec, &partition); status != ZX_OK) { |
| ERROR("Could not find \"%s\" Partition on device: %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| uint64_t partition_size; |
| if (zx_status_t status = partition->GetPartitionSize(&partition_size); status != ZX_OK) { |
| ERROR("Error getting partition \"%s\" size: %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| zx::vmo vmo; |
| if (zx_status_t status = zx::vmo::create(fbl::round_up(partition_size, ZX_PAGE_SIZE), 0, &vmo); |
| status != ZX_OK) { |
| ERROR("Error creating vmo for \"%s\": %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| if (zx_status_t status = partition->Read(vmo, static_cast<size_t>(partition_size)); |
| status != ZX_OK) { |
| ERROR("Error writing partition data for \"%s\": %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| *out_vmo = std::move(vmo); |
| *out_vmo_size = static_cast<size_t>(partition_size); |
| LOG("Completed successfully\n"); |
| return ZX_OK; |
| } |
| |
| zx_status_t ValidatePartitionPayload(const DevicePartitioner& partitioner, |
| const zx::vmo& payload_vmo, size_t payload_size, |
| const PartitionSpec& spec) { |
| fzl::VmoMapper payload_mapper; |
| zx_status_t status = payload_mapper.Map(payload_vmo, 0, 0, ZX_VM_PERM_READ); |
| if (status != ZX_OK) { |
| ERROR("Could not map payload into memory: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| ZX_ASSERT(payload_mapper.size() >= payload_size); |
| |
| auto payload = |
| fbl::Span<const uint8_t>(static_cast<const uint8_t*>(payload_mapper.start()), payload_size); |
| return partitioner.ValidatePayload(spec, payload); |
| } |
| |
| // Paves an image onto the disk. |
| zx_status_t PartitionPave(const DevicePartitioner& partitioner, zx::vmo payload_vmo, |
| size_t payload_size, const PartitionSpec& spec) { |
| LOG("Paving partition \"%s\".\n", spec.ToString().c_str()); |
| |
| // The payload_vmo might be pager-backed. Commit its pages first before using it for |
| // block writes below, to avoid deadlocks in the block server. If all the pages of the |
| // payload_vmo are not in memory, the block server might see a read fault in the midst of |
| // a write. Read faults need to be fulfilled by the block server itself, so it will deadlock. |
| // |
| // TODO(ZX-48145): The caveat with this approach is that we also need to lock these pages to make |
| // sure they don't get evicted after we've committed them here. |
| // |
| // We should investigate if the block server can handle page faults without deadlocking. If we can |
| // support that, the client will not need to worry about re-entrancy in the block server code, and |
| // this ZX_VMO_OP_COMMIT will no longer be needed. |
| zx_status_t status = payload_vmo.op_range(ZX_VMO_OP_COMMIT, 0, payload_size, nullptr, 0); |
| if (status != ZX_OK) { |
| ERROR("Failed to commit payload VMO for partition \"%s\": %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Perform basic safety checking on the partition before we attempt to write it. |
| status = ValidatePartitionPayload(partitioner, payload_vmo, payload_size, spec); |
| if (status != ZX_OK) { |
| ERROR("Failed to validate partition \"%s\": %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Find or create the appropriate partition. |
| std::unique_ptr<PartitionClient> partition; |
| if ((status = partitioner.FindPartition(spec, &partition)) != ZX_OK) { |
| if (status != ZX_ERR_NOT_FOUND) { |
| ERROR("Failure looking for partition \"%s\": %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| |
| LOG("Could not find \"%s\" Partition on device. Attemping to add new partition\n", |
| spec.ToString().c_str()); |
| |
| if ((status = partitioner.AddPartition(spec, &partition)) != ZX_OK) { |
| ERROR("Failure creating partition \"%s\": %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| } else { |
| LOG("Partition \"%s\" already exists\n", spec.ToString().c_str()); |
| } |
| |
| size_t block_size_bytes; |
| if ((status = partition->GetBlockSize(&block_size_bytes)) != ZX_OK) { |
| ERROR("Couldn't get partition \"%s\" block size\n", spec.ToString().c_str()); |
| return status; |
| } |
| |
| if (CheckIfSame(partition.get(), payload_vmo, payload_size, block_size_bytes)) { |
| LOG("Skipping write as partition \"%s\" contents match payload.\n", spec.ToString().c_str()); |
| } else { |
| // Pad payload with 0s to make it block size aligned. |
| if (payload_size % block_size_bytes != 0) { |
| const size_t remaining_bytes = block_size_bytes - (payload_size % block_size_bytes); |
| size_t vmo_size; |
| if ((status = payload_vmo.get_size(&vmo_size)) != ZX_OK) { |
| ERROR("Couldn't get vmo size for \"%s\"\n", spec.ToString().c_str()); |
| return status; |
| } |
| // Grow VMO if it's too small. |
| if (vmo_size < payload_size + remaining_bytes) { |
| const auto new_size = fbl::round_up(payload_size + remaining_bytes, ZX_PAGE_SIZE); |
| status = payload_vmo.set_size(new_size); |
| if (status != ZX_OK) { |
| ERROR("Couldn't grow vmo for \"%s\"\n", spec.ToString().c_str()); |
| return status; |
| } |
| } |
| auto buffer = std::make_unique<uint8_t[]>(remaining_bytes); |
| memset(buffer.get(), 0, remaining_bytes); |
| status = payload_vmo.write(buffer.get(), payload_size, remaining_bytes); |
| if (status != ZX_OK) { |
| ERROR("Failed to write padding to vmo for \"%s\"\n", spec.ToString().c_str()); |
| return status; |
| } |
| payload_size += remaining_bytes; |
| } |
| if ((status = partition->Write(payload_vmo, payload_size)) != ZX_OK) { |
| ERROR("Error writing partition \"%s\" data: %s\n", spec.ToString().c_str(), |
| zx_status_get_string(status)); |
| return status; |
| } |
| } |
| |
| if ((status = partitioner.FinalizePartition(spec)) != ZX_OK) { |
| ERROR("Failed to finalize partition \"%s\"\n", spec.ToString().c_str()); |
| return status; |
| } |
| |
| LOG("Completed paving partition \"%s\" successfully\n", spec.ToString().c_str()); |
| return ZX_OK; |
| } |
| |
| zx::channel OpenServiceRoot() { |
| zx::channel request, service_root; |
| if (zx::channel::create(0, &request, &service_root) != ZX_OK) { |
| return zx::channel(); |
| } |
| if (fdio_service_connect("/svc/.", request.release()) != ZX_OK) { |
| return zx::channel(); |
| } |
| return service_root; |
| } |
| |
| std::optional<Configuration> SlotIndexToConfiguration(AbrSlotIndex slot_index) { |
| switch (slot_index) { |
| case kAbrSlotIndexA: |
| return Configuration::A; |
| case kAbrSlotIndexB: |
| return Configuration::B; |
| case kAbrSlotIndexR: |
| return Configuration::RECOVERY; |
| } |
| ERROR("Unknown Abr slot index %d\n", static_cast<int>(slot_index)); |
| return std::nullopt; |
| } |
| |
| std::optional<AbrSlotIndex> ConfigurationToSlotIndex(Configuration config) { |
| switch (config) { |
| case Configuration::A: |
| return kAbrSlotIndexA; |
| case Configuration::B: |
| return kAbrSlotIndexB; |
| case Configuration::RECOVERY: |
| return kAbrSlotIndexR; |
| } |
| ERROR("Unknown configuration %d\n", static_cast<int>(config)); |
| return std::nullopt; |
| } |
| |
| std::optional<Configuration> GetActiveConfiguration(const abr::Client& abr_client) { |
| auto slot_index = abr_client.GetBootSlot(false, nullptr); |
| return slot_index == kAbrSlotIndexR ? std::nullopt : SlotIndexToConfiguration(slot_index); |
| } |
| |
| // Helper to wrap a std::variant with a WriteFirmwareResult union. |
| // |
| // This can go away once llcpp unions support owning memory, but until then we |
| // need the variant to own the underlying data. |
| // |
| // |variant| must outlive the returned WriteFirmwareResult. |
| WriteFirmwareResult CreateWriteFirmwareResult( |
| std::variant<zx_status_t, fidl::aligned<bool>>* variant) { |
| WriteFirmwareResult result; |
| if (std::holds_alternative<zx_status_t>(*variant)) { |
| result.set_status(fidl::unowned_ptr(&std::get<zx_status_t>(*variant))); |
| } else { |
| result.set_unsupported_type(fidl::unowned_ptr(&std::get<fidl::aligned<bool>>(*variant))); |
| } |
| return result; |
| } |
| |
| } // namespace |
| |
| void Paver::FindDataSink(zx::channel data_sink, FindDataSinkCompleter::Sync _completer) { |
| // Use global devfs if one wasn't injected via set_devfs_root. |
| if (!devfs_root_) { |
| devfs_root_ = fbl::unique_fd(open("/dev", O_RDONLY)); |
| } |
| if (!svc_root_) { |
| svc_root_ = OpenServiceRoot(); |
| } |
| |
| DataSink::Bind(dispatcher_, devfs_root_.duplicate(), std::move(svc_root_), std::move(data_sink)); |
| } |
| |
| void Paver::UseBlockDevice(zx::channel block_device, zx::channel dynamic_data_sink, |
| UseBlockDeviceCompleter::Sync _completer) { |
| // Use global devfs if one wasn't injected via set_devfs_root. |
| if (!devfs_root_) { |
| devfs_root_ = fbl::unique_fd(open("/dev", O_RDONLY)); |
| } |
| if (!svc_root_) { |
| svc_root_ = OpenServiceRoot(); |
| } |
| |
| DynamicDataSink::Bind(dispatcher_, devfs_root_.duplicate(), std::move(svc_root_), |
| std::move(block_device), std::move(dynamic_data_sink)); |
| } |
| |
| void Paver::FindBootManager(zx::channel boot_manager, FindBootManagerCompleter::Sync _completer) { |
| // Use global devfs if one wasn't injected via set_devfs_root. |
| if (!devfs_root_) { |
| devfs_root_ = fbl::unique_fd(open("/dev", O_RDONLY)); |
| } |
| if (!svc_root_) { |
| svc_root_ = OpenServiceRoot(); |
| } |
| |
| BootManager::Bind(dispatcher_, devfs_root_.duplicate(), std::move(svc_root_), |
| std::move(boot_manager)); |
| } |
| |
| void DataSink::ReadAsset(::llcpp::fuchsia::paver::Configuration configuration, |
| ::llcpp::fuchsia::paver::Asset asset, ReadAssetCompleter::Sync completer) { |
| ::llcpp::fuchsia::mem::Buffer buf; |
| zx_status_t status = sink_.ReadAsset(configuration, asset, &buf); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(std::move(buf)); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void DataSink::WriteFirmware(fidl::StringView type, ::llcpp::fuchsia::mem::Buffer payload, |
| WriteFirmwareCompleter::Sync completer) { |
| auto variant = sink_.WriteFirmware(std::move(type), std::move(payload)); |
| completer.Reply(CreateWriteFirmwareResult(&variant)); |
| } |
| |
| void DataSink::WipeVolume(WipeVolumeCompleter::Sync completer) { |
| zx::channel out; |
| zx_status_t status = sink_.WipeVolume(&out); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(std::move(out)); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| zx_status_t DataSinkImpl::ReadAsset(Configuration configuration, Asset asset, |
| ::llcpp::fuchsia::mem::Buffer* buf) { |
| // No assets support content types yet, use the PartitionSpec default. |
| PartitionSpec spec(PartitionType(configuration, asset)); |
| |
| // Important: if we ever do pass a content type here, do NOT just return |
| // ZX_ERR_NOT_SUPPORTED directly - the caller needs to be able to distinguish |
| // between unknown asset types (which should be ignored) and actual errors |
| // that happen to return this same status code. |
| if (!partitioner_->SupportsPartition(spec)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return PartitionRead(*partitioner_, spec, &buf->vmo, &buf->size); |
| } |
| |
| zx_status_t DataSinkImpl::WriteAsset(Configuration configuration, Asset asset, |
| ::llcpp::fuchsia::mem::Buffer payload) { |
| // No assets support content types yet, use the PartitionSpec default. |
| PartitionSpec spec(PartitionType(configuration, asset)); |
| |
| // Important: if we ever do pass a content type here, do NOT just return |
| // ZX_ERR_NOT_SUPPORTED directly - the caller needs to be able to distinguish |
| // between unknown asset types (which should be ignored) and actual errors |
| // that happen to return this same status code. |
| if (!partitioner_->SupportsPartition(spec)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return PartitionPave(*partitioner_, std::move(payload.vmo), payload.size, spec); |
| } |
| |
| std::variant<zx_status_t, fidl::aligned<bool>> DataSinkImpl::WriteFirmware( |
| fidl::StringView type, ::llcpp::fuchsia::mem::Buffer payload) { |
| // Currently all our supported firmware lives in Partition::kBootloader. |
| PartitionSpec spec(Partition::kBootloader, std::string_view(type.data(), type.size())); |
| |
| if (!partitioner_->SupportsPartition(spec)) { |
| // unsupported_type = true. |
| return fidl::aligned<bool>(true); |
| } |
| |
| return PartitionPave(*partitioner_, std::move(payload.vmo), payload.size, spec); |
| } |
| |
| zx_status_t DataSinkImpl::WriteVolumes(zx::channel payload_stream) { |
| std::unique_ptr<StreamReader> reader; |
| zx_status_t status = StreamReader::Create(std::move(payload_stream), &reader); |
| if (status != ZX_OK) { |
| ERROR("Unable to create stream.\n"); |
| return status; |
| } |
| return FvmPave(devfs_root_, *partitioner_, std::move(reader)); |
| } |
| |
| // Deprecated in favor of WriteFirmware(). |
| // TODO(45606): move clients off this function and delete it. |
| zx_status_t DataSinkImpl::WriteBootloader(::llcpp::fuchsia::mem::Buffer payload) { |
| PartitionSpec spec(Partition::kBootloader); |
| |
| if (!partitioner_->SupportsPartition(spec)) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| return PartitionPave(*partitioner_, std::move(payload.vmo), payload.size, spec); |
| } |
| |
| zx_status_t DataSinkImpl::WriteDataFile(fidl::StringView filename, |
| ::llcpp::fuchsia::mem::Buffer payload) { |
| const char* mount_path = "/volume/data"; |
| const uint8_t data_guid[] = GUID_DATA_VALUE; |
| char minfs_path[PATH_MAX] = {0}; |
| char path[PATH_MAX] = {0}; |
| zx_status_t status = ZX_OK; |
| |
| fbl::unique_fd part_fd( |
| open_partition_with_devfs(devfs_root_.get(), nullptr, data_guid, ZX_SEC(1), path)); |
| if (!part_fd) { |
| ERROR("DATA partition not found in FVM\n"); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| auto disk_format = detect_disk_format(part_fd.get()); |
| fbl::unique_fd mountpoint_dev_fd; |
| // By the end of this switch statement, mountpoint_dev_fd needs to be an |
| // open handle to the block device that we want to mount at mount_path. |
| switch (disk_format) { |
| case DISK_FORMAT_MINFS: |
| // If the disk we found is actually minfs, we can just use the block |
| // device path we were given by open_partition. |
| strncpy(minfs_path, path, PATH_MAX); |
| mountpoint_dev_fd.reset(open(minfs_path, O_RDWR)); |
| break; |
| |
| case DISK_FORMAT_ZXCRYPT: { |
| std::unique_ptr<zxcrypt::FdioVolume> zxc_volume; |
| uint8_t slot = 0; |
| if ((status = zxcrypt::FdioVolume::UnlockWithDeviceKey( |
| std::move(part_fd), devfs_root_.duplicate(), static_cast<zxcrypt::key_slot_t>(slot), |
| &zxc_volume)) != ZX_OK) { |
| ERROR("Couldn't unlock zxcrypt volume: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Most of the time we'll expect the volume to actually already be |
| // unsealed, because we created it and unsealed it moments ago to |
| // format minfs. |
| if ((status = zxc_volume->Open(zx::sec(0), &mountpoint_dev_fd)) == ZX_OK) { |
| // Already unsealed, great, early exit. |
| break; |
| } |
| |
| // Ensure zxcrypt volume manager is bound. |
| zx::channel zxc_manager_chan; |
| if ((status = zxc_volume->OpenManager(zx::sec(5), |
| zxc_manager_chan.reset_and_get_address())) != ZX_OK) { |
| ERROR("Couldn't open zxcrypt volume manager: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Unseal. |
| zxcrypt::FdioVolumeManager zxc_manager(std::move(zxc_manager_chan)); |
| if ((status = zxc_manager.UnsealWithDeviceKey(slot)) != ZX_OK) { |
| ERROR("Couldn't unseal zxcrypt volume: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| // Wait for the device to appear, and open it. |
| if ((status = zxc_volume->Open(zx::sec(5), &mountpoint_dev_fd)) != ZX_OK) { |
| ERROR("Couldn't open block device atop unsealed zxcrypt volume: %s\n", |
| zx_status_get_string(status)); |
| return status; |
| } |
| } break; |
| |
| default: |
| ERROR("unsupported disk format at %s\n", path); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| mount_options_t opts(default_mount_options); |
| opts.create_mountpoint = true; |
| if ((status = mount(mountpoint_dev_fd.get(), mount_path, DISK_FORMAT_MINFS, &opts, |
| launch_logs_async)) != ZX_OK) { |
| ERROR("mount error: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| int filename_size = static_cast<int>(filename.size()); |
| |
| // mkdir any intermediate directories between mount_path and basename(filename). |
| snprintf(path, sizeof(path), "%s/%.*s", mount_path, filename_size, filename.data()); |
| size_t cur = strlen(mount_path); |
| size_t max = strlen(path) - strlen(basename(path)); |
| // note: the call to basename above modifies path, so it needs reconstruction. |
| snprintf(path, sizeof(path), "%s/%.*s", mount_path, filename_size, filename.data()); |
| while (cur < max) { |
| ++cur; |
| if (path[cur] == '/') { |
| path[cur] = 0; |
| // errors ignored, let the open() handle that later. |
| mkdir(path, 0700); |
| path[cur] = '/'; |
| } |
| } |
| |
| // We append here, because the primary use case here is to send SSH keys |
| // which can be appended, but we may want to revisit this choice for other |
| // files in the future. |
| { |
| uint8_t buf[8192]; |
| fbl::unique_fd kfd(open(path, O_CREAT | O_WRONLY | O_APPEND, 0600)); |
| if (!kfd) { |
| umount(mount_path); |
| ERROR("open %.*s error: %s\n", filename_size, filename.data(), strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| VmoReader reader(std::move(payload)); |
| size_t actual; |
| while ((status = reader.Read(buf, sizeof(buf), &actual)) == ZX_OK && actual > 0) { |
| if (write(kfd.get(), buf, actual) != static_cast<ssize_t>(actual)) { |
| umount(mount_path); |
| ERROR("write %.*s error: %s\n", filename_size, filename.data(), strerror(errno)); |
| return ZX_ERR_IO; |
| } |
| } |
| fsync(kfd.get()); |
| } |
| |
| if ((status = umount(mount_path)) != ZX_OK) { |
| ERROR("unmount %s failed: %s\n", mount_path, zx_status_get_string(status)); |
| return status; |
| } |
| |
| LOG("Wrote %.*s\n", filename_size, filename.data()); |
| return ZX_OK; |
| } |
| |
| zx_status_t DataSinkImpl::WipeVolume(zx::channel* out) { |
| std::unique_ptr<PartitionClient> partition; |
| zx_status_t status = GetFvmPartition(*partitioner_, &partition); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Bind the FVM driver to be in a well known state regarding races with block watcher. |
| // The block watcher will attempt to bind the FVM driver automatically based on |
| // the contents of the partition. However, that operation is not synchronized in |
| // any way with this service so the driver can be loaded at any time. |
| // WipeFvm basically writes underneath that driver, which means that we should |
| // eliminate the races at this point: assuming that the driver can load, either |
| // this call or the block watcher will succeed (and the other one will fail), |
| // but the driver will be loaded before moving on. |
| TryBindToFvmDriver(devfs_root_, partition->block_fd(), zx::sec(3)); |
| |
| status = partitioner_->WipeFvm(); |
| if (status != ZX_OK) { |
| ERROR("Failure wiping partition: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| zx::channel channel; |
| status = FormatFvm(devfs_root_, *partitioner_, &channel); |
| if (status != ZX_OK) { |
| ERROR("Failure formatting partition: %s\n", zx_status_get_string(status)); |
| return status; |
| } |
| |
| *out = std::move(channel); |
| return ZX_OK; |
| } |
| |
| void DataSink::Bind(async_dispatcher_t* dispatcher, fbl::unique_fd devfs_root, zx::channel svc_root, |
| zx::channel server) { |
| auto partitioner = |
| DevicePartitioner::Create(devfs_root.duplicate(), std::move(svc_root), GetCurrentArch()); |
| if (!partitioner) { |
| ERROR("Unable to initialize a partitioner.\n"); |
| fidl_epitaph_write(server.get(), ZX_ERR_BAD_STATE); |
| return; |
| } |
| auto data_sink = std::make_unique<DataSink>(std::move(devfs_root), std::move(partitioner)); |
| fidl::Bind(dispatcher, std::move(server), std::move(data_sink)); |
| } |
| |
| void DynamicDataSink::Bind(async_dispatcher_t* dispatcher, fbl::unique_fd devfs_root, |
| zx::channel svc_root, zx::channel block_device, zx::channel server) { |
| auto partitioner = DevicePartitioner::Create(devfs_root.duplicate(), std::move(svc_root), |
| GetCurrentArch(), std::move(block_device)); |
| if (!partitioner) { |
| ERROR("Unable to initialize a partitioner.\n"); |
| fidl_epitaph_write(server.get(), ZX_ERR_BAD_STATE); |
| return; |
| } |
| auto data_sink = std::make_unique<DynamicDataSink>(std::move(devfs_root), std::move(partitioner)); |
| fidl::Bind(dispatcher, std::move(server), std::move(data_sink)); |
| } |
| |
| void DynamicDataSink::InitializePartitionTables( |
| InitializePartitionTablesCompleter::Sync completer) { |
| completer.Reply(sink_.partitioner()->InitPartitionTables()); |
| } |
| |
| void DynamicDataSink::WipePartitionTables(WipePartitionTablesCompleter::Sync completer) { |
| completer.Reply(sink_.partitioner()->WipePartitionTables()); |
| } |
| |
| void DynamicDataSink::ReadAsset(::llcpp::fuchsia::paver::Configuration configuration, |
| ::llcpp::fuchsia::paver::Asset asset, |
| ReadAssetCompleter::Sync completer) { |
| ::llcpp::fuchsia::mem::Buffer buf; |
| auto status = sink_.ReadAsset(configuration, asset, &buf); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(std::move(buf)); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void DynamicDataSink::WriteFirmware(fidl::StringView type, ::llcpp::fuchsia::mem::Buffer payload, |
| WriteFirmwareCompleter::Sync completer) { |
| auto variant = sink_.WriteFirmware(std::move(type), std::move(payload)); |
| completer.Reply(CreateWriteFirmwareResult(&variant)); |
| } |
| |
| void DynamicDataSink::WipeVolume(WipeVolumeCompleter::Sync completer) { |
| zx::channel out; |
| zx_status_t status = sink_.WipeVolume(&out); |
| if (status == ZX_OK) { |
| completer.ReplySuccess(std::move(out)); |
| } else { |
| completer.ReplyError(status); |
| } |
| } |
| |
| void BootManager::Bind(async_dispatcher_t* dispatcher, fbl::unique_fd devfs_root, |
| zx::channel svc_root, zx::channel server) { |
| std::unique_ptr<abr::Client> abr_client; |
| if (zx_status_t status = |
| abr::Client::Create(std::move(devfs_root), std::move(svc_root), &abr_client); |
| status != ZX_OK) { |
| ERROR("Failed to get ABR client: %s\n", zx_status_get_string(status)); |
| fidl_epitaph_write(server.get(), status); |
| return; |
| } |
| |
| auto boot_manager = std::make_unique<BootManager>(std::move(abr_client)); |
| fidl::Bind(dispatcher, std::move(server), std::move(boot_manager)); |
| } |
| |
| void BootManager::QueryActiveConfiguration(QueryActiveConfigurationCompleter::Sync completer) { |
| std::optional<Configuration> config = GetActiveConfiguration(*abr_client_); |
| if (!config) { |
| completer.ReplyError(ZX_ERR_NOT_SUPPORTED); |
| return; |
| } |
| completer.ReplySuccess(config.value()); |
| } |
| |
| void BootManager::QueryConfigurationStatus(Configuration configuration, |
| QueryConfigurationStatusCompleter::Sync completer) { |
| AbrSlotInfo slot; |
| auto slot_index = ConfigurationToSlotIndex(configuration); |
| if (auto res = slot_index ? abr_client_->GetSlotInfo(*slot_index, &slot) : ZX_ERR_INVALID_ARGS; |
| res != ZX_OK) { |
| ERROR("Failed to get slot info %d\n", static_cast<uint32_t>(configuration)); |
| completer.ReplyError(res); |
| return; |
| } |
| |
| if (!slot.is_bootable) { |
| completer.ReplySuccess(::llcpp::fuchsia::paver::ConfigurationStatus::UNBOOTABLE); |
| } else if (slot.is_marked_successful == 0) { |
| completer.ReplySuccess(::llcpp::fuchsia::paver::ConfigurationStatus::PENDING); |
| } else { |
| completer.ReplySuccess(::llcpp::fuchsia::paver::ConfigurationStatus::HEALTHY); |
| } |
| } |
| |
| void BootManager::SetConfigurationActive(Configuration configuration, |
| SetConfigurationActiveCompleter::Sync completer) { |
| LOG("Setting configuration %d as active\n", static_cast<uint32_t>(configuration)); |
| |
| auto slot_index = ConfigurationToSlotIndex(configuration); |
| if (auto res = slot_index ? abr_client_->MarkSlotActive(*slot_index) : ZX_ERR_INVALID_ARGS; |
| res != ZX_OK) { |
| ERROR("Failed to set configuration: %d active\n", static_cast<uint32_t>(configuration)); |
| completer.Reply(res); |
| return; |
| } |
| |
| LOG("Set active configuration to %d\n", static_cast<uint32_t>(configuration)); |
| |
| completer.Reply(ZX_OK); |
| } |
| |
| void BootManager::SetConfigurationUnbootable(Configuration configuration, |
| SetConfigurationUnbootableCompleter::Sync completer) { |
| LOG("Setting configuration %d as unbootable\n", static_cast<uint32_t>(configuration)); |
| |
| auto slot_index = ConfigurationToSlotIndex(configuration); |
| if (auto res = slot_index ? abr_client_->MarkSlotUnbootable(*slot_index) : ZX_ERR_INVALID_ARGS; |
| res != ZX_OK) { |
| ERROR("Failed to set configuration: %d unbootable\n", static_cast<uint32_t>(configuration)); |
| completer.Reply(res); |
| return; |
| } |
| |
| LOG("Set %d configuration as unbootable\n", static_cast<uint32_t>(configuration)); |
| |
| completer.Reply(ZX_OK); |
| } |
| |
| void BootManager::SetActiveConfigurationHealthy( |
| SetActiveConfigurationHealthyCompleter::Sync completer) { |
| LOG("Setting active configuration as healthy\n"); |
| |
| std::optional<Configuration> config = GetActiveConfiguration(*abr_client_); |
| if (!config) { |
| ERROR("No configuration bootable. Cannot mark as successful boot.\n"); |
| completer.Reply(ZX_ERR_BAD_STATE); |
| return; |
| } |
| |
| if (auto res = abr_client_->MarkSlotSuccessful(*ConfigurationToSlotIndex(*config)); |
| res != ZX_OK) { |
| ERROR("Failed to set configuration: %d healthy\n", static_cast<uint32_t>(*config)); |
| completer.Reply(res); |
| return; |
| } |
| |
| LOG("Set active configuration as healthy\n"); |
| |
| completer.Reply(ZX_OK); |
| } |
| |
| } // namespace paver |