| // 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 "paver.h" |
| |
| #include <algorithm> |
| #include <fcntl.h> |
| #include <stdio.h> |
| |
| #include <fbl/auto_call.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/sysconfig/sync-client.h> |
| #include <lib/zx/clock.h> |
| #include <zircon/boot/netboot.h> |
| |
| #include "payload-streamer.h" |
| |
| namespace netsvc { |
| namespace { |
| |
| size_t NB_IMAGE_PREFIX_LEN() { return strlen(NB_IMAGE_PREFIX); } |
| size_t NB_FILENAME_PREFIX_LEN() { return strlen(NB_FILENAME_PREFIX); } |
| |
| zx_status_t ClearSysconfig(const fbl::unique_fd& devfs_root) { |
| std::optional<sysconfig::SyncClient> client; |
| zx_status_t status = sysconfig::SyncClient::Create(devfs_root, &client); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| // We only clear sysconfig on devices with sysconfig partition. |
| return ZX_OK; |
| } else if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Failed to create sysconfig SyncClient.\n"); |
| return status; |
| } |
| constexpr auto kPartition = sysconfig::SyncClient::PartitionType::kSysconfig; |
| size_t size = client->GetPartitionSize(kPartition); |
| |
| // We assume the vmo is zero initialized. |
| zx::vmo vmo; |
| status = zx::vmo::create(fbl::round_up(size, ZX_PAGE_SIZE), 0, &vmo); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Failed to create vmo.\n"); |
| return status; |
| } |
| |
| status = client->WritePartition(kPartition, vmo, 0); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Failed to write to sysconfig partition.\n"); |
| return status; |
| } |
| |
| return ZX_OK; |
| } |
| |
| |
| } // namespace |
| |
| Paver* Paver::Get() { |
| static Paver* instance_ = nullptr; |
| if (instance_ == nullptr) { |
| zx::channel local, remote; |
| auto status = zx::channel::create(0, &local, &remote); |
| if (status != ZX_OK) { |
| return nullptr; |
| } |
| status = fdio_service_connect("/svc", remote.release()); |
| if (status != ZX_OK) { |
| return nullptr; |
| } |
| fbl::unique_fd devfs_root(open("/dev", O_RDWR)); |
| if (!devfs_root) { |
| return nullptr; |
| } |
| |
| instance_ = new Paver(std::move(local), std::move(devfs_root)); |
| } |
| return instance_; |
| } |
| |
| bool Paver::InProgress() { return in_progress_.load(); } |
| zx_status_t Paver::exit_code() { return exit_code_.load(); } |
| void Paver::reset_exit_code() { exit_code_.store(ZX_OK); } |
| |
| int Paver::StreamBuffer() { |
| zx::time last_reported = zx::clock::get_monotonic(); |
| int result = 0; |
| auto callback = [this, &last_reported, &result](void* buf, size_t read_offset, size_t size, |
| size_t* actual) { |
| if (read_offset >= size_) { |
| *actual = 0; |
| return ZX_OK; |
| } |
| sync_completion_reset(&data_ready_); |
| 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. |
| auto status = sync_completion_wait(&data_ready_, timeout_.get()); |
| if (status != ZX_OK) { |
| printf("netsvc: 1 timed out while waiting for data in paver-copy thread\n"); |
| exit_code_.store(status); |
| result = TFTP_ERR_TIMED_OUT; |
| return ZX_ERR_TIMED_OUT; |
| } |
| sync_completion_reset(&data_ready_); |
| write_offset = write_offset_.load(); |
| }; |
| size = std::min(size, write_offset - read_offset); |
| memcpy(buf, buffer() + read_offset, size); |
| *actual = 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; |
| }; |
| |
| fbl::AutoCall cleanup([this, &result]() { |
| unsigned int refcount = std::atomic_fetch_sub(&buf_refcount_, 1u); |
| if (refcount == 1) { |
| buffer_mapper_.Reset(); |
| } |
| |
| paver_svc_.reset(); |
| |
| if (result != 0) { |
| printf("netsvc: copy exited prematurely (%d): expect paver errors\n", result); |
| } |
| |
| in_progress_.store(false); |
| }); |
| |
| zx::channel client, server; |
| auto status = zx::channel::create(0, &client, &server); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: unable to create channel\n"); |
| exit_code_.store(status); |
| return 0; |
| } |
| |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| PayloadStreamer streamer(std::move(server), std::move(callback)); |
| loop.StartThread("payload-streamer"); |
| |
| // Blocks untils paving is complete. |
| auto res = paver_svc_->WriteVolumes(std::move(client)); |
| status = res.status() == ZX_OK ? res.value().status : res.status(); |
| |
| exit_code_.store(status); |
| return 0; |
| } |
| |
| zx_status_t Paver::WriteAsset(::llcpp::fuchsia::mem::Buffer buffer) { |
| bool abr_supported; |
| // First find out whether or not ABR is supported. |
| { |
| auto result = paver_svc_->QueryActiveConfiguration(); |
| auto status = |
| result.ok() ? (result->result.is_err() ? result->result.err() : ZX_OK) : result.status(); |
| if (status == ZX_ERR_NOT_SUPPORTED) { |
| auto init_result = paver_svc_->InitializeAbr(); |
| status = init_result.ok() ? init_result->status : init_result.status(); |
| abr_supported = false; |
| if (status == ZX_OK) { |
| abr_supported = true; |
| } else if (status != ZX_ERR_NOT_SUPPORTED) { |
| fprintf(stderr, "netsvc: Unable to initialize ABR.\n"); |
| return status; |
| } |
| } else if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Unable to query active configuration.\n"); |
| return status; |
| } else { |
| abr_supported = true; |
| } |
| } |
| // Make sure to mark the configuration we are about to pave as no longer bootable. |
| if (abr_supported && configuration_ != ::llcpp::fuchsia::paver::Configuration::RECOVERY) { |
| auto result = paver_svc_->SetConfigurationUnbootable(configuration_); |
| auto status = result.ok() ? result->status : result.status(); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Unable to set configuration as unbootable.\n"); |
| return status; |
| } |
| } |
| { |
| auto result = paver_svc_->WriteAsset(configuration_, asset_, std::move(buffer)); |
| auto status = result.ok() ? result->status : result.status(); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Unable to write asset.\n"); |
| return status; |
| } |
| } |
| // Set configuration A as default. |
| // We assume that verified boot metadata asset will only be written after the kernel asset. |
| if (!abr_supported || configuration_ != ::llcpp::fuchsia::paver::Configuration::A || |
| asset_ != ::llcpp::fuchsia::paver::Asset::VERIFIED_BOOT_METADATA) { |
| return ZX_OK; |
| } |
| { |
| auto result = paver_svc_->SetConfigurationActive(configuration_); |
| auto status = result.ok() ? result->status : result.status(); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Unable to set configuration as active.\n"); |
| return status; |
| } |
| } |
| return ClearSysconfig(devfs_root_); |
| } |
| |
| int Paver::MonitorBuffer() { |
| int result = TFTP_NO_ERROR; |
| |
| fbl::AutoCall cleanup([this, &result]() { |
| unsigned int refcount = std::atomic_fetch_sub(&buf_refcount_, 1u); |
| if (refcount == 1) { |
| buffer_mapper_.Reset(); |
| } |
| |
| paver_svc_.reset(); |
| |
| if (result != 0) { |
| printf("netsvc: copy exited prematurely (%d): expect paver errors\n", result); |
| } |
| |
| in_progress_.store(false); |
| }); |
| |
| 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"); |
| exit_code_.store(status); |
| result = TFTP_ERR_TIMED_OUT; |
| return result; |
| } |
| sync_completion_reset(&data_ready_); |
| write_ndx = write_offset_.load(); |
| } while (write_ndx < size_); |
| |
| zx::vmo dup; |
| auto status = buffer_mapper_.vmo().duplicate(ZX_RIGHT_SAME_RIGHTS, &dup); |
| if (status != ZX_OK) { |
| exit_code_.store(status); |
| return 0; |
| } |
| |
| ::llcpp::fuchsia::mem::Buffer buffer = { |
| .vmo = std::move(dup), |
| .size = buffer_mapper_.size(), |
| }; |
| |
| // Blocks untils paving is complete. |
| switch (command_) { |
| case Command::kDataFile: { |
| auto res = |
| paver_svc_->WriteDataFile(fidl::StringView(path_, strlen(path_)), std::move(buffer)); |
| status = res.status() == ZX_OK ? res.value().status : res.status(); |
| break; |
| } |
| case Command::kBootloader: { |
| auto res = paver_svc_->WriteBootloader(std::move(buffer)); |
| status = res.status() == ZX_OK ? res.value().status : res.status(); |
| break; |
| } |
| case Command::kAsset: |
| status = WriteAsset(std::move(buffer)); |
| break; |
| default: |
| result = TFTP_ERR_INTERNAL; |
| status = ZX_ERR_INTERNAL; |
| break; |
| } |
| exit_code_.store(status); |
| |
| return 0; |
| } |
| |
| tftp_status Paver::OpenWrite(const char* filename, size_t size) { |
| // Paving an image to disk. |
| if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_FVM_HOST_FILENAME)) { |
| printf("netsvc: Running FVM Paver\n"); |
| command_ = Command::kFvm; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_BOOTLOADER_HOST_FILENAME)) { |
| printf("netsvc: Running BOOTLOADER Paver\n"); |
| command_ = Command::kBootloader; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_ZIRCONA_HOST_FILENAME)) { |
| printf("netsvc: Running ZIRCON-A Paver\n"); |
| command_ = Command::kAsset; |
| configuration_ = ::llcpp::fuchsia::paver::Configuration::A; |
| asset_ = ::llcpp::fuchsia::paver::Asset::KERNEL; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_ZIRCONB_HOST_FILENAME)) { |
| printf("netsvc: Running ZIRCON-B Paver\n"); |
| command_ = Command::kAsset; |
| configuration_ = ::llcpp::fuchsia::paver::Configuration::B; |
| asset_ = ::llcpp::fuchsia::paver::Asset::KERNEL; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_ZIRCONR_HOST_FILENAME)) { |
| printf("netsvc: Running ZIRCON-R Paver\n"); |
| command_ = Command::kAsset; |
| configuration_ = ::llcpp::fuchsia::paver::Configuration::RECOVERY; |
| asset_ = ::llcpp::fuchsia::paver::Asset::KERNEL; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_VBMETAA_HOST_FILENAME)) { |
| printf("netsvc: Running VBMETA-A Paver\n"); |
| command_ = Command::kAsset; |
| configuration_ = ::llcpp::fuchsia::paver::Configuration::A; |
| asset_ = ::llcpp::fuchsia::paver::Asset::VERIFIED_BOOT_METADATA; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_VBMETAB_HOST_FILENAME)) { |
| printf("netsvc: Running VBMETA-B Paver\n"); |
| command_ = Command::kAsset; |
| configuration_ = ::llcpp::fuchsia::paver::Configuration::B; |
| asset_ = ::llcpp::fuchsia::paver::Asset::VERIFIED_BOOT_METADATA; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_VBMETAR_HOST_FILENAME)) { |
| printf("netsvc: Running VBMETA-R Paver\n"); |
| command_ = Command::kAsset; |
| configuration_ = ::llcpp::fuchsia::paver::Configuration::RECOVERY; |
| asset_ = ::llcpp::fuchsia::paver::Asset::VERIFIED_BOOT_METADATA; |
| } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN(), NB_SSHAUTH_HOST_FILENAME)) { |
| printf("netsvc: Installing SSH authorized_keys\n"); |
| command_ = Command::kDataFile; |
| strncpy(path_, "ssh/authorized_keys", PATH_MAX); |
| } 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\n"); |
| return status; |
| } |
| fbl::AutoCall buffer_cleanup([this]() { buffer_mapper_.Reset(); }); |
| |
| zx::channel paver_local, paver_remote; |
| status = zx::channel::create(0, &paver_local, &paver_remote); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Unable to create channel pair.\n"); |
| return TFTP_ERR_IO; |
| } |
| status = fdio_service_connect_at(svc_root_.get(), ::llcpp::fuchsia::paver::Paver::Name, |
| paver_remote.release()); |
| if (status != ZX_OK) { |
| fprintf(stderr, "netsvc: Unable to open /svc/%s.\n", ::llcpp::fuchsia::paver::Paver::Name); |
| return TFTP_ERR_IO; |
| } |
| |
| paver_svc_.emplace(std::move(paver_local)); |
| fbl::AutoCall svc_cleanup([&]() { paver_svc_.reset(); }); |
| |
| size_ = size; |
| |
| buf_refcount_.store(2u); |
| write_offset_.store(0ul); |
| exit_code_.store(0); |
| in_progress_.store(true); |
| |
| sync_completion_reset(&data_ready_); |
| |
| auto thread_fn = command_ == Command::kFvm |
| ? [](void* arg) { return static_cast<Paver*>(arg)->StreamBuffer(); } |
| : [](void* arg) { return static_cast<Paver*>(arg)->MonitorBuffer(); }; |
| if (thrd_create(&buf_thrd_, thread_fn, this) != thrd_success) { |
| fprintf(stderr, "netsvc: unable to launch buffer stream/monitor thread\n"); |
| status = ZX_ERR_NO_RESOURCES; |
| return status; |
| } |
| thrd_detach(buf_thrd_); |
| svc_cleanup.cancel(); |
| buffer_cleanup.cancel(); |
| |
| return TFTP_NO_ERROR; |
| } |
| |
| tftp_status Paver::Write(const void* data, size_t* length, off_t offset) { |
| if (!InProgress()) { |
| printf("netsvc: paver exited prematurely with %d\n", exit_code()); |
| reset_exit_code(); |
| 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() { |
| unsigned int refcount = std::atomic_fetch_sub(&buf_refcount_, 1u); |
| if (refcount == 1) { |
| buffer_mapper_.Reset(); |
| } |
| // TODO: Signal thread to wake up rather than wait for it to timeout if |
| // stream is closed before write is complete? |
| } |
| |
| } // namespace netsvc |