blob: b42ebb95b69429d53af7762776d595ed14b18ddb [file] [log] [blame]
// Copyright 2019 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 "src/bringup/bin/netsvc/paver.h"
#include <fcntl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/async-loop/loop.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/fit/defer.h>
#include <lib/netboot/netboot.h>
#include <lib/stdcompat/string_view.h>
#include <lib/zx/clock.h>
#include <stdio.h>
#include <zircon/errors.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <algorithm>
#include <optional>
#include <string_view>
#include <fbl/algorithm.h>
#include "src/bringup/bin/netsvc/payload-streamer.h"
namespace netsvc {
namespace {
template <typename T>
zx::result<fidl::ClientEnd<T>> ConnectToDevfs(fidl::ClientEnd<fuchsia_io::Directory>& dev,
std::string_view full_path) {
// `dev` allows dependency injection in tests. However "/dev" is not guaranteed to be
// backed by a channel in the local namespace, preventing `dev_root` from always being provided.
// In particular, it may be that a subdirectory of "/dev" is installed in the namespace - this
// would result in "/dev" itself being a local node in the namespace, and not backed by a
// channel. We handle both cases by using `dev` only when it has been provided and
// otherwise connecting through the namespace.
if (!dev.is_valid()) {
return component::Connect<T>(full_path);
}
constexpr std::string_view kDevfsPrefix = "/dev/";
return component::ConnectAt<T>(dev, full_path.substr(kDevfsPrefix.size()));
}
} // namespace
std::optional<Paver> Paver::instance_ = std::nullopt;
Paver& Paver::Get() {
if (instance_.has_value()) {
return instance_.value();
}
return instance_.emplace(fidl::ClientEnd<fuchsia_io::Directory>{},
fidl::ClientEnd<fuchsia_io::Directory>{});
}
void Paver::Reset() { instance_.reset(); }
std::shared_future<zx_status_t> Paver::exit_code() {
if (exit_code_future_.has_value()) {
return exit_code_future_.value();
}
return exit_code_future_.emplace(exit_code_.get_future());
}
int Paver::StreamBuffer() {
zx::time last_reported = zx::clock::get_monotonic();
size_t decommitted_offset = 0;
int result = 0;
auto callback = [this, &last_reported, &decommitted_offset, &result](
void* buf, size_t read_offset, size_t size, size_t* actual) {
if (read_offset >= size_) {
*actual = 0;
return ZX_OK;
}
size_t write_offset = write_offset_.load();
while (write_offset == read_offset) {
// Wait for more data to be written -- we are allowed up to 3 tftp timeouts before
// a connection is dropped, so we should wait at least that long before giving up.
if (zx_status_t status = sync_completion_wait(&data_ready_, timeout_.get());
status != ZX_OK) {
printf("netsvc: 1 timed out while waiting for data in paver-copy thread\n");
result = TFTP_ERR_TIMED_OUT;
return status;
}
sync_completion_reset(&data_ready_);
if (aborted_) {
printf("netsvc: 1 paver aborted, exiting copy thread\n");
result = TFTP_ERR_BAD_STATE;
return ZX_ERR_CANCELED;
}
write_offset = write_offset_.load();
};
size = std::min(size, write_offset - read_offset);
memcpy(buf, buffer() + read_offset, size);
*actual = size;
// Best effort try to decommit pages we have already copied. This will prevent us from
// running out of memory.
ZX_ASSERT(read_offset + size > decommitted_offset);
const size_t decommit_size =
fbl::round_down(read_offset + size - decommitted_offset, zx_system_get_page_size());
// TODO(surajmalhotra): Tune this in case we decommit too aggressively.
if (decommit_size > 0) {
if (auto status = buffer_mapper_.vmo().op_range(ZX_VMO_OP_DECOMMIT, decommitted_offset,
decommit_size, nullptr, 0);
status != ZX_OK) {
printf("netsvc: Failed to decommit offset 0x%zx with size: 0x%zx: %s\n", decommitted_offset,
decommit_size, zx_status_get_string(status));
}
decommitted_offset += decommit_size;
}
zx::time curr_time = zx::clock::get_monotonic();
if (curr_time - last_reported >= zx::sec(1)) {
float complete = (static_cast<float>(read_offset) / static_cast<float>(size_)) * 100.f;
printf("netsvc: paver write progress %0.1f%%\n", complete);
last_reported = curr_time;
}
return ZX_OK;
};
auto cleanup = fit::defer([this, &result]() {
ClearBufferRef(kBufferRefWorker);
paver_svc_ = {};
fshost_admin_svc_ = {};
if (result != 0) {
printf("netsvc: copy exited prematurely (%d): expect paver errors\n", result);
}
});
zx::result data_sink = fidl::CreateEndpoints<fuchsia_paver::DataSink>();
if (data_sink.is_error()) {
fprintf(stderr, "netsvc: unable to create channel: %s\n", data_sink.status_string());
return data_sink.status_value();
}
fidl::Status res = paver_svc_->FindDataSink(std::move(data_sink->server));
if (!res.ok()) {
fprintf(stderr, "netsvc: unable to find data sink: %s\n", res.FormatDescription().c_str());
return res.status();
}
zx::result payload_stream = fidl::CreateEndpoints<fuchsia_paver::PayloadStream>();
if (payload_stream.is_error()) {
fprintf(stderr, "netsvc: unable to create channel: %s\n", payload_stream.status_string());
return payload_stream.status_value();
}
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
PayloadStreamer streamer(std::move(payload_stream->server), std::move(callback));
loop.StartThread("payload-streamer");
// Blocks until paving is complete.
fidl::WireResult res2 =
fidl::WireCall(data_sink->client)->WriteVolumes(std::move(payload_stream->client));
zx_status_t status = res2.ok() ? res2.value().status : res2.status();
// Shutdown the loop. Destructor ordering will destroy the streamer first, so
// we must force the loop to stop before then.
loop.Shutdown();
loop.JoinThreads();
return status;
}
namespace {
using WriteFirmwareResult = fidl::WireResult<fuchsia_paver::DataSink::WriteFirmware>;
zx_status_t ProcessWriteFirmwareResult(const WriteFirmwareResult& res, const char* firmware_type) {
if (!res.ok()) {
return res.status();
}
if (res.value().result.is_status()) {
return res.value().result.status();
}
if (res.value().result.is_unsupported()) {
// Log a message but just skip this, we want to keep going so that we
// can add new firmware types in the future without breaking older
// paver versions.
printf("netsvc: skipping unsupported firmware type '%s'\n", firmware_type);
return ZX_OK;
}
// We must have added another union field but forgot to update this code.
fprintf(stderr, "netsvc: unknown WriteFirmware result\n");
return ZX_ERR_INTERNAL;
}
} // namespace
zx_status_t Paver::WriteABImage(fidl::WireSyncClient<fuchsia_paver::DataSink> data_sink,
fuchsia_mem::wire::Buffer buffer) {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_paver::BootManager>();
if (endpoints.is_error()) {
fprintf(stderr, "netsvc: unable to create channel: %s\n", endpoints.status_string());
return endpoints.status_value();
}
fidl::Status res = paver_svc_->FindBootManager(std::move(endpoints->server));
if (!res.ok()) {
fprintf(stderr, "netsvc: unable to find boot manager: %s\n", res.FormatDescription().c_str());
return res.status();
}
fidl::WireSyncClient<fuchsia_paver::BootManager> boot_manager =
fidl::WireSyncClient(std::move(endpoints->client));
// First find out whether or not ABR is supported.
{
auto result = boot_manager->QueryActiveConfiguration();
if (result.status() != ZX_OK) {
boot_manager = {};
}
}
// Make sure to mark the configuration we are about to pave as no longer bootable.
if (boot_manager && configuration_ != fuchsia_paver::wire::Configuration::kRecovery) {
auto result = boot_manager->SetConfigurationUnbootable(configuration_);
auto status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to set configuration as unbootable.\n");
return status;
}
}
if (command_ == Command::kAsset) {
auto result = data_sink->WriteAsset(configuration_, asset_, std::move(buffer));
auto status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to write asset. %s\n", zx_status_get_string(status));
return status;
}
} else if (command_ == Command::kFirmware) {
auto result = data_sink->WriteFirmware(
configuration_, fidl::StringView::FromExternal(firmware_type_), std::move(buffer));
if (auto status = ProcessWriteFirmwareResult(result, firmware_type_); status != ZX_OK) {
return status;
}
}
// Set configuration A/B as default.
// We assume that verified boot metadata asset will only be written after the kernel asset.
if (!boot_manager || configuration_ == fuchsia_paver::wire::Configuration::kRecovery ||
command_ == Command::kFirmware ||
asset_ != fuchsia_paver::wire::Asset::kVerifiedBootMetadata) {
if (boot_manager) {
auto res = boot_manager->Flush();
auto status_sync = res.ok() ? res.value().status : res.status();
if (status_sync != ZX_OK) {
fprintf(stderr, "netsvc: failed to sync A/B/R configuration. %s\n",
zx_status_get_string(status_sync));
return status_sync;
}
}
return ZX_OK;
}
{
auto result = boot_manager->SetConfigurationActive(configuration_);
auto status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to set configuration as active.\n");
return status;
}
}
{
auto opposite = configuration_ == fuchsia_paver::wire::Configuration::kA
? fuchsia_paver::wire::Configuration::kB
: fuchsia_paver::wire::Configuration::kA;
auto result = boot_manager->SetConfigurationUnbootable(opposite);
auto status = result.ok() ? result.value().status : result.status();
if (status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to set opposite configuration as unbootable.\n");
return status;
}
}
// TODO(https://fxbug.dev/42124259): The following two syncs are called everytime WriteAsset is called, which
// is not optimal for reducing NAND PE cycles. Ideally, we want to sync when all assets, A/B
// configuration have been written to buffer. Find a safe time and place for sync.
{
auto res = data_sink->Flush();
auto status = res.ok() ? res.value().status : res.status();
if (status != ZX_OK) {
fprintf(stderr, "netsvc: failed to flush data_sink. %s\n", zx_status_get_string(status));
return status;
}
}
if (boot_manager) {
auto res = boot_manager->Flush();
auto status = res.ok() ? res.value().status : res.status();
if (status != ZX_OK) {
fprintf(stderr, "netsvc: failed to flush A/B/R configuration. %s\n",
zx_status_get_string(status));
return status;
}
}
return ClearSysconfig();
}
zx_status_t Paver::ClearSysconfig() {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_paver::Sysconfig>();
if (endpoints.is_error()) {
fprintf(stderr, "netsvc: unable to create channel\n");
return endpoints.status_value();
}
fidl::Status status_find_sysconfig = paver_svc_->FindSysconfig(std::move(endpoints->server));
if (!status_find_sysconfig.ok()) {
fprintf(stderr, "netsvc: unable to find sysconfig\n");
return status_find_sysconfig.status();
}
fidl::WireSyncClient client{std::move(endpoints->client)};
auto wipe_result = client->Wipe();
auto wipe_status =
wipe_result.status() == ZX_OK ? wipe_result.value().status : wipe_result.status();
if (wipe_status == ZX_ERR_PEER_CLOSED) {
// If the first fidl call after connection returns ZX_ERR_PEER_CLOSED,
// consider it as not supported.
fprintf(stderr, "netsvc: sysconfig is not supported\n");
return ZX_OK;
}
if (wipe_status != ZX_OK) {
fprintf(stderr, "netsvc: Failed to wipe sysconfig partition.\n");
return wipe_status;
}
auto flush_result = client->Flush();
auto flush_status =
flush_result.status() == ZX_OK ? flush_result.value().status : flush_result.status();
if (flush_status != ZX_OK) {
fprintf(stderr, "netsvc: Failed to flush sysconfig partition.\n");
return flush_status;
}
return ZX_OK;
}
zx_status_t Paver::OpenDataSink(fuchsia_mem::wire::Buffer buffer,
fidl::WireSyncClient<fuchsia_paver::DynamicDataSink>* data_sink) {
netboot_block_device_t partition_info = {};
auto status = buffer.vmo.read(&partition_info, 0, sizeof(partition_info));
if (status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to read from vmo\n");
return status;
}
if (partition_info.block_device_path[NETBOOT_PATH_MAX - 1] != '\0') {
fprintf(stderr, "netsvc: Invalid block device path specified\n");
return ZX_ERR_INVALID_ARGS;
}
const std::string_view block_device_path{partition_info.block_device_path};
constexpr std::string_view kDevfsPrefix = "/dev/";
if (!cpp20::starts_with(block_device_path, kDevfsPrefix)) {
fprintf(stderr, "netsvc: Invalid block device path specified %s\n",
partition_info.block_device_path);
return ZX_ERR_INVALID_ARGS;
}
zx::result client_end =
ConnectToDevfs<fuchsia_hardware_block::Block>(dev_root_, block_device_path);
if (client_end.is_error()) {
fprintf(stderr, "netsvc: Unable to open %s.\n", partition_info.block_device_path);
return client_end.status_value();
}
std::string controller_path = std::string(block_device_path) + "/device_controller";
zx::result controller = ConnectToDevfs<fuchsia_device::Controller>(dev_root_, controller_path);
if (client_end.is_error()) {
fprintf(stderr, "netsvc: Unable to open controller %s\n", controller_path.c_str());
return controller.status_value();
}
zx::result endpoints = fidl::CreateEndpoints<fuchsia_paver::DynamicDataSink>();
if (endpoints.is_error()) {
fprintf(stderr, "netsvc: unable to create channel.\n");
return endpoints.status_value();
}
fidl::Status res = paver_svc_->UseBlockDevice(
std::move(client_end.value()), std::move(controller.value()), std::move(endpoints->server));
if (!res.ok()) {
fprintf(stderr, "netsvc: unable to use block device.\n");
return res.status();
}
data_sink->Bind(std::move(endpoints->client));
return ZX_OK;
}
zx_status_t Paver::InitPartitionTables(fuchsia_mem::wire::Buffer buffer) {
fidl::WireSyncClient<fuchsia_paver::DynamicDataSink> data_sink;
if (zx_status_t status = OpenDataSink(std::move(buffer), &data_sink); status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to open data sink.\n");
return status;
}
auto result = data_sink->InitializePartitionTables();
if (zx_status_t status = result.ok() ? result.value().status : result.status(); status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to initialize partition tables: %s\n",
zx_status_get_string(status));
return status;
}
return ZX_OK;
}
zx_status_t Paver::WipePartitionTables(fuchsia_mem::wire::Buffer buffer) {
fidl::WireSyncClient<fuchsia_paver::DynamicDataSink> data_sink;
if (zx_status_t status = OpenDataSink(std::move(buffer), &data_sink); status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to open data sink.\n");
return status;
}
auto result = data_sink->WipePartitionTables();
if (zx_status_t status = result.ok() ? result.value().status : result.status(); status != ZX_OK) {
fprintf(stderr, "netsvc: Unable to wipe partition tables.\n");
return status;
}
return ZX_OK;
}
zx_status_t Paver::MonitorBuffer() {
int result = TFTP_NO_ERROR;
auto cleanup = fit::defer([this, &result]() {
ClearBufferRef(kBufferRefWorker);
paver_svc_ = {};
fshost_admin_svc_ = {};
if (result != 0) {
printf("netsvc: copy exited prematurely (%d): expect paver errors\n", result);
}
});
size_t write_ndx = 0;
do {
// Wait for more data to be written -- we are allowed up to 3 tftp timeouts before
// a connection is dropped, so we should wait at least that long before giving up.
auto status = sync_completion_wait(&data_ready_, timeout_.get());
if (status != ZX_OK) {
printf("netsvc: 2 timed out while waiting for data in paver-copy thread\n");
result = TFTP_ERR_TIMED_OUT;
return status;
}
sync_completion_reset(&data_ready_);
if (aborted_) {
printf("netsvc: 2 paver aborted, exiting copy thread\n");
result = TFTP_ERR_BAD_STATE;
return ZX_ERR_CANCELED;
}
write_ndx = write_offset_.load();
} while (write_ndx < size_);
zx::vmo dup;
if (zx_status_t status = buffer_mapper_.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup);
status != ZX_OK) {
return status;
}
if (zx_status_t status = dup.set_prop_content_size(buffer_mapper_.size()); status != ZX_OK) {
return status;
}
fuchsia_mem::wire::Buffer buffer = {
.vmo = std::move(dup),
.size = buffer_mapper_.size(),
};
// We need to open a specific data sink rather than find the default for partition table
// management commands.
switch (command_) {
case Command::kInitPartitionTables:
return InitPartitionTables(std::move(buffer));
case Command::kWipePartitionTables:
return WipePartitionTables(std::move(buffer));
default:
break;
};
zx::result endpoints = fidl::CreateEndpoints<fuchsia_paver::DataSink>();
if (endpoints.is_error()) {
fprintf(stderr, "netsvc: unable to create channel: %s\n", endpoints.status_string());
return endpoints.status_value();
}
fidl::Status res = paver_svc_->FindDataSink(std::move(endpoints->server));
if (!res.ok()) {
fprintf(stderr, "netsvc: unable to find data sink: %s\n", res.FormatDescription().c_str());
return res.status();
}
fidl::WireSyncClient data_sink{std::move(endpoints->client)};
// Blocks until paving is complete.
switch (command_) {
case Command::kDataFile: {
auto res = fshost_admin_svc_->WriteDataFile(fidl::StringView::FromExternal(path_),
std::move(buffer.vmo));
return (res.status() == ZX_OK ? (res.ok() ? ZX_OK : res->error_value()) : res.status());
}
case Command::kFirmware:
[[fallthrough]];
case Command::kAsset:
return WriteABImage(std::move(data_sink), std::move(buffer));
case Command::kFvmFull: {
auto res = data_sink->WriteOpaqueVolume(std::move(buffer));
return (res.status() == ZX_OK ? (res.ok() ? ZX_OK : res->error_value()) : res.status());
}
case Command::kFxfsSparse: {
auto res = data_sink->WriteSparseVolume(std::move(buffer));
return (res.status() == ZX_OK ? (res.ok() ? ZX_OK : res->error_value()) : res.status());
}
default:
result = TFTP_ERR_INTERNAL;
return ZX_ERR_INTERNAL;
}
}
namespace {
// If |string| starts with |prefix|, returns |string| with |prefix| removed.
// Otherwise, returns std::nullopt.
std::optional<std::string_view> WithoutPrefix(std::string_view string, std::string_view prefix) {
if (string.size() >= prefix.size() && (string.compare(0, prefix.size(), prefix) == 0)) {
return std::string_view(string.data() + prefix.size(), string.size() - prefix.size());
}
return std::nullopt;
}
} // namespace
tftp_status Paver::ProcessAsFirmwareImage(std::string_view host_filename) {
struct {
const char* prefix;
const char* config_suffix;
fuchsia_paver::wire::Configuration config;
} matches[] = {
{NETBOOT_FIRMWARE_HOST_FILENAME_PREFIX, "", fuchsia_paver::wire::Configuration::kA},
{NETBOOT_FIRMWAREA_HOST_FILENAME_PREFIX, "-A", fuchsia_paver::wire::Configuration::kA},
{NETBOOT_FIRMWAREB_HOST_FILENAME_PREFIX, "-B", fuchsia_paver::wire::Configuration::kB},
{NETBOOT_FIRMWARER_HOST_FILENAME_PREFIX, "-R", fuchsia_paver::wire::Configuration::kRecovery},
};
for (auto& match : matches) {
auto type = WithoutPrefix(host_filename, match.prefix);
if (!type.has_value()) {
continue;
}
printf("netsvc: Running FIRMWARE%s Paver (firmware type '%.*s')\n", match.config_suffix,
static_cast<int>(type->size()), type->data());
if (type->length() > sizeof(firmware_type_) - 1) {
fprintf(stderr, "netsvc: Firmware type '%.*s' is too long (max %zu, including NUL)\n",
static_cast<int>(type->size()), type->data(), sizeof(firmware_type_));
return TFTP_ERR_INVALID_ARGS;
}
memcpy(firmware_type_, type->data(), type->length());
firmware_type_[type->length()] = '\0';
configuration_ = match.config;
command_ = Command::kFirmware;
return TFTP_NO_ERROR;
}
return TFTP_ERR_NOT_FOUND;
}
namespace {
template <typename Protocol>
zx::result<fidl::ClientEnd<Protocol>> ConnectAt(
fidl::UnownedClientEnd<fuchsia_io::Directory> svc_dir) {
if (svc_dir.is_valid()) {
return component::ConnectAt<Protocol>(svc_dir);
}
return component::Connect<Protocol>();
}
} // namespace
tftp_status Paver::OpenWrite(std::string_view filename, size_t size, zx::duration timeout) {
// Skip past the NETBOOT_IMAGE_PREFIX prefix.
std::string_view host_filename;
if (auto without_prefix = WithoutPrefix(filename, NETBOOT_IMAGE_PREFIX);
without_prefix.has_value()) {
host_filename = without_prefix.value();
} else {
fprintf(stderr, "netsvc: Missing '%s' prefix in '%.*s'\n", NETBOOT_IMAGE_PREFIX,
static_cast<int>(filename.size()), filename.data());
return TFTP_ERR_IO;
}
// Paving an image to disk.
if (host_filename == NETBOOT_FVM_HOST_FILENAME) {
printf("netsvc: Running FVM full Paver\n");
command_ = Command::kFvmFull;
} else if (host_filename == NETBOOT_FXFS_HOST_FILENAME) {
printf("netsvc: Running Fxfs Paver\n");
command_ = Command::kFxfsSparse;
} else if (host_filename == NETBOOT_FVM_SPARSE_HOST_FILENAME) {
printf("netsvc: Running FVM Paver\n");
command_ = Command::kFvmSparse;
} else if (host_filename == NETBOOT_BOOTLOADER_HOST_FILENAME) {
// WriteBootloader() has been replaced by WriteFirmware() with an empty
// firmware type, but keep this function around for backwards-compatibility
// until we don't use it anymore.
printf("netsvc: Running BOOTLOADER Paver (firmware type '')\n");
command_ = Command::kFirmware;
configuration_ = fuchsia_paver::wire::Configuration::kA;
firmware_type_[0] = '\0';
} else if (auto status = ProcessAsFirmwareImage(host_filename); status != TFTP_ERR_NOT_FOUND) {
if (status != TFTP_NO_ERROR) {
return status;
}
} else if (host_filename == NETBOOT_ZIRCONA_HOST_FILENAME) {
printf("netsvc: Running ZIRCON-A Paver\n");
command_ = Command::kAsset;
configuration_ = fuchsia_paver::wire::Configuration::kA;
asset_ = fuchsia_paver::wire::Asset::kKernel;
} else if (host_filename == NETBOOT_ZIRCONB_HOST_FILENAME) {
printf("netsvc: Running ZIRCON-B Paver\n");
command_ = Command::kAsset;
configuration_ = fuchsia_paver::wire::Configuration::kB;
asset_ = fuchsia_paver::wire::Asset::kKernel;
} else if (host_filename == NETBOOT_ZIRCONR_HOST_FILENAME) {
printf("netsvc: Running ZIRCON-R Paver\n");
command_ = Command::kAsset;
configuration_ = fuchsia_paver::wire::Configuration::kRecovery;
asset_ = fuchsia_paver::wire::Asset::kKernel;
} else if (host_filename == NETBOOT_VBMETAA_HOST_FILENAME) {
printf("netsvc: Running VBMETA-A Paver\n");
command_ = Command::kAsset;
configuration_ = fuchsia_paver::wire::Configuration::kA;
asset_ = fuchsia_paver::wire::Asset::kVerifiedBootMetadata;
} else if (host_filename == NETBOOT_VBMETAB_HOST_FILENAME) {
printf("netsvc: Running VBMETA-B Paver\n");
command_ = Command::kAsset;
configuration_ = fuchsia_paver::wire::Configuration::kB;
asset_ = fuchsia_paver::wire::Asset::kVerifiedBootMetadata;
} else if (host_filename == NETBOOT_VBMETAR_HOST_FILENAME) {
printf("netsvc: Running VBMETA-R Paver\n");
command_ = Command::kAsset;
configuration_ = fuchsia_paver::wire::Configuration::kRecovery;
asset_ = fuchsia_paver::wire::Asset::kVerifiedBootMetadata;
} else if (host_filename == NETBOOT_SSHAUTH_HOST_FILENAME) {
printf("netsvc: Installing SSH authorized_keys\n");
command_ = Command::kDataFile;
strncpy(path_, "ssh/authorized_keys", PATH_MAX);
} else if (host_filename == NETBOOT_INIT_PARTITION_TABLES_HOST_FILENAME) {
if (size < sizeof(netboot_block_device_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
printf("netsvc: Initializing partition tables\n");
command_ = Command::kInitPartitionTables;
} else if (host_filename == NETBOOT_WIPE_PARTITION_TABLES_HOST_FILENAME) {
if (size < sizeof(netboot_block_device_t)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
printf("netsvc: Wiping partition tables\n");
command_ = Command::kWipePartitionTables;
} else {
fprintf(stderr, "netsvc: Unknown Paver\n");
return TFTP_ERR_IO;
}
auto status = buffer_mapper_.CreateAndMap(size, "paver");
if (status != ZX_OK) {
printf("netsvc: unable to allocate and map buffer. Size - %lu, Error - %d\n", size, status);
return status;
}
auto buffer_cleanup = fit::defer([this]() { buffer_mapper_.Reset(); });
zx::result paver = ConnectAt<fuchsia_paver::Paver>(svc_root_);
if (paver.is_error()) {
fprintf(stderr, "netsvc: Unable to open /svc/%s.\n",
fidl::DiscoverableProtocolName<fuchsia_paver::Paver>);
return TFTP_ERR_IO;
}
zx::result fshost = ConnectAt<fuchsia_fshost::Admin>(svc_root_);
if (fshost.is_error()) {
fprintf(stderr, "netsvc: Unable to open /svc/%s.\n",
fidl::DiscoverableProtocolName<fuchsia_fshost::Admin>);
return TFTP_ERR_IO;
}
paver_svc_ = fidl::WireSyncClient(std::move(*paver));
fshost_admin_svc_ = fidl::WireSyncClient(std::move(*fshost));
auto svc_cleanup = fit::defer([&]() {
fshost_admin_svc_ = {};
paver_svc_ = {};
});
size_ = size;
buffer_refs_.store(kBufferRefWorker | kBufferRefApi);
write_offset_.store(0ul);
exit_code_future_.reset();
exit_code_ = {};
// Use a fixed multiplier on requested timeout based on empirical tests for
// paving stability.
timeout_ = timeout * 5;
aborted_ = false;
sync_completion_reset(&data_ready_);
threads_.emplace_back([this]() {
exit_code_.set_value_at_thread_exit(command_ == Command::kFvmSparse ? StreamBuffer()
: MonitorBuffer());
});
svc_cleanup.cancel();
buffer_cleanup.cancel();
return TFTP_NO_ERROR;
}
tftp_status Paver::Write(const void* data, size_t* length, off_t offset) {
std::shared_future fut = exit_code();
if (fut.wait_for(std::chrono::nanoseconds::zero()) == std::future_status::ready) {
printf("netsvc: paver exited prematurely with %s. Check the debuglog for more information.\n",
zx_status_get_string(fut.get()));
return TFTP_ERR_IO;
}
if ((static_cast<size_t>(offset) > size_) || (offset + *length) > size_) {
return TFTP_ERR_INVALID_ARGS;
}
memcpy(&buffer()[offset], data, *length);
size_t new_offset = offset + *length;
write_offset_.store(new_offset);
// Wake the paver thread, if it is waiting for data
sync_completion_signal(&data_ready_);
return TFTP_NO_ERROR;
}
void Paver::Close() { ClearBufferRef(kBufferRefApi); }
void Paver::Abort() {
aborted_ = true;
sync_completion_signal(&data_ready_);
}
void Paver::ClearBufferRef(uint32_t ref) {
if (buffer_refs_.fetch_and(~ref) == ref) {
buffer_mapper_.Reset();
}
}
} // namespace netsvc