blob: e0a1afd035a9d69e977f5fbe883e61408dda199f [file] [log] [blame]
// 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 "fvm.h"
#include <dirent.h>
#include <fcntl.h>
#include <fidl/fuchsia.device/cpp/fidl.h>
#include <fidl/fuchsia.hardware.block.partition/cpp/wire.h>
#include <fidl/fuchsia.hardware.block.volume/cpp/wire.h>
#include <fidl/fuchsia.hardware.block/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fit/defer.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/zx/channel.h>
#include <lib/zx/fifo.h>
#include <lib/zx/time.h>
#include <lib/zx/vmo.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include <algorithm>
#include <cstddef>
#include <memory>
#include <fbl/algorithm.h>
#include <fbl/array.h>
#include <fbl/string_buffer.h>
#include <fbl/unique_fd.h>
#include <ramdevice-client/ramdisk.h>
#include <safemath/safe_math.h>
#include "pave-logging.h"
#include "src/lib/uuid/uuid.h"
#include "src/security/lib/zxcrypt/client.h"
#include "src/storage/fvm/format.h"
#include "src/storage/fvm/fvm_sparse.h"
#include "src/storage/lib/block_client/cpp/client.h"
#include "src/storage/lib/fs_management/cpp/fvm.h"
namespace paver {
namespace {
namespace block = fuchsia_hardware_block;
namespace partition = fuchsia_hardware_block_partition;
namespace volume = fuchsia_hardware_block_volume;
namespace device = fuchsia_device;
using fuchsia_hardware_block_volume::wire::VolumeManagerInfo;
constexpr const char kDevPath[] = "/dev/";
constexpr const char kFvmName[] = "fvm";
constexpr const char kFvmControllerPath[] = "fvm/device_controller";
// The number of additional slices a partition will need to become
// zxcrypt'd.
//
// TODO(aarongreen): Replace this with a value supplied by ulib/zxcrypt.
constexpr size_t kZxcryptExtraSlices = 1;
zx::result<std::string> GetTopoPath(fidl::UnownedClientEnd<fuchsia_device::Controller> controller) {
fidl::Result result = fidl::Call(controller)->GetTopologicalPath();
if (result.is_error()) {
if (result.error_value().is_domain_error()) {
return zx::error(result.error_value().domain_error());
}
if (result.error_value().is_framework_error()) {
return zx::error(result.error_value().framework_error().status());
}
}
return zx::ok(std::move(result.value().path()));
}
// Confirm that the file descriptor to the underlying partition exists within an
// FVM, not, for example, a GPT or MBR.
//
// |out| is true if |fd| is a VPartition, else false.
zx::result<bool> FvmIsVirtualPartition(
fidl::UnownedClientEnd<fuchsia_device::Controller> controller) {
zx::result path = GetTopoPath(controller);
if (path.is_error()) {
return path.take_error();
}
return zx::ok(path.value().find("fvm") != std::string::npos);
}
// Describes the state of a partition actively being written
// out to disk.
struct PartitionInfo {
fvm::PartitionDescriptor* pd = nullptr;
fvm::PartitionDescriptor aligned_pd = {};
fidl::ClientEnd<fuchsia_device::Controller> controller;
fidl::ClientEnd<partition::Partition> partition;
bool active = false;
zx::result<> SetPartition(fidl::ClientEnd<fuchsia_device::Controller> controller) {
fidl::ClientEnd<partition::Partition> partition;
zx::result server = fidl::CreateEndpoints(&partition);
if (server.is_error()) {
ERROR("Failed creating endpoints: %s", server.status_string());
return server.take_error();
}
if (fidl::OneWayStatus status =
fidl::WireCall(controller)->ConnectToDeviceFidl(server->TakeChannel());
!status.ok()) {
ERROR("Failed to connect to device fidl: %s", status.FormatDescription().c_str());
return zx::error(status.status());
}
this->controller = std::move(controller);
this->partition = std::move(partition);
return zx::ok();
}
};
ptrdiff_t GetExtentOffset(size_t extent) {
return safemath::checked_cast<ptrdiff_t>(sizeof(fvm::PartitionDescriptor) +
extent * sizeof(fvm::ExtentDescriptor));
}
fvm::ExtentDescriptor GetExtent(fvm::PartitionDescriptor* pd, size_t extent) {
fvm::ExtentDescriptor descriptor = {};
const auto* descriptor_ptr = reinterpret_cast<uint8_t*>(pd) + GetExtentOffset(extent);
memcpy(&descriptor, descriptor_ptr, sizeof(fvm::ExtentDescriptor));
return descriptor;
}
zx_status_t FlushClient(block_client::Client& client) {
block_fifo_request_t request;
request.group = 0;
request.vmoid = block::wire::kVmoidInvalid;
request.command = {.opcode = BLOCK_OPCODE_FLUSH, .flags = 0};
request.length = 0;
request.vmo_offset = 0;
request.dev_offset = 0;
return client.Transaction(&request, 1);
}
// Stream an FVM partition to disk.
zx_status_t StreamFvmPartition(fvm::SparseReader* reader, PartitionInfo* part,
const fzl::VmoMapper& mapper, block_client::Client& client,
size_t block_size, block_fifo_request_t* request) {
size_t slice_size = reader->Image()->slice_size;
const size_t vmo_cap = mapper.size();
for (size_t e = 0; e < part->aligned_pd.extent_count; e++) {
LOG("Writing extent %zu... \n", e);
fvm::ExtentDescriptor ext = GetExtent(part->pd, e);
size_t offset = ext.slice_start * slice_size;
size_t bytes_left = ext.extent_length;
// Write real data
while (bytes_left > 0) {
size_t actual;
zx_status_t status = reader->ReadData(reinterpret_cast<uint8_t*>(mapper.start()),
std::min(bytes_left, vmo_cap), &actual);
if (status != ZX_OK) {
ERROR("Error reading extent data with %zu bytes of %zu remaining: %s\n", bytes_left,
ext.extent_length, zx_status_get_string(status));
return status;
}
const size_t vmo_sz = actual;
bytes_left -= actual;
if (vmo_sz == 0) {
ERROR("Read nothing from src_fd; %zu bytes left\n", bytes_left);
return ZX_ERR_IO;
}
if (vmo_sz % block_size != 0) {
ERROR("Cannot write non-block size multiple: %zu\n", vmo_sz);
return ZX_ERR_IO;
}
uint64_t length = vmo_sz / block_size;
if (length > UINT32_MAX) {
ERROR("Error writing partition: Too large\n");
return ZX_ERR_OUT_OF_RANGE;
}
request->length = static_cast<uint32_t>(length);
request->vmo_offset = 0;
request->dev_offset = offset / block_size;
if (zx_status_t status = client.Transaction(request, 1); status != ZX_OK) {
ERROR("Error writing partition data\n");
return status;
}
offset += vmo_sz;
}
// Write trailing zeroes (which are implied, but were omitted from
// transfer).
bytes_left = (ext.slice_count * slice_size) - ext.extent_length;
if (bytes_left > 0) {
LOG("%zu bytes written, %zu zeroes left\n", ext.extent_length, bytes_left);
memset(mapper.start(), 0, vmo_cap);
}
while (bytes_left > 0) {
uint64_t length = std::min(bytes_left, vmo_cap) / block_size;
if (length > UINT32_MAX) {
ERROR("Error writing trailing zeroes: Too large(%lu)\n", length);
return ZX_ERR_OUT_OF_RANGE;
}
request->length = static_cast<uint32_t>(length);
request->vmo_offset = 0;
request->dev_offset = offset / block_size;
if (zx_status_t status = client.Transaction(request, 1); status != ZX_OK) {
ERROR("Error writing trailing zeroes length:%u dev_offset:%lu vmo_offset:%lu\n",
request->length, request->dev_offset, request->vmo_offset);
return status;
}
offset += request->length * block_size;
bytes_left -= request->length * block_size;
}
}
return ZX_OK;
}
} // namespace
zx::result<fidl::ClientEnd<fuchsia_device::Controller>> TryBindToFvmDriver(
const fbl::unique_fd& devfs_root, fidl::UnownedClientEnd<fuchsia_device::Controller> partition,
zx::duration timeout) {
zx::result topo_path = GetTopoPath(partition);
if (topo_path.is_error()) {
ERROR("Failed to get topological path: %s\n", topo_path.status_string());
return topo_path.take_error();
}
std::string path = std::move(topo_path.value());
if (!cpp20::starts_with(std::string_view(path), kDevPath)) {
ERROR("Topo path does not start with '%s': %s\n", kDevPath, path.c_str());
return zx::error(ZX_ERR_BAD_STATE);
}
path.erase(0, std::string_view(kDevPath).length());
// Check if fvm already exists, and return it if it does.
int fd;
if (zx_status_t status =
fdio_open_fd_at(devfs_root.get(), path.c_str(),
static_cast<uint32_t>(fuchsia_io::OpenFlags::kDirectory), &fd);
status != ZX_OK) {
ERROR("Failed to open %s: %s\n", path.c_str(), zx_status_get_string(status));
return zx::error(status);
}
DIR* dir = fdopendir(fd);
auto cleanup = fit::defer([dir]() { closedir(dir); });
fdio_cpp::UnownedFdioCaller dir_caller(dirfd(dir));
while (true) {
struct dirent* de = readdir(dir);
if (!de) {
break;
}
std::string_view name = de->d_name;
if (name != kFvmName) {
continue;
}
return component::ConnectAt<fuchsia_device::Controller>(dir_caller.directory(),
kFvmControllerPath);
}
LOG("Could not find fvm: proceeding to rebind...\n");
constexpr char kFvmDriverLib[] = "fvm.cm";
fidl::WireResult result = fidl::WireCall(partition)->Rebind(fidl::StringView(kFvmDriverLib));
if (!result.ok()) {
ERROR("Could not call rebind driver: %s\n", zx_status_get_string(result.status()));
return zx::error(result.status());
}
if (result.value().is_error() && result.value().error_value() != ZX_ERR_ALREADY_BOUND) {
ERROR("Could not rebind fvm driver: %s\n", zx_status_get_string(result.value().error_value()));
return zx::error(result.value().error_value());
}
auto fvm_controller_path = std::string(kFvmName) + "/device_controller";
zx::result channel =
device_watcher::RecursiveWaitForFile(dirfd(dir), fvm_controller_path.c_str(), timeout);
if (channel.is_error()) {
ERROR("Error waiting for fvm driver to bind: %s\n", channel.status_string());
return channel.take_error();
}
return zx::ok(fidl::ClientEnd<fuchsia_device::Controller>(std::move(channel.value())));
}
zx::result<fidl::ClientEnd<fuchsia_device::Controller>> FvmPartitionFormat(
const fbl::unique_fd& devfs_root,
fidl::UnownedClientEnd<fuchsia_hardware_block::Block> partition,
fidl::UnownedClientEnd<fuchsia_device::Controller> partition_controller,
const fvm::SparseImage& header, BindOption option, FormatResult* format_result) {
// Although the format (based on the magic in the FVM superblock)
// indicates this is (or at least was) an FVM image, it may be invalid.
//
// Attempt to bind the FVM driver to this partition, but fall-back to
// reinitializing the FVM image so the rest of the paving
// process can continue successfully.
if (format_result != nullptr) {
*format_result = FormatResult::kUnknown;
}
if (option == BindOption::TryBind) {
fs_management::DiskFormat df = fs_management::DetectDiskFormat(partition);
if (df == fs_management::kDiskFormatFvm) {
zx::result fvm = TryBindToFvmDriver(devfs_root, partition_controller, zx::sec(3));
if (fvm.is_ok()) {
LOG("Found already formatted FVM.\n");
auto [volume, volume_server] = fidl::Endpoints<volume::VolumeManager>::Create();
if (fidl::OneWayError status =
fidl::WireCall(fvm.value())->ConnectToDeviceFidl(volume_server.TakeChannel());
!status.ok()) {
return zx::error(status.status());
}
fidl::WireResult result = fidl::WireCall(volume)->GetInfo();
if (result.ok()) {
auto get_maximum_slice_count = [](const fvm::SparseImage& header) {
return fvm::Header::FromDiskSize(fvm::kMaxUsablePartitions, header.maximum_disk_size,
header.slice_size)
.GetAllocationTableAllocatedEntryCount();
};
if (result.value().info->slice_size != header.slice_size) {
ERROR("Mismatched slice size. Reinitializing FVM.\n");
} else if (header.maximum_disk_size > 0 &&
result.value().info->maximum_slice_count < get_maximum_slice_count(header)) {
ERROR("Mismatched maximum slice count. Reinitializing FVM.\n");
} else {
if (format_result != nullptr) {
*format_result = FormatResult::kPreserved;
}
return fvm;
}
} else {
ERROR("Could not query FVM for info. Reinitializing FVM.\n");
}
} else {
ERROR(
"Saw fs_management::kDiskFormatFvm, but could not bind driver. Reinitializing FVM.\n");
}
}
}
LOG("Initializing partition as FVM\n");
{
if (format_result != nullptr) {
*format_result = FormatResult::kReformatted;
}
const fidl::WireResult result = fidl::WireCall(partition)->GetInfo();
if (!result.ok()) {
ERROR("Failed to query block info: %s\n", result.FormatDescription().c_str());
return zx::error(result.status());
}
const fit::result response = result.value();
if (response.is_error()) {
ERROR("Failed to query block info: %s\n", zx_status_get_string(response.error_value()));
return zx::error(response.error_value());
}
const fuchsia_hardware_block::wire::BlockInfo& info = response.value()->info;
uint64_t initial_disk_size = info.block_count * info.block_size;
uint64_t max_disk_size =
(header.maximum_disk_size == 0) ? initial_disk_size : header.maximum_disk_size;
zx_status_t status = fs_management::FvmInitPreallocated(partition, initial_disk_size,
max_disk_size, header.slice_size);
if (status != ZX_OK) {
ERROR("Failed to initialize fvm: %s\n", zx_status_get_string(status));
return zx::error(status);
}
}
return TryBindToFvmDriver(devfs_root, partition_controller, zx::sec(3));
}
namespace {
// Formats a block device as a zxcrypt volume.
//
// On success, returns the VolumeManager for the zxcrypt instance, and writes the inner block
// device's into |part|.
zx::result<zxcrypt::VolumeManager> ZxcryptCreate(PartitionInfo* part) {
// TODO(security): https://fxbug.dev/42106007. We need to bind with channel in order to pass a key
// here.
// TODO(security): https://fxbug.dev/42106740. The created volume must marked as needing key
// rotation.
fbl::unique_fd devfs_root;
if (zx_status_t status = fdio_open_fd("/dev", 0, devfs_root.reset_and_get_address());
status != ZX_OK) {
return zx::error(status);
}
zxcrypt::VolumeManager zxcrypt_manager(std::move(part->controller), std::move(devfs_root));
zx::channel client_chan;
if (zx_status_t status = zxcrypt_manager.OpenClient(zx::sec(3), client_chan); status != ZX_OK) {
ERROR("Could not open zxcrypt volume manager\n");
return zx::error(status);
}
zxcrypt::EncryptedVolumeClient zxcrypt_client(std::move(client_chan));
uint8_t slot = 0;
if (zx_status_t status = zxcrypt_client.FormatWithImplicitKey(slot); status != ZX_OK) {
ERROR("Could not create zxcrypt volume\n");
return zx::error(status);
}
if (zx_status_t status = zxcrypt_client.UnsealWithImplicitKey(slot); status != ZX_OK) {
ERROR("Could not unseal zxcrypt volume\n");
return zx::error(status);
}
zx::result controller = zxcrypt_manager.OpenInnerBlockDevice(zx::sec(3));
if (controller.is_error()) {
ERROR("Could not open zxcrypt volume: %s\n", controller.status_string());
return controller.take_error();
}
if (zx::result status = part->SetPartition(std::move(controller.value())); status.is_error()) {
return status.take_error();
}
return zx::ok(std::move(zxcrypt_manager));
}
zx::result<bool> FvmPartitionIsChild(fidl::UnownedClientEnd<fuchsia_device::Controller> fvm,
fidl::UnownedClientEnd<fuchsia_device::Controller> partition) {
zx::result fvm_path = GetTopoPath(fvm);
if (fvm_path.is_error()) {
ERROR("Couldn't get topological path of FVM: %s\n", fvm_path.status_string());
return fvm_path.take_error();
}
zx::result partition_path = GetTopoPath(partition);
if (partition_path.is_error()) {
ERROR("Couldn't get topological path of partition: %s\n", partition_path.status_string());
return partition_path.take_error();
}
if (cpp20::starts_with(std::string_view(fvm_path.value()),
std::string_view(partition_path.value()))) {
ERROR("Partition does not exist within FVM: partition='%s' fvm='%s'\n",
partition_path.value().c_str(), fvm_path.value().c_str());
return zx::ok(false);
}
return zx::ok(true);
}
void RecommendWipe(const char* problem) {
Warn(problem, "Please run 'install-disk-image wipe' to wipe your partitions");
}
// Calculate the amount of space necessary for the incoming partitions,
// validating the header along the way. Additionally, deletes any old partitions
// which match the type GUID of the provided partition.
//
// Parses the information from the |reader| into |parts|.
zx_status_t PreProcessPartitions(fidl::UnownedClientEnd<fuchsia_device::Controller> fvm,
const std::unique_ptr<fvm::SparseReader>& reader,
const fbl::Array<PartitionInfo>& parts,
size_t* out_requested_slices) {
fvm::PartitionDescriptor* part = reader->Partitions();
fvm::SparseImage* hdr = reader->Image();
// Validate the header and determine the necessary slice requirements for
// all partitions and all offsets.
size_t requested_slices = 0;
for (size_t p = 0; p < hdr->partition_count; p++) {
parts[p].pd = part;
memcpy(&parts[p].aligned_pd, part, sizeof(fvm::PartitionDescriptor));
if (parts[p].pd->magic != fvm::kPartitionDescriptorMagic) {
ERROR("Bad partition magic\n");
return ZX_ERR_IO;
}
if (zx_status_t status = WipeAllFvmPartitionsWithGuid(fvm, parts[p].pd->type);
status != ZX_OK) {
ERROR("Failure wiping old partitions matching this GUID: %s\n", zx_status_get_string(status));
return status;
}
fvm::ExtentDescriptor ext = GetExtent(parts[p].pd, 0);
if (ext.magic != fvm::kExtentDescriptorMagic) {
ERROR("Bad extent magic\n");
return ZX_ERR_IO;
}
if (ext.slice_start != 0) {
ERROR("First slice must start at zero\n");
return ZX_ERR_IO;
}
if (ext.slice_count == 0) {
ERROR("Extents must have > 0 slices\n");
return ZX_ERR_IO;
}
if (ext.extent_length > ext.slice_count * hdr->slice_size) {
ERROR("Extent length(%lu) must fit within allocated slice count(%lu * %lu)\n",
ext.extent_length, ext.slice_count, hdr->slice_size);
return ZX_ERR_IO;
}
// Filter drivers may require additional space.
if ((parts[p].aligned_pd.flags & fvm::kSparseFlagZxcrypt) != 0) {
requested_slices += kZxcryptExtraSlices;
}
for (size_t e = 1; e < parts[p].aligned_pd.extent_count; e++) {
ext = GetExtent(parts[p].pd, e);
if (ext.magic != fvm::kExtentDescriptorMagic) {
ERROR("Bad extent magic\n");
return ZX_ERR_IO;
}
if (ext.slice_count == 0) {
ERROR("Extents must have > 0 slices\n");
return ZX_ERR_IO;
}
if (ext.extent_length > ext.slice_count * hdr->slice_size) {
char name[sizeof(parts[p].aligned_pd.name) + 1];
name[sizeof(parts[p].aligned_pd.name)] = '\0';
memcpy(name, parts[p].aligned_pd.name, sizeof(parts[p].aligned_pd.name));
ERROR("Partition(%s) extent length(%lu) must fit within allocated slice count(%lu * %lu)\n",
name, ext.extent_length, ext.slice_count, hdr->slice_size);
return ZX_ERR_IO;
}
requested_slices += ext.slice_count;
}
part = reinterpret_cast<fvm::PartitionDescriptor*>(
reinterpret_cast<uint8_t*>(parts[p].pd) + sizeof(fvm::PartitionDescriptor) +
parts[p].aligned_pd.extent_count * sizeof(fvm::ExtentDescriptor));
}
*out_requested_slices = requested_slices;
return ZX_OK;
}
struct BoundZxcryptDevice {
public:
BoundZxcryptDevice() = default;
BoundZxcryptDevice(BoundZxcryptDevice&&) = default;
BoundZxcryptDevice(const BoundZxcryptDevice&) = delete;
explicit BoundZxcryptDevice(zxcrypt::VolumeManager&& manager) : manager(std::move(manager)) {}
~BoundZxcryptDevice() {
if (manager) {
if (zx_status_t status = manager->Unbind(); status != ZX_OK) {
ERROR("Failed to unbind Zxcrypt: %s. The driver may be unavailable.",
zx_status_get_string(status));
}
}
}
std::optional<zxcrypt::VolumeManager> manager;
};
// Allocates the space requested by the partitions by creating new
// partitions and filling them with extents. This guarantees that
// streaming the data to the device will not run into "no space" issues
// later.
// Partitions which are zxcrypt-enabled will have their driver bound. A set of all such drivers is
// returned so the caller can unbind them when finished writing into them.
zx::result<std::vector<BoundZxcryptDevice>> AllocatePartitions(
const fbl::unique_fd& devfs_root,
fidl::UnownedClientEnd<fuchsia_hardware_block_volume::VolumeManager> fvm_device,
fbl::Array<PartitionInfo>* parts) {
fdio_cpp::UnownedFdioCaller devfs_caller(devfs_root);
fidl::UnownedClientEnd devfs_client_end = devfs_caller.directory();
std::vector<BoundZxcryptDevice> bound_devices;
for (PartitionInfo& part_info : *parts) {
fvm::ExtentDescriptor ext = GetExtent(part_info.pd, 0);
// Allocate this partition as inactive so it gets deleted on the next
// reboot if this stream fails.
uint32_t flags = part_info.active ? 0 : volume::wire::kAllocatePartitionFlagInactive;
uint64_t slice_count = ext.slice_count;
uuid::Uuid type_guid(part_info.pd->type);
uuid::Uuid instance_guid = uuid::Uuid::Generate();
const char* name = reinterpret_cast<const char*>(part_info.pd->name);
std::string_view name_view(name, strnlen(name, sizeof(part_info.pd->name)));
{
char name[sizeof(part_info.pd->name) + 1];
name[sizeof(part_info.pd->name)] = '\0';
memcpy(name, part_info.pd->name, sizeof(part_info.pd->name));
LOG("Allocating partition %s consisting of %zu slices\n", name, slice_count);
}
if (zx::result channel = fs_management::FvmAllocatePartitionWithDevfs(
devfs_client_end, fvm_device, slice_count, type_guid, instance_guid, name_view, flags);
channel.is_error()) {
ERROR("Couldn't allocate partition: %s\n", channel.status_string());
return zx::error(ZX_ERR_NO_SPACE);
} else {
if (zx::result status = part_info.SetPartition(std::move(channel.value()));
status.is_error()) {
return status.take_error();
}
}
// Add filter drivers.
if ((part_info.pd->flags & fvm::kSparseFlagZxcrypt) != 0) {
LOG("Creating zxcrypt volume\n");
auto volume_manager = ZxcryptCreate(&part_info);
if (volume_manager.is_error()) {
return volume_manager.take_error();
}
bound_devices.emplace_back(std::move(*volume_manager));
}
// The 0th index extent is allocated alongside the partition, so we
// begin indexing from the 1st extent here.
for (size_t e = 1; e < part_info.pd->extent_count; e++) {
ext = GetExtent(part_info.pd, e);
uint64_t offset = ext.slice_start;
uint64_t length = ext.slice_count;
fidl::UnownedClientEnd<volume::Volume> volume(part_info.partition.channel().borrow());
auto result = fidl::WireCall(volume)->Extend(offset, length);
auto status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("Failed to extend partition: %s\n", zx_status_get_string(status));
return zx::error(status);
}
}
}
return zx::ok(std::move(bound_devices));
}
// Holds the description of a partition with a single extent. Note that even though some code asks
// for a PartitionDescriptor, in reality it treats that as a descriptor followed by a bunch of
// extents, so this copes with that de-facto pattern.
struct FvmPartition {
// Returns an FVM partition with no real information about extents. In order to
// use the partitions, they should be formatted with the appropriate filesystem.
static FvmPartition Make(const std::array<uint8_t, fvm::kGuidSize> partition_type,
std::string_view name) {
FvmPartition partition{.extent = {
.slice_count = 1,
}};
std::copy(std::begin(partition_type), std::end(partition_type),
std::begin(partition.descriptor.type));
std::copy(name.begin(), name.end(), std::begin(partition.descriptor.name));
return partition;
}
fvm::PartitionDescriptor descriptor;
fvm::ExtentDescriptor extent;
};
} // namespace
// Deletes all partitions within the FVM with a type GUID matching |type_guid|
// until there are none left.
zx_status_t WipeAllFvmPartitionsWithGuid(fidl::UnownedClientEnd<fuchsia_device::Controller> fvm,
const uint8_t type_guid[]) {
zx::result fvm_topo_path = GetTopoPath(fvm);
if (fvm_topo_path.is_error()) {
ERROR("Couldn't get topological path of FVM! %s\n", fvm_topo_path.status_string());
return fvm_topo_path.error_value();
}
fs_management::PartitionMatcher matcher{
.type_guids = {uuid::Uuid(&type_guid[0])},
.parent_device = fvm_topo_path->c_str(),
};
for (;;) {
zx::result old_partition = fs_management::OpenPartition(matcher, /* wait=*/false);
if (old_partition.is_error()) {
if (old_partition.error_value() == ZX_ERR_NOT_FOUND) {
return ZX_OK;
}
return old_partition.error_value();
}
zx::result is_vpartition = FvmIsVirtualPartition(*old_partition);
if (is_vpartition.is_error()) {
ERROR("Couldn't confirm old vpartition type: %s\n", is_vpartition.status_string());
return ZX_ERR_IO;
}
if (zx::result result = FvmPartitionIsChild(fvm, *old_partition); result.is_ok()) {
if (!result.value()) {
RecommendWipe("Streaming a partition type which also exists outside the target FVM");
return ZX_ERR_BAD_STATE;
}
} else {
std::string error = std::string("Failed to check if partition type is a child of the FVM: ") +
result.status_string();
RecommendWipe(error.c_str());
return result.error_value();
}
if (!is_vpartition.value()) {
RecommendWipe("Streaming a partition type which also exists in a GPT");
return ZX_ERR_BAD_STATE;
}
// We're paving a partition that already exists within the FVM: let's
// destroy it before we pave anew.
auto [volume, volume_server] = fidl::Endpoints<volume::Volume>::Create();
if (fidl::OneWayError status =
fidl::WireCall(*old_partition)->ConnectToDeviceFidl(volume_server.TakeChannel());
!status.ok()) {
return status.status();
}
fidl::WireResult result = fidl::WireCall(volume)->Destroy();
zx_status_t status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
ERROR("Couldn't destroy partition: %s\n", zx_status_get_string(status));
return status;
}
}
}
zx::result<> AllocateEmptyPartitions(
const fbl::unique_fd& devfs_root,
fidl::UnownedClientEnd<fuchsia_hardware_block_volume::VolumeManager> fvm_device) {
FvmPartition fvm_partitions[] = {
FvmPartition::Make(std::array<uint8_t, fvm::kGuidSize>(GUID_BLOB_VALUE),
paver::kBlobfsPartitionLabel),
FvmPartition::Make(std::array<uint8_t, fvm::kGuidSize>(GUID_DATA_VALUE),
paver::kDataPartitionLabel)};
fbl::Array<PartitionInfo> partitions(new PartitionInfo[2]{{
.pd = &fvm_partitions[0].descriptor,
.active = true,
},
{
.pd = &fvm_partitions[1].descriptor,
.active = true,
}},
2);
zx::result res = AllocatePartitions(devfs_root, fvm_device, &partitions);
if (res.is_error()) {
return res.take_error();
}
return zx::ok();
}
zx::result<> FvmStreamPartitions(const fbl::unique_fd& devfs_root,
std::unique_ptr<PartitionClient> partition_client,
std::unique_ptr<fvm::ReaderInterface> payload) {
if (!partition_client->SupportsBlockPartition()) {
ERROR("tlp partition is not a block device\n");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
BlockPartitionClient* block = reinterpret_cast<BlockPartitionClient*>(partition_client.get());
std::unique_ptr<fvm::SparseReader> reader;
zx::result<> status = zx::ok();
if (status = zx::make_result(fvm::SparseReader::Create(std::move(payload), &reader));
status.is_error()) {
return status.take_error();
}
LOG("Header Validated - OK\n");
fvm::SparseImage* hdr = reader->Image();
// Acquire an fd to the FVM, either by finding one that already
// exists, or formatting a new one.
zx::result fvm = FvmPartitionFormat(devfs_root, block->block_channel(),
block->controller_channel(), *hdr, BindOption::TryBind);
if (fvm.is_error()) {
ERROR("Couldn't find FVM partition: %s\n", fvm.status_string());
return fvm.take_error();
}
fbl::Array<PartitionInfo> parts(new PartitionInfo[hdr->partition_count], hdr->partition_count);
// Parse the incoming image and calculate its size.
//
// Additionally, delete the old versions of any new partitions.
size_t requested_slices = 0;
if (status = zx::make_result(PreProcessPartitions(fvm.value(), reader, parts, &requested_slices));
status.is_error()) {
ERROR("Failed to validate partitions: %s\n", status.status_string());
return status.take_error();
}
// Get a connection to the fvm's volume manager.
fidl::ClientEnd<volume::VolumeManager> volume_manager;
{
zx::result volume_server = fidl::CreateEndpoints(&volume_manager);
if (volume_server.is_error()) {
return volume_server.take_error();
}
if (fidl::OneWayError status =
fidl::WireCall(fvm.value())->ConnectToDeviceFidl(volume_server.value().TakeChannel());
!status.ok()) {
return zx::error(status.status());
}
}
// Contend with issues from an image that may be too large for this device.
VolumeManagerInfo info;
{
fidl::WireResult result = fidl::WireCall(volume_manager)->GetInfo();
if (!result.ok()) {
ERROR("Failed to call FVM info: %s\n", zx_status_get_string(result.error().status()));
return zx::error(result.error().status());
}
fidl::WireResponse response = result.value();
if (response.status != ZX_OK) {
ERROR("Failed to get FVM info: %s\n", zx_status_get_string(response.status));
return zx::error(response.status);
}
info = *response.info.get();
}
size_t free_slices = info.slice_count - info.assigned_slice_count;
if (info.slice_count < requested_slices) {
char buf[256];
snprintf(buf, sizeof(buf), "Image size (%zu) > Storage size (%zu)",
requested_slices * hdr->slice_size, info.slice_count * hdr->slice_size);
Warn(buf, "Image is too large to be paved to device");
return zx::error(ZX_ERR_NO_SPACE);
}
if (free_slices < requested_slices) {
Warn("Not enough space to non-destructively pave",
"Automatically reinitializing FVM; Expect data loss");
fvm = FvmPartitionFormat(devfs_root, block->block_channel(), block->controller_channel(), *hdr,
BindOption::Reformat);
if (fvm.is_error()) {
ERROR("Couldn't reformat FVM partition: %s\n", fvm.status_string());
return zx::error(ZX_ERR_IO);
}
LOG("FVM Reformatted successfully.\n");
}
LOG("Partitions pre-validated successfully: Enough space exists to pave.\n");
// Actually allocate the storage for the incoming image.
zx::result devices = AllocatePartitions(devfs_root, volume_manager, &parts);
if (devices.is_error()) {
ERROR("Failed to allocate partitions: %s\n", devices.status_string());
return devices.take_error();
}
LOG("Partition space pre-allocated successfully.\n");
constexpr size_t vmo_size = 1 << 20;
fzl::VmoMapper mapping;
zx::vmo vmo;
if (mapping.CreateAndMap(vmo_size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, nullptr, &vmo) != ZX_OK) {
ERROR("Failed to create stream VMO\n");
return zx::error(ZX_ERR_NO_MEMORY);
}
// Now that all partitions are preallocated, begin streaming data to them.
for (size_t p = 0; p < parts.size(); p++) {
const fidl::WireResult result = fidl::WireCall(parts[p].partition)->GetInfo();
if (!result.ok()) {
ERROR("Couldn't get partition block info: %s\n", result.FormatDescription().c_str());
return zx::error(result.status());
}
fit::result response = result.value();
if (response.is_error()) {
ERROR("Couldn't get partition block info: %s\n",
zx_status_get_string(response.error_value()));
return response.take_error();
}
const fuchsia_hardware_block::wire::BlockInfo& info = response.value()->info;
auto [session, server] = fidl::Endpoints<block::Session>::Create();
if (fidl::Status result = fidl::WireCall(parts[p].partition)->OpenSession(std::move(server));
!result.ok()) {
return zx::error(result.status());
}
const fidl::WireResult fifo_result = fidl::WireCall(session)->GetFifo();
if (!fifo_result.ok()) {
return zx::error(fifo_result.status());
}
fit::result fifo_response = fifo_result.value();
if (fifo_response.is_error()) {
return fifo_response.take_error();
}
block_client::Client client(std::move(session), std::move(fifo_response.value()->fifo));
zx::result vmoid = client.RegisterVmo(vmo);
if (vmoid.is_error()) {
return vmoid.take_error();
}
block_fifo_request_t request = {
.command = {.opcode = BLOCK_OPCODE_WRITE, .flags = 0},
.group = 0,
.vmoid = vmoid->get(),
};
LOG("Streaming partition %zu\n", p);
status = zx::make_result(
StreamFvmPartition(reader.get(), &parts[p], mapping, client, info.block_size, &request));
LOG("Done streaming partition %zu\n", p);
if (status.is_error()) {
ERROR("Failed to stream partition status=%d\n", status.error_value());
return status.take_error();
}
if (status = zx::make_result(FlushClient(client)); status.is_error()) {
ERROR("Failed to flush client\n");
return status.take_error();
}
LOG("Done flushing partition %zu\n", p);
}
for (const PartitionInfo& part_info : parts) {
// Upgrade the old partition (currently active) to the new partition (currently
// inactive) so the new partition persists.
auto result = fidl::WireCall(part_info.partition)->GetInstanceGuid();
if (!result.ok() || result.value().status != ZX_OK) {
ERROR("Failed to get unique GUID of new partition\n");
return zx::error(ZX_ERR_BAD_STATE);
}
auto* guid = result.value().guid.get();
fidl::WireResult result2 = fidl::WireCall(volume_manager)->Activate(*guid, *guid);
if (result2.status() != ZX_OK || result2.value().status != ZX_OK) {
ERROR("Failed to upgrade partition\n");
return zx::error(ZX_ERR_IO);
}
}
return zx::ok();
}
// 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 FvmUnbind(const fbl::unique_fd& devfs_root, const char* device) {
size_t len = strnlen(device, PATH_MAX);
constexpr const char* kDevPath = "/dev/";
constexpr size_t kDevPathLen = std::char_traits<char>::length(kDevPath);
if (len == PATH_MAX || len <= kDevPathLen) {
ERROR("Invalid device name: %s\n", device);
return ZX_ERR_INVALID_ARGS;
}
fbl::StringBuffer<PATH_MAX - 1> name_buffer;
name_buffer.Append(device + kDevPathLen);
name_buffer.Append("/fvm/device_controller");
fdio_cpp::UnownedFdioCaller caller(devfs_root.get());
zx::result channel = component::ConnectAt<device::Controller>(caller.directory(), name_buffer);
if (channel.is_error()) {
ERROR("Unable to connect to FVM service: %s on device %s\n", channel.status_string(),
name_buffer.c_str());
return channel.status_value();
}
auto resp = fidl::WireCall(channel.value())->ScheduleUnbind();
if (resp.status() != ZX_OK) {
ERROR("Failed to schedule FVM unbind: %s on device %s\n", zx_status_get_string(resp.status()),
name_buffer.data());
return resp.status();
}
if (resp->is_error()) {
ERROR("FVM unbind failed: %s on device %s\n", zx_status_get_string(resp->error_value()),
name_buffer.data());
return resp->error_value();
}
return ZX_OK;
}
} // namespace paver