[netsvc] Partial rewrite and add tests
Also call directly into paving service rather go through
install-disk-image.
Tested: runtests -t netsvc-test
Tested: Paved astro, sherlock, nuc, pixelbook
Change-Id: I8976b773e8a00baf5438a987e68f46d96f510ec8
diff --git a/zircon/system/core/netsvc/BUILD.gn b/zircon/system/core/netsvc/BUILD.gn
index f827b07..c47a522 100644
--- a/zircon/system/core/netsvc/BUILD.gn
+++ b/zircon/system/core/netsvc/BUILD.gn
@@ -6,14 +6,9 @@
executable("netsvc") {
sources = [
- "board-name.cpp",
"debuglog.cpp",
"device_id.cpp",
- "netboot.cpp",
- "netfile.cpp",
"netsvc.cpp",
- "tftp.cpp",
- "zbi.cpp",
]
if (enable_netsvc_debugging_features) {
sources += [
@@ -25,27 +20,71 @@
]
}
deps = [
- "$zx/system/fidl/fuchsia-device:c",
- "$zx/system/fidl/fuchsia-device-manager:c",
- "$zx/system/fidl/fuchsia-hardware-block:c",
+ ":netsvc_common",
"$zx/system/fidl/fuchsia-hardware-ethernet:c",
- "$zx/system/fidl/fuchsia-sysinfo:c",
- "$zx/system/ulib/chromeos-disk-setup",
"$zx/system/ulib/fdio",
- "$zx/system/ulib/fzl",
- "$zx/system/ulib/gpt",
"$zx/system/ulib/inet6",
- "$zx/system/ulib/libzbi",
"$zx/system/ulib/sync",
"$zx/system/ulib/tftp",
"$zx/system/ulib/zircon",
"$zx/system/ulib/zx",
]
data_deps = [
- # netsvc launches /boot/bin/install-disk-image under --netboot.
- "$zx/system/uapp/disk-pave",
-
# netsvc launches /boot/bin/sh for netruncmd.
"$zx/third_party/uapp/dash",
]
}
+
+source_set("netsvc_common") {
+ sources = [
+ "board-name.cpp",
+ "file-api.cpp",
+ "netcp.cpp",
+ "netboot.cpp",
+ "paver.cpp",
+ "payload-streamer.cpp",
+ "tftp.cpp",
+ "zbi.cpp",
+ ]
+ deps = [
+ "$zx/system/fidl/fuchsia-device:c",
+ "$zx/system/fidl/fuchsia-device-manager:c",
+ "$zx/system/fidl/fuchsia-hardware-block:c",
+ "$zx/system/fidl/fuchsia-sysinfo:c",
+ "$zx/system/ulib/chromeos-disk-setup",
+ "$zx/system/ulib/async-loop:async-loop-cpp",
+ "$zx/system/ulib/fdio",
+ "$zx/system/ulib/gpt",
+ "$zx/system/ulib/libzbi",
+ "$zx/system/ulib/zircon",
+ ]
+ public_deps = [
+ "$zx/system/fidl/fuchsia-paver:c",
+ "$zx/system/ulib/fbl",
+ "$zx/system/ulib/fidl-utils",
+ "$zx/system/ulib/fzl",
+ "$zx/system/ulib/inet6",
+ "$zx/system/ulib/sync",
+ "$zx/system/ulib/tftp",
+ "$zx/system/ulib/zx",
+ ]
+}
+
+test("netsvc-test") {
+ output_name = "netsvc-test"
+ sources = [
+ "test/file-api-test.cpp",
+ "test/paver-test.cpp",
+ "test/payload-streamer-test.cpp",
+ "test/tftp-test.cpp",
+ ]
+ include_dirs = [ "." ]
+ deps = [
+ ":netsvc_common",
+ "$zx/system/fidl/fuchsia-sysinfo:c",
+ "$zx/system/ulib/async-loop:async-loop-cpp",
+ "$zx/system/ulib/fidl-utils",
+ "$zx/system/ulib/fs",
+ "$zx/system/ulib/zxtest",
+ ]
+}
diff --git a/zircon/system/core/netsvc/board-name.cpp b/zircon/system/core/netsvc/board-name.cpp
index 3de5059..9537cac 100644
--- a/zircon/system/core/netsvc/board-name.cpp
+++ b/zircon/system/core/netsvc/board-name.cpp
@@ -19,7 +19,6 @@
#include <gpt/gpt.h>
#include <lib/fdio/directory.h>
#include <lib/fzl/fdio.h>
-#include <lib/zx/channel.h>
#include <zircon/status.h>
#include <algorithm>
@@ -30,12 +29,12 @@
constexpr char kBlockDevPath[] = "/dev/class/block/";
fbl::unique_fd d_fd(open(kBlockDevPath, O_RDONLY));
if (!d_fd) {
- printf("netsvc: Cannot inspect block devices\n");
+ fprintf(stderr, "netsvc: Cannot inspect block devices\n");
return fbl::unique_fd();
}
DIR* d = fdopendir(d_fd.release());
if (d == nullptr) {
- printf("netsvc: Cannot inspect block devices\n");
+ fprintf(stderr, "netsvc: Cannot inspect block devices\n");
return fbl::unique_fd();
}
const auto closer = fbl::MakeAutoCall([&]() { closedir(d); });
@@ -95,14 +94,15 @@
status = io_status;
}
if (status != ZX_OK) {
- printf("netsvc: Could not acquire GPT block info: %s\n", zx_status_get_string(status));
+ fprintf(stderr, "netsvc: Could not acquire GPT block info: %s\n",
+ zx_status_get_string(status));
return false;
}
fbl::unique_ptr<gpt::GptDevice> gpt;
status = gpt::GptDevice::Create(gpt_fd.get(), block_info.block_size, block_info.block_count,
&gpt);
if (status != ZX_OK) {
- printf("netsvc: Failed to get GPT info: %s\n", zx_status_get_string(status));
+ fprintf(stderr, "netsvc: Failed to get GPT info: %s\n", zx_status_get_string(status));
return false;
}
return is_cros(gpt.get());
@@ -110,24 +110,18 @@
} // namespace
-bool check_board_name(const char* name, size_t length) {
- length = std::min(length, ZX_MAX_NAME_LEN);
-
- constexpr char kSysInfoPath[] = "/dev/misc/sysinfo";
- fbl::unique_fd sysinfo(open(kSysInfoPath, O_RDWR));
+bool CheckBoardName(const zx::channel& sysinfo, const char* name, size_t length) {
if (!sysinfo) {
return false;
}
- zx::channel channel;
- if (fdio_get_service_handle(sysinfo.release(), channel.reset_and_get_address()) != ZX_OK) {
- return false;
- }
+
+ length = std::min(length, ZX_MAX_NAME_LEN);
char real_board_name[ZX_MAX_NAME_LEN] = {};
zx_status_t status;
size_t actual_size;
zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetBoardName(
- channel.get(), &status, real_board_name, sizeof(real_board_name), &actual_size);
+ sysinfo.get(), &status, real_board_name, sizeof(real_board_name), &actual_size);
if (fidl_status != ZX_OK || status != ZX_OK) {
return false;
}
diff --git a/zircon/system/core/netsvc/board-name.h b/zircon/system/core/netsvc/board-name.h
index f4c4c68d..61d641c 100644
--- a/zircon/system/core/netsvc/board-name.h
+++ b/zircon/system/core/netsvc/board-name.h
@@ -6,4 +6,6 @@
#include <unistd.h>
-bool check_board_name(const char* name, size_t length);
+#include <lib/zx/channel.h>
+
+bool CheckBoardName(const zx::channel& sysinfo, const char* name, size_t length);
diff --git a/zircon/system/core/netsvc/debuglog.cpp b/zircon/system/core/netsvc/debuglog.cpp
index 292df74..f9cd11e 100644
--- a/zircon/system/core/netsvc/debuglog.cpp
+++ b/zircon/system/core/netsvc/debuglog.cpp
@@ -2,13 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "debuglog.h"
+
#include <inttypes.h>
#include <string.h>
+#include <stdio.h>
+#include <zircon/boot/netboot.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/log.h>
#include "netsvc.h"
+#include "tftp.h"
#define MAX_LOG_LINE (ZX_LOG_RECORD_MAX + 32)
@@ -19,7 +24,9 @@
static volatile uint32_t seqno = 1;
static volatile uint32_t pending = 0;
-zx_time_t debuglog_next_timeout = ZX_TIME_INFINITE;
+static zx_time_t g_debuglog_next_timeout = ZX_TIME_INFINITE;
+
+zx_time_t debuglog_next_timeout() { return g_debuglog_next_timeout; }
#define SEND_DELAY_SHORT ZX_MSEC(100)
#define SEND_DELAY_LONG ZX_SEC(4)
@@ -63,7 +70,7 @@
}
// Set up our timeout to expire immediately, so that we check for pending log messages
- debuglog_next_timeout = zx_clock_get_monotonic();
+ g_debuglog_next_timeout = zx_clock_get_monotonic();
seqno = 1;
pending = 0;
@@ -77,7 +84,7 @@
if (pending == 0) {
pkt.magic = NB_DEBUGLOG_MAGIC;
pkt.seqno = seqno;
- strncpy(pkt.nodename, nodename, sizeof(pkt.nodename) - 1);
+ strncpy(pkt.nodename, nodename(), sizeof(pkt.nodename) - 1);
pkt_len = 0;
while (pkt_len < (MAX_LOG_DATA - MAX_LOG_LINE)) {
size_t r = get_log_line(pkt.data + pkt_len);
@@ -97,7 +104,7 @@
}
udp6_send(&pkt, pkt_len, &ip6_ll_all_nodes, DEBUGLOG_PORT, DEBUGLOG_ACK_PORT, false);
done:
- debuglog_next_timeout = zx_deadline_after(send_delay);
+ g_debuglog_next_timeout = zx_deadline_after(send_delay);
}
void debuglog_recv(void* data, size_t len, bool is_mcast) {
diff --git a/zircon/system/core/netsvc/debuglog.h b/zircon/system/core/netsvc/debuglog.h
new file mode 100644
index 0000000..00f26e1
--- /dev/null
+++ b/zircon/system/core/netsvc/debuglog.h
@@ -0,0 +1,17 @@
+// Copyright 2016 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.
+
+#pragma once
+
+#include <zircon/types.h>
+
+zx_time_t debuglog_next_timeout();
+
+int debuglog_init();
+
+void debuglog_recv(void* data, size_t len, bool is_mcast);
+
+void debuglog_timeout_expired();
+
+
diff --git a/zircon/system/core/netsvc/file-api.cpp b/zircon/system/core/netsvc/file-api.cpp
new file mode 100644
index 0000000..761b319
--- /dev/null
+++ b/zircon/system/core/netsvc/file-api.cpp
@@ -0,0 +1,174 @@
+// 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 "file-api.h"
+
+#include <errno.h>
+#include <fcntl.h>
+
+#include <fbl/unique_fd.h>
+#include <lib/fdio/fdio.h>
+#include <zircon/boot/netboot.h>
+
+#include "board-name.h"
+#include "netboot.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);
+}
+
+} // namespace
+
+FileApi::FileApi(bool is_zedboot, std::unique_ptr<NetCopyInterface> netcp, zx::channel sysinfo,
+ PaverInterface* paver)
+ : is_zedboot_(is_zedboot), sysinfo_(std::move(sysinfo)), netcp_(std::move(netcp)),
+ paver_(paver) {
+ ZX_ASSERT(paver_ != nullptr);
+
+ if (!sysinfo_) {
+ constexpr char kSysInfoPath[] = "/dev/misc/sysinfo";
+ fbl::unique_fd sysinfo_fd(open(kSysInfoPath, O_RDWR));
+ if (sysinfo_fd) {
+ fdio_get_service_handle(sysinfo_fd.release(), sysinfo_.reset_and_get_address());
+ }
+ }
+}
+
+ssize_t FileApi::OpenRead(const char* filename) {
+ // Make sure all in-progress paving operations have completed
+ if (paver_->InProgress() == true) {
+ return TFTP_ERR_SHOULD_WAIT;
+ }
+ if (paver_->exit_code() != ZX_OK) {
+ fprintf(stderr, "paver exited with error: %d\n", paver_->exit_code());
+ paver_->reset_exit_code();
+ return TFTP_ERR_IO;
+ }
+
+ is_write_ = false;
+ strncpy(filename_, filename, PATH_MAX);
+ filename_[PATH_MAX] = '\0';
+ netboot_file_ = NULL;
+ size_t file_size;
+ if (netcp_->Open(filename, O_RDONLY, &file_size) == 0) {
+ return static_cast<ssize_t>(file_size);
+ }
+ return TFTP_ERR_NOT_FOUND;
+}
+
+tftp_status FileApi::OpenWrite(const char* filename, size_t size) {
+ // Make sure all in-progress paving operations have completed
+ if (paver_->InProgress() == true) {
+ return TFTP_ERR_SHOULD_WAIT;
+ }
+ if (paver_->exit_code() != ZX_OK) {
+ fprintf(stderr, "paver exited with error: %d\n", paver_->exit_code());
+ paver_->reset_exit_code();
+ return TFTP_ERR_IO;
+ }
+
+ is_write_ = true;
+ strncpy(filename_, filename, PATH_MAX);
+ filename_[PATH_MAX] = '\0';
+
+ if (is_zedboot_ && !strncmp(filename_, NB_FILENAME_PREFIX, NB_FILENAME_PREFIX_LEN())) {
+ type_ = NetfileType::kNetboot;
+ netboot_file_ = netboot_get_buffer(filename_, size);
+ if (netboot_file_ != NULL) {
+ return TFTP_NO_ERROR;
+ }
+ } else if (is_zedboot_ && !strcmp(filename_, NB_BOARD_NAME_FILENAME)) {
+ printf("netsvc: Running board name validation\n");
+ type_ = NetfileType::kBoardName;
+ return TFTP_NO_ERROR;
+ } else if (is_zedboot_ && !strncmp(filename_, NB_IMAGE_PREFIX, NB_IMAGE_PREFIX_LEN())) {
+ type_ = NetfileType::kPaver;
+ tftp_status status = paver_->OpenWrite(filename_, size);
+ if (status != TFTP_NO_ERROR) {
+ filename_[0] = '\0';
+ }
+ return status;
+ } else {
+ type_ = NetfileType::kNetCopy;
+ if (netcp_->Open(filename_, O_WRONLY, NULL) == 0) {
+ return TFTP_NO_ERROR;
+ }
+ }
+ return TFTP_ERR_INVALID_ARGS;
+}
+
+tftp_status FileApi::Read(void* data, size_t* length, off_t offset) {
+ if (length == NULL) {
+ return TFTP_ERR_INVALID_ARGS;
+ }
+ ssize_t read_len = netcp_->Read(data, offset, *length);
+ if (read_len < 0) {
+ return TFTP_ERR_IO;
+ }
+ *length = static_cast<size_t>(read_len);
+ return TFTP_NO_ERROR;
+}
+
+tftp_status FileApi::Write(const void* data, size_t* length, off_t offset) {
+ if (length == NULL) {
+ return TFTP_ERR_INVALID_ARGS;
+ }
+ switch (type_) {
+ case NetfileType::kNetboot: {
+ nbfile* nb_file = netboot_file_;
+ if ((static_cast<size_t>(offset) > nb_file->size) || (offset + *length) > nb_file->size) {
+ return TFTP_ERR_INVALID_ARGS;
+ }
+ memcpy(nb_file->data + offset, data, *length);
+ nb_file->offset = offset + *length;
+ return TFTP_NO_ERROR;
+ }
+ case NetfileType::kPaver:
+ return paver_->Write(data, length, offset);
+
+ case NetfileType::kBoardName:
+ return CheckBoardName(sysinfo_, reinterpret_cast<const char*>(data), *length)
+ ? TFTP_NO_ERROR
+ : TFTP_ERR_BAD_STATE;
+ case NetfileType::kNetCopy: {
+ ssize_t write_result =
+ netcp_->Write(reinterpret_cast<const char*>(data), offset, *length);
+ if (static_cast<size_t>(write_result) == *length) {
+ return TFTP_NO_ERROR;
+ }
+ if (write_result == -EBADF) {
+ return TFTP_ERR_BAD_STATE;
+ }
+ return TFTP_ERR_IO;
+ }
+ default:
+ return ZX_ERR_BAD_STATE;
+ }
+
+ return TFTP_ERR_BAD_STATE;
+}
+
+void FileApi::Close() {
+ if (type_ == NetfileType::kNetCopy) {
+ netcp_->Close();
+ } else if (type_ == NetfileType::kPaver) {
+ paver_->Close();
+ }
+ type_ = NetfileType::kUnknown;
+}
+
+void FileApi::Abort() {
+ if (is_write_ && type_ == NetfileType::kNetCopy) {
+ netcp_->AbortWrite();
+ }
+ Close();
+}
+
+} // namespace netsvc
diff --git a/zircon/system/core/netsvc/file-api.h b/zircon/system/core/netsvc/file-api.h
new file mode 100644
index 0000000..f820d40
--- /dev/null
+++ b/zircon/system/core/netsvc/file-api.h
@@ -0,0 +1,95 @@
+// 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.
+
+#pragma once
+
+#include <tftp/tftp.h>
+#include <zircon/boot/netboot.h>
+
+#include "netcp.h"
+#include "paver.h"
+
+namespace netsvc {
+
+// Provides capabilities to read/write files sent over TFTP.
+//
+// Reads only implements netcp. Specifically it enables reading of files in
+// global /data.
+//
+// Writes come in 4 flavors:
+// * netcp: Ability to write to global /data.
+// * netboot: Mexec into image once write completes.
+// * paving: Writes boot partions, or FVM.
+// * board name validation: Validates that board name sent matches current
+// board.
+class FileApiInterface {
+public:
+ // Returns size of file on success.
+ virtual ssize_t OpenRead(const char* filename) = 0;
+ virtual tftp_status OpenWrite(const char* filename, size_t size) = 0;
+ virtual tftp_status Read(void* data, size_t* length, off_t offset) = 0;
+ virtual tftp_status Write(const void* data, size_t* length, off_t offset) = 0;
+ virtual void Close() = 0;
+ // Like close, but signals read or write operation was incomplete.
+ virtual void Abort() = 0;
+
+ virtual bool is_write() = 0;
+ virtual const char* filename() = 0;
+};
+
+class FileApi : public FileApiInterface {
+public:
+ // FileApi does *not* take ownership of |paver|.
+ explicit FileApi(bool is_zedboot,
+ std::unique_ptr<NetCopyInterface> netcp = std::make_unique<NetCopy>(),
+ zx::channel sysinfo = zx::channel(),
+ PaverInterface* paver = Paver::Get());
+
+ ssize_t OpenRead(const char* filename) final;
+ tftp_status OpenWrite(const char* filename, size_t size) final;
+ tftp_status Read(void* data, size_t* length, off_t offset) final;
+ tftp_status Write(const void* data, size_t* length, off_t offset) final;
+ void Close() final;
+ void Abort() final;
+
+ const char* filename() final {
+ return filename_;
+ }
+
+ bool is_write() final {
+ return is_write_;
+ }
+
+private:
+ // Identifies what the file being streamed over TFTP should be
+ // used for.
+ enum class NetfileType {
+ kUnknown, // No reads/writes currently in progress.
+ kNetCopy, // A file in /data
+ kNetboot, // A bootfs file
+ kPaver, // A disk image which should be paved to disk
+ kBoardName, // A file containing the board name.
+ // Expected to return error if it doesn't match the current board name.
+ };
+
+ bool is_zedboot_;
+
+ bool is_write_ = false;
+ char filename_[PATH_MAX + 1] = {};
+ NetfileType type_ = NetfileType::kUnknown;
+
+ // Use when type_ == NetfileType::kBoardName.
+ zx::channel sysinfo_;
+
+ // Used when type_ == NetfileType::kNetCopy.
+ std::unique_ptr<NetCopyInterface> netcp_;
+
+ // Only valid when type_ == NetfileType::kNetboot.
+ nbfile* netboot_file_ = nullptr;
+
+ // Used when type_ == NetfileType::kPaver.
+ PaverInterface* paver_;
+};
+
+} // namespace netsvc
diff --git a/zircon/system/core/netsvc/netboot.cpp b/zircon/system/core/netsvc/netboot.cpp
index a2abbd7..6b762d1 100644
--- a/zircon/system/core/netsvc/netboot.cpp
+++ b/zircon/system/core/netsvc/netboot.cpp
@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "netsvc.h"
-#include "zbi.h"
+#include "netboot.h"
#include <assert.h>
#include <errno.h>
@@ -26,6 +25,11 @@
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
+#include "netsvc.h"
+#include "netcp.h"
+#include "paver.h"
+#include "zbi.h"
+
static uint32_t last_cookie = 0;
static uint32_t last_cmd = 0;
static uint32_t last_arg = 0;
@@ -155,10 +159,15 @@
m.magic = NB_MAGIC;
m.cookie = cookie;
m.cmd = NB_ACK;
- m.arg = netfile_open(filename, arg, NULL);
+ m.arg = netcp_open(filename, arg, NULL);
udp6_send(&m, sizeof(m), saddr, sport, dport, false);
}
+struct netfilemsg {
+ nbmsg hdr;
+ uint8_t data[1024];
+};
+
static void nb_read(uint32_t cookie, uint32_t arg, const ip6_addr_t* saddr, uint16_t sport,
uint16_t dport) {
static netfilemsg m = {
@@ -182,7 +191,7 @@
msg_size = sizeof(m.hdr);
}
} else if (arg == 0 || arg == blocknum + 1) {
- ssize_t result = netfile_read(&m.data, sizeof(m.data));
+ ssize_t result = netcp_read(&m.data, sizeof(m.data));
if (result < 0) {
m.hdr.arg = static_cast<uint32_t>(result);
msg_size = sizeof(m.hdr);
@@ -218,7 +227,7 @@
m.arg = -EIO;
}
} else if (arg == 0 || arg == blocknum + 1) {
- ssize_t result = netfile_write(data, len);
+ ssize_t result = netcp_write(data, len);
m.arg = static_cast<uint32_t>(result > 0 ? 0 : result);
blocknum = arg;
}
@@ -231,7 +240,7 @@
m.magic = NB_MAGIC;
m.cookie = cookie;
m.cmd = NB_ACK;
- m.arg = netfile_close();
+ m.arg = netcp_close();
udp6_send(&m, sizeof(m), saddr, sport, dport, false);
}
@@ -391,12 +400,12 @@
break;
case NB_BOOT:
// Wait for the paver to complete
- while (atomic_load(&paving_in_progress)) {
+ while (netsvc::Paver::Get()->InProgress()) {
thrd_yield();
}
- if (atomic_load(&paver_exit_code) != 0) {
- printf("netboot: detected paver error: %d\n", atomic_load(&paver_exit_code));
- atomic_store(&paver_exit_code, 0);
+ if (netsvc::Paver::Get()->exit_code() != 0) {
+ printf("netboot: detected paver error: %d\n", netsvc::Paver::Get()->exit_code());
+ netsvc::Paver::Get()->reset_exit_code();
break;
}
do_boot = true;
@@ -404,12 +413,12 @@
break;
case NB_REBOOT:
// Wait for the paver to complete
- while (atomic_load(&paving_in_progress)) {
+ while (netsvc::Paver::Get()->InProgress()) {
thrd_yield();
}
- if (atomic_load(&paver_exit_code) != 0) {
- printf("netboot: detected paver error: %d\n", atomic_load(&paver_exit_code));
- atomic_store(&paver_exit_code, 0);
+ if (netsvc::Paver::Get()->exit_code() != 0) {
+ printf("netboot: detected paver error: %d\n", netsvc::Paver::Get()->exit_code());
+ netsvc::Paver::Get()->reset_exit_code();
break;
}
do_reboot = true;
@@ -468,17 +477,17 @@
switch (msg->cmd) {
case NB_QUERY: {
if (strcmp(reinterpret_cast<char*>(msg->data), "*") &&
- strcmp(reinterpret_cast<char*>(msg->data), nodename)) {
+ strcmp(reinterpret_cast<char*>(msg->data), nodename())) {
break;
}
- size_t dlen = strlen(nodename) + 1;
+ size_t dlen = strlen(nodename()) + 1;
char buf[1024 + sizeof(nbmsg)];
if ((dlen + sizeof(nbmsg)) > sizeof(buf)) {
return;
}
msg->cmd = NB_ACK;
memcpy(buf, msg, sizeof(nbmsg));
- memcpy(buf + sizeof(nbmsg), nodename, dlen);
+ memcpy(buf + sizeof(nbmsg), nodename(), dlen);
udp6_send(buf, sizeof(nbmsg) + dlen, saddr, sport, dport, false);
break;
}
@@ -505,7 +514,7 @@
default:
// If the bootloader is enabled, then let it have a crack at the
// incoming packets as well.
- if (netbootloader) {
+ if (netbootloader()) {
bootloader_recv(data, len + sizeof(nbmsg), daddr, dport, saddr, sport);
}
}
diff --git a/zircon/system/core/netsvc/netboot.h b/zircon/system/core/netsvc/netboot.h
new file mode 100644
index 0000000..8440ded
--- /dev/null
+++ b/zircon/system/core/netsvc/netboot.h
@@ -0,0 +1,14 @@
+// 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.
+
+#pragma once
+
+#include <inet6/inet6.h>
+
+void netboot_advertise(const char* nodename);
+
+void netboot_recv(void* data, size_t len, bool is_mcast, const ip6_addr_t* daddr, uint16_t dport,
+ const ip6_addr_t* saddr, uint16_t sport);
+
+void netboot_run_cmd(const char* cmd);
diff --git a/zircon/system/core/netsvc/netcp.cpp b/zircon/system/core/netsvc/netcp.cpp
new file mode 100644
index 0000000..4a97a0e
--- /dev/null
+++ b/zircon/system/core/netsvc/netcp.cpp
@@ -0,0 +1,234 @@
+// Copyright 2016 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 "netcp.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <inet6/inet6.h>
+#include <inet6/netifc.h>
+
+#include <zircon/processargs.h>
+#include <zircon/syscalls.h>
+
+#include <zircon/boot/netboot.h>
+
+namespace {
+
+constexpr char TMP_SUFFIX[] = ".netsvc.tmp";
+
+struct netcp_state {
+ int fd;
+ off_t offset;
+ // false: Filename is the open file and final destination
+ // true : Filename is final destination; open file has a magic tmp suffix
+ bool needs_rename;
+ char filename[PATH_MAX];
+};
+
+netcp_state netcp = {
+ .fd = -1,
+ .offset = 0,
+ .needs_rename = false,
+ .filename = {0},
+};
+
+int netcp_mkdir(const char* filename) {
+ const char* ptr = filename[0] == '/' ? filename + 1 : filename;
+ struct stat st;
+ char tmp[1024];
+ for (;;) {
+ ptr = strchr(ptr, '/');
+ if (!ptr) {
+ return 0;
+ }
+ memcpy(tmp, filename, ptr - filename);
+ tmp[ptr - filename] = '\0';
+ ptr += 1;
+ if (stat(tmp, &st) < 0) {
+ if (errno == ENOENT) {
+ if (mkdir(tmp, 0755) < 0) {
+ return -1;
+ }
+ } else {
+ return -1;
+ }
+ }
+ }
+}
+
+} // namespace
+
+int netcp_open(const char* filename, uint32_t arg, size_t* file_size) {
+ if (netcp.fd >= 0) {
+ printf("netsvc: closing still-open '%s', replacing with '%s'\n", netcp.filename,
+ filename);
+ close(netcp.fd);
+ netcp.fd = -1;
+ }
+ size_t len = strlen(filename);
+ strlcpy(netcp.filename, filename, sizeof(netcp.filename));
+
+ struct stat st;
+again: // label here to catch filename=/path/to/new/directory/
+ if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) {
+ errno = EISDIR;
+ goto err;
+ }
+
+ switch (arg) {
+ case O_RDONLY:
+ netcp.needs_rename = false;
+ netcp.fd = open(filename, O_RDONLY);
+ if (file_size) {
+ *file_size = st.st_size;
+ }
+ break;
+ case O_WRONLY: {
+ // If we're writing a file, actually write to "filename + TMP_SUFFIX",
+ // and rename to the final destination when we would close. This makes
+ // written files appear to atomically update.
+ if (len + strlen(TMP_SUFFIX) + 1 > PATH_MAX) {
+ errno = ENAMETOOLONG;
+ goto err;
+ }
+ strcat(netcp.filename, TMP_SUFFIX);
+ netcp.needs_rename = true;
+ netcp.fd = open(netcp.filename, O_WRONLY | O_CREAT | O_TRUNC);
+ netcp.filename[len] = '\0';
+ if (netcp.fd < 0 && errno == ENOENT) {
+ if (netcp_mkdir(filename) == 0) {
+ goto again;
+ }
+ }
+ break;
+ }
+ default:
+ printf("netsvc: open '%s' with invalid mode %d\n", filename, arg);
+ errno = EINVAL;
+ }
+ if (netcp.fd < 0) {
+ goto err;
+ } else {
+ strlcpy(netcp.filename, filename, sizeof(netcp.filename));
+ netcp.offset = 0;
+ }
+
+ return 0;
+err:
+ netcp.filename[0] = '\0';
+ return -errno;
+}
+
+ssize_t netcp_offset_read(void* data_out, off_t offset, size_t max_len) {
+ if (netcp.fd < 0) {
+ printf("netsvc: read, but no open file\n");
+ return -EBADF;
+ }
+ if (offset != netcp.offset) {
+ if (lseek(netcp.fd, offset, SEEK_SET) != offset) {
+ return -errno;
+ }
+ netcp.offset = offset;
+ }
+ return netcp_read(data_out, max_len);
+}
+
+ssize_t netcp_read(void* data_out, size_t data_sz) {
+ if (netcp.fd < 0) {
+ printf("netsvc: read, but no open file\n");
+ return -EBADF;
+ }
+ ssize_t n = read(netcp.fd, data_out, data_sz);
+ if (n < 0) {
+ printf("netsvc: error reading '%s': %d\n", netcp.filename, errno);
+ int result = (errno == 0) ? -EIO : -errno;
+ close(netcp.fd);
+ netcp.fd = -1;
+ return result;
+ }
+ netcp.offset += n;
+ return n;
+}
+
+ssize_t netcp_offset_write(const char* data, off_t offset, size_t length) {
+ if (netcp.fd < 0) {
+ printf("netsvc: write, but no open file\n");
+ return -EBADF;
+ }
+ if (offset != netcp.offset) {
+ if (lseek(netcp.fd, offset, SEEK_SET) != offset) {
+ return -errno;
+ }
+ netcp.offset = offset;
+ }
+ return netcp_write(data, length);
+}
+
+ssize_t netcp_write(const char* data, size_t len) {
+ if (netcp.fd < 0) {
+ printf("netsvc: write, but no open file\n");
+ return -EBADF;
+ }
+ ssize_t n = write(netcp.fd, data, len);
+ if (n != static_cast<ssize_t>(len)) {
+ printf("netsvc: error writing %s: %d\n", netcp.filename, errno);
+ int result = (errno == 0) ? -EIO : -errno;
+ close(netcp.fd);
+ netcp.fd = -1;
+ return result;
+ }
+ netcp.offset += len;
+ return len;
+}
+
+int netcp_close() {
+ int result = 0;
+ if (netcp.fd < 0) {
+ printf("netsvc: close, but no open file\n");
+ } else {
+ if (netcp.needs_rename) {
+ char src[PATH_MAX];
+ strlcpy(src, netcp.filename, sizeof(src));
+ strlcat(src, TMP_SUFFIX, sizeof(src));
+ if (rename(src, netcp.filename)) {
+ printf("netsvc: failed to rename temporary file: %s\n", strerror(errno));
+ }
+ }
+ if (close(netcp.fd)) {
+ result = (errno == 0) ? -EIO : -errno;
+ }
+ netcp.fd = -1;
+ }
+ return result;
+}
+
+// Clean up if we abort before finishing a write. Close out and unlink it, rather than
+// leaving an incomplete file.
+void netcp_abort_write() {
+ if (netcp.fd < 0) {
+ return;
+ }
+ close(netcp.fd);
+ netcp.fd = -1;
+ char tmp[PATH_MAX];
+ const char* filename;
+ if (netcp.needs_rename) {
+ strlcpy(tmp, netcp.filename, sizeof(tmp));
+ strlcat(tmp, TMP_SUFFIX, sizeof(tmp));
+ filename = tmp;
+ } else {
+ filename = netcp.filename;
+ }
+ if (unlink(filename) != 0) {
+ printf("netsvc: failed to unlink aborted file %s\n", filename);
+ }
+}
diff --git a/zircon/system/core/netsvc/netcp.h b/zircon/system/core/netsvc/netcp.h
new file mode 100644
index 0000000..8a698bb
--- /dev/null
+++ b/zircon/system/core/netsvc/netcp.h
@@ -0,0 +1,72 @@
+// 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.
+
+#pragma once
+
+#include <optional>
+
+#include <sys/types.h>
+#include <zircon/types.h>
+
+int netcp_open(const char* filename, uint32_t arg, size_t* file_size);
+
+ssize_t netcp_offset_read(void* data_out, off_t offset, size_t max_len);
+
+ssize_t netcp_read(void* data_out, size_t data_sz);
+
+ssize_t netcp_offset_write(const char* data, off_t offset, size_t length);
+
+ssize_t netcp_write(const char* data, size_t len);
+
+int netcp_close();
+
+void netcp_abort_write();
+
+
+namespace netsvc {
+
+class NetCopyInterface {
+public:
+ virtual ~NetCopyInterface() {}
+ virtual int Open(const char* filename, uint32_t arg, size_t* file_size) = 0;
+ virtual ssize_t Read(void* data_out, std::optional<off_t> offset, size_t max_len) = 0;
+ virtual ssize_t Write(const char* data, std::optional<off_t> offset, size_t length) = 0;
+ virtual int Close() = 0;
+ virtual void AbortWrite() = 0;
+};
+
+class NetCopy : public NetCopyInterface {
+public:
+ explicit NetCopy() {}
+
+ int Open(const char* filename, uint32_t arg, size_t* file_size) final {
+ return netcp_open(filename, arg, file_size);
+ }
+
+ ssize_t Read(void* data_out, std::optional<off_t> offset, size_t max_len) final {
+ if (offset) {
+ return netcp_offset_read(data_out, *offset, max_len);
+ } else {
+ return netcp_read(data_out, max_len);
+ }
+ }
+
+ ssize_t Write(const char* data, std::optional<off_t> offset, size_t length) final {
+ if (offset) {
+ return netcp_offset_write(data, *offset, length);
+ } else {
+ return netcp_write(data, length);
+ }
+ }
+
+ int Close() final {
+ return netcp_close();
+ }
+
+ void AbortWrite() final {
+ netcp_abort_write();
+ }
+};
+
+} // namespace netsvc
diff --git a/zircon/system/core/netsvc/netfile.cpp b/zircon/system/core/netsvc/netfile.cpp
deleted file mode 100644
index 8b8e914..0000000
--- a/zircon/system/core/netsvc/netfile.cpp
+++ /dev/null
@@ -1,220 +0,0 @@
-// Copyright 2016 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 "netsvc.h"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-#include <inet6/inet6.h>
-#include <inet6/netifc.h>
-
-#include <zircon/processargs.h>
-#include <zircon/syscalls.h>
-
-#include <zircon/boot/netboot.h>
-
-#define TMP_SUFFIX ".netsvc.tmp"
-
-netfile_state netfile = {
- .fd = -1,
- .offset = 0,
- .needs_rename = false,
- .filename = {0},
-};
-
-static int netfile_mkdir(const char* filename) {
- const char* ptr = filename[0] == '/' ? filename + 1 : filename;
- struct stat st;
- char tmp[1024];
- for (;;) {
- ptr = strchr(ptr, '/');
- if (!ptr) {
- return 0;
- }
- memcpy(tmp, filename, ptr - filename);
- tmp[ptr - filename] = '\0';
- ptr += 1;
- if (stat(tmp, &st) < 0) {
- if (errno == ENOENT) {
- if (mkdir(tmp, 0755) < 0) {
- return -1;
- }
- } else {
- return -1;
- }
- }
- }
-}
-
-int netfile_open(const char* filename, uint32_t arg, size_t* file_size) {
- if (netfile.fd >= 0) {
- printf("netsvc: closing still-open '%s', replacing with '%s'\n", netfile.filename,
- filename);
- close(netfile.fd);
- netfile.fd = -1;
- }
- size_t len = strlen(filename);
- strlcpy(netfile.filename, filename, sizeof(netfile.filename));
-
- struct stat st;
-again: // label here to catch filename=/path/to/new/directory/
- if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) {
- errno = EISDIR;
- goto err;
- }
-
- switch (arg) {
- case O_RDONLY:
- netfile.needs_rename = false;
- netfile.fd = open(filename, O_RDONLY);
- if (file_size) {
- *file_size = st.st_size;
- }
- break;
- case O_WRONLY: {
- // If we're writing a file, actually write to "filename + TMP_SUFFIX",
- // and rename to the final destination when we would close. This makes
- // written files appear to atomically update.
- if (len + strlen(TMP_SUFFIX) + 1 > PATH_MAX) {
- errno = ENAMETOOLONG;
- goto err;
- }
- strcat(netfile.filename, TMP_SUFFIX);
- netfile.needs_rename = true;
- netfile.fd = open(netfile.filename, O_WRONLY | O_CREAT | O_TRUNC);
- netfile.filename[len] = '\0';
- if (netfile.fd < 0 && errno == ENOENT) {
- if (netfile_mkdir(filename) == 0) {
- goto again;
- }
- }
- break;
- }
- default:
- printf("netsvc: open '%s' with invalid mode %d\n", filename, arg);
- errno = EINVAL;
- }
- if (netfile.fd < 0) {
- goto err;
- } else {
- strlcpy(netfile.filename, filename, sizeof(netfile.filename));
- netfile.offset = 0;
- }
-
- return 0;
-err:
- netfile.filename[0] = '\0';
- return -errno;
-}
-
-ssize_t netfile_offset_read(void* data_out, off_t offset, size_t max_len) {
- if (netfile.fd < 0) {
- printf("netsvc: read, but no open file\n");
- return -EBADF;
- }
- if (offset != netfile.offset) {
- if (lseek(netfile.fd, offset, SEEK_SET) != offset) {
- return -errno;
- }
- netfile.offset = offset;
- }
- return netfile_read(data_out, max_len);
-}
-
-ssize_t netfile_read(void* data_out, size_t data_sz) {
- if (netfile.fd < 0) {
- printf("netsvc: read, but no open file\n");
- return -EBADF;
- }
- ssize_t n = read(netfile.fd, data_out, data_sz);
- if (n < 0) {
- printf("netsvc: error reading '%s': %d\n", netfile.filename, errno);
- int result = (errno == 0) ? -EIO : -errno;
- close(netfile.fd);
- netfile.fd = -1;
- return result;
- }
- netfile.offset += n;
- return n;
-}
-
-ssize_t netfile_offset_write(const char* data, off_t offset, size_t length) {
- if (netfile.fd < 0) {
- printf("netsvc: write, but no open file\n");
- return -EBADF;
- }
- if (offset != netfile.offset) {
- if (lseek(netfile.fd, offset, SEEK_SET) != offset) {
- return -errno;
- }
- netfile.offset = offset;
- }
- return netfile_write(data, length);
-}
-
-ssize_t netfile_write(const char* data, size_t len) {
- if (netfile.fd < 0) {
- printf("netsvc: write, but no open file\n");
- return -EBADF;
- }
- ssize_t n = write(netfile.fd, data, len);
- if (n != static_cast<ssize_t>(len)) {
- printf("netsvc: error writing %s: %d\n", netfile.filename, errno);
- int result = (errno == 0) ? -EIO : -errno;
- close(netfile.fd);
- netfile.fd = -1;
- return result;
- }
- netfile.offset += len;
- return len;
-}
-
-int netfile_close() {
- int result = 0;
- if (netfile.fd < 0) {
- printf("netsvc: close, but no open file\n");
- } else {
- if (netfile.needs_rename) {
- char src[PATH_MAX];
- strlcpy(src, netfile.filename, sizeof(src));
- strlcat(src, TMP_SUFFIX, sizeof(src));
- if (rename(src, netfile.filename)) {
- printf("netsvc: failed to rename temporary file: %s\n", strerror(errno));
- }
- }
- if (close(netfile.fd)) {
- result = (errno == 0) ? -EIO : -errno;
- }
- netfile.fd = -1;
- }
- return result;
-}
-
-// Clean up if we abort before finishing a write. Close out and unlink it, rather than
-// leaving an incomplete file.
-void netfile_abort_write() {
- if (netfile.fd < 0) {
- return;
- }
- close(netfile.fd);
- netfile.fd = -1;
- char tmp[PATH_MAX];
- const char* filename;
- if (netfile.needs_rename) {
- strlcpy(tmp, netfile.filename, sizeof(tmp));
- strlcat(tmp, TMP_SUFFIX, sizeof(tmp));
- filename = tmp;
- } else {
- filename = netfile.filename;
- }
- if (unlink(filename) != 0) {
- printf("netsvc: failed to unlink aborted file %s\n", filename);
- }
-}
diff --git a/zircon/system/core/netsvc/netsvc.cpp b/zircon/system/core/netsvc/netsvc.cpp
index 3d7b470..ebc8002 100644
--- a/zircon/system/core/netsvc/netsvc.cpp
+++ b/zircon/system/core/netsvc/netsvc.cpp
@@ -23,12 +23,18 @@
#include <zircon/time.h>
#include "device_id.h"
+#include "debuglog.h"
+#include "netboot.h"
+#include "tftp.h"
#define FILTER_IPV6 1
-bool netbootloader = false;
+static bool g_netbootloader = false;
-const char* nodename = "zircon";
+static const char* g_nodename = "zircon";
+
+bool netbootloader() { return g_netbootloader; }
+const char* nodename() { return g_nodename; }
void udp6_recv(void* data, size_t len, const ip6_addr_t* daddr, uint16_t dport,
const ip6_addr_t* saddr, uint16_t sport) {
@@ -63,8 +69,9 @@
void update_timeouts() {
zx_time_t now = zx_clock_get_monotonic();
- zx_time_t next_timeout =
- (debuglog_next_timeout < tftp_next_timeout) ? debuglog_next_timeout : tftp_next_timeout;
+ zx_time_t next_timeout = (debuglog_next_timeout() < tftp_next_timeout())
+ ? debuglog_next_timeout()
+ : tftp_next_timeout();
if (next_timeout != ZX_TIME_INFINITE) {
uint32_t ms = static_cast<uint32_t>(
(next_timeout < now) ? 0 : (zx_time_sub_time(next_timeout, now)) / ZX_MSEC(1));
@@ -96,7 +103,7 @@
bool should_advertise = false;
while (argc > 1) {
if (!strncmp(argv[1], "--netboot", 9)) {
- netbootloader = true;
+ g_netbootloader = true;
} else if (!strncmp(argv[1], "--advertise", 11)) {
should_advertise = true;
} else if (!strncmp(argv[1], "--interface", 11)) {
@@ -111,7 +118,7 @@
} else if (!strncmp(argv[1], "--nodename", 10)) {
print_nodename_and_exit = true;
} else {
- nodename = argv[1];
+ g_nodename = argv[1];
nodename_provided = true;
}
argv++;
@@ -131,25 +138,25 @@
if (!nodename_provided) {
netifc_get_info(mac, &mtu);
device_id_get(mac, device_id);
- nodename = device_id;
+ g_nodename = device_id;
if (print_nodename_and_exit) {
- printf("%s\n", nodename);
+ printf("%s\n", g_nodename);
return 0;
}
}
- if (netbootloader) {
+ if (g_netbootloader) {
printf("%szedboot: version: %s\n\n", zedboot_banner, BOOTLOADER_VERSION);
}
- printf("netsvc: nodename='%s'\n", nodename);
+ printf("netsvc: nodename='%s'\n", g_nodename);
if (!should_advertise) {
printf("netsvc: will not advertise\n");
}
printf("netsvc: start\n");
for (;;) {
- if (netbootloader && should_advertise) {
- netboot_advertise(nodename);
+ if (g_netbootloader && should_advertise) {
+ netboot_advertise(g_nodename);
}
update_timeouts();
@@ -159,10 +166,10 @@
break;
}
zx_time_t now = zx_clock_get_monotonic();
- if (now > debuglog_next_timeout) {
+ if (now > debuglog_next_timeout()) {
debuglog_timeout_expired();
}
- if (now > tftp_next_timeout) {
+ if (now > tftp_next_timeout()) {
tftp_timeout_expired();
}
}
diff --git a/zircon/system/core/netsvc/netsvc.h b/zircon/system/core/netsvc/netsvc.h
index 727f5e9..1cd48ad 100644
--- a/zircon/system/core/netsvc/netsvc.h
+++ b/zircon/system/core/netsvc/netsvc.h
@@ -4,79 +4,7 @@
#pragma once
-#include <limits.h>
-#include <stdio.h>
-
-#include <inet6/inet6.h>
-
-#include <atomic>
-
-#include <zircon/boot/netboot.h>
-#include <zircon/types.h>
-
-// netfile interface
-struct netfile_state {
- int fd;
- off_t offset;
- // false: Filename is the open file and final destination
- // true : Filename is final destination; open file has a magic tmp suffix
- bool needs_rename;
- char filename[PATH_MAX];
-};
-
-extern netfile_state netfile;
-
-struct netfilemsg {
- nbmsg hdr;
- uint8_t data[1024];
-};
-
-int netfile_open(const char* filename, uint32_t arg, size_t* file_size);
-
-ssize_t netfile_offset_read(void* data_out, off_t offset, size_t max_len);
-
-ssize_t netfile_read(void* data_out, size_t data_sz);
-
-ssize_t netfile_offset_write(const char* data, off_t offset, size_t length);
-
-ssize_t netfile_write(const char* data, size_t len);
-
-int netfile_close();
-
-void netfile_abort_write();
-
-// netboot interface
-extern const char* nodename;
-extern bool netbootloader;
-
-void netboot_advertise(const char* nodename);
-
-void netboot_recv(void* data, size_t len, bool is_mcast, const ip6_addr_t* daddr, uint16_t dport,
- const ip6_addr_t* saddr, uint16_t sport);
-
-void netboot_run_cmd(const char* cmd);
-
-// TFTP interface
-extern zx_time_t tftp_next_timeout;
-extern std::atomic<bool> paving_in_progress;
-extern std::atomic<int> paver_exit_code;
-
-void tftp_recv(void* data, size_t len, const ip6_addr_t* daddr, uint16_t dport,
- const ip6_addr_t* saddr, uint16_t sport);
-
-void tftp_timeout_expired();
-
-bool tftp_has_pending();
-void tftp_send_next();
-
-// debuglog interface
-extern zx_time_t debuglog_next_timeout;
-
-int debuglog_init();
-
-void debuglog_recv(void* data, size_t len, bool is_mcast);
-
-void debuglog_timeout_expired();
-
-// netsvc interface
void update_timeouts();
+
+bool netbootloader();
+const char* nodename();
diff --git a/zircon/system/core/netsvc/paver.cpp b/zircon/system/core/netsvc/paver.cpp
new file mode 100644
index 0000000..5391e88
--- /dev/null
+++ b/zircon/system/core/netsvc/paver.cpp
@@ -0,0 +1,323 @@
+// 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 <fbl/auto_call.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fdio/directory.h>
+#include <lib/zx/time.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);
+}
+
+} // 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;
+ }
+ instance_ = new Paver(std::move(local));
+ }
+ 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: 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) {
+ fprintf(stderr, "netsvc: unable to create channel\n");
+ exit_code_.store(status);
+ return 0;
+ }
+
+ async::Loop loop(&kAsyncLoopConfigAttachToThread);
+ PayloadStreamer streamer(std::move(server), std::move(callback));
+ loop.StartThread("payload-streamer");
+
+ // Blocks untils paving is complete.
+ auto io_status = fuchsia_paver_PaverWriteVolumes(paver_svc_.get(), client.release(),
+ &status);
+ status = io_status == ZX_OK ? status : io_status;
+ exit_code_.store(status);
+
+ return 0;
+}
+
+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: 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;
+ }
+
+ fuchsia_mem_Buffer buffer = {
+ .vmo = dup.release(),
+ .size = buffer_mapper_.size(),
+ };
+
+ zx_status_t io_status = ZX_ERR_INTERNAL;
+ // Blocks untils paving is complete.
+ switch (command_) {
+ case Command::kDataFile:
+ io_status = fuchsia_paver_PaverWriteDataFile(paver_svc_.get(), path_, strlen(path_),
+ &buffer, &status);
+ break;
+ case Command::kBootloader:
+ io_status = fuchsia_paver_PaverWriteBootloader(paver_svc_.get(), &buffer, &status);
+ break;
+ case Command::kAsset:
+ io_status = fuchsia_paver_PaverWriteAsset(paver_svc_.get(), configuration_, asset_, &buffer,
+ &status);
+ break;
+ default:
+ io_status = ZX_OK;
+ result = TFTP_ERR_INTERNAL;
+ status = ZX_ERR_INTERNAL;
+ break;
+ }
+ status = io_status == ZX_OK ? status : io_status;
+ 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_ = fuchsia_paver_Configuration_A;
+ asset_ = 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_ = fuchsia_paver_Configuration_B;
+ asset_ = 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_ = fuchsia_paver_Configuration_RECOVERY;
+ asset_ = 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_ = fuchsia_paver_Configuration_A;
+ asset_ = 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_ = fuchsia_paver_Configuration_B;
+ asset_ = 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_ = fuchsia_paver_Configuration_RECOVERY;
+ asset_ = 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(), fuchsia_paver_Paver_Name,
+ paver_remote.release());
+ if (status != ZX_OK) {
+ fprintf(stderr, "netsvc: Unable to open /svc/%s.\n", fuchsia_paver_Paver_Name);
+ return TFTP_ERR_IO;
+ }
+
+ paver_svc_ = 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
diff --git a/zircon/system/core/netsvc/paver.h b/zircon/system/core/netsvc/paver.h
new file mode 100644
index 0000000..979d3bb
--- /dev/null
+++ b/zircon/system/core/netsvc/paver.h
@@ -0,0 +1,112 @@
+// 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.
+
+#pragma once
+
+#include <atomic>
+#include <threads.h>
+
+#include <limits.h>
+
+#include <fuchsia/paver/c/fidl.h>
+#include <lib/fzl/owned-vmo-mapper.h>
+#include <lib/sync/completion.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/time.h>
+#include <tftp/tftp.h>
+#include <zircon/types.h>
+
+#include "tftp.h"
+
+namespace netsvc {
+
+class PaverInterface {
+public:
+ virtual bool InProgress() = 0;
+ virtual zx_status_t exit_code() = 0;
+ virtual void reset_exit_code() = 0;
+
+ // TODO: Explore returning an object which implements write and when it goes
+ // out of scope, closes.
+ virtual tftp_status OpenWrite(const char* filename, size_t size) = 0;
+ virtual tftp_status Write(const void* data, size_t* length, off_t offset) = 0;
+ virtual void Close() = 0;
+};
+
+class Paver : public PaverInterface {
+public:
+ // Get the singleton instance.
+ static Paver* Get();
+
+ bool InProgress() final;
+ zx_status_t exit_code() final;
+ void reset_exit_code() final;
+
+ tftp_status OpenWrite(const char* filename, size_t size) final;
+ tftp_status Write(const void* data, size_t* length, off_t offset) final;
+ void Close() final;
+
+ // Visible for testing.
+ explicit Paver(zx::channel svc_root)
+ : svc_root_(std::move(svc_root)) {}
+
+ void set_timeout(zx::duration timeout) { timeout_ = timeout; }
+
+private:
+ // Refer to //zircon/system/fidl/fuchsia.paver/paver.fidl for a list of what
+ // these commands translate to.
+ enum class Command {
+ kAsset,
+ kBootloader,
+ kDataFile,
+ kFvm,
+ };
+
+ uint8_t* buffer() { return static_cast<uint8_t*>(buffer_mapper_.start()); }
+
+ // Pushes all data from the paver buffer (filled by netsvc) into the paver input VMO. When
+ // there's no data to copy, blocks on data_ready until more data is written into the buffer.
+ int StreamBuffer();
+
+ // Monitors the vmo progress, and calls into paver service once finished.
+ int MonitorBuffer();
+
+ std::atomic<bool> in_progress_ = false;
+ std::atomic<zx_status_t> exit_code_ = ZX_OK;
+
+ // Total size of file
+ size_t size_ = 0;
+
+ // Paver command to call into.
+ Command command_;
+
+ // Channel to svc.
+ zx::channel svc_root_;
+
+ // Channel to paver service.
+ zx::channel paver_svc_;
+
+ union {
+ // Only valid when command == Command::kAsset.
+ struct {
+ fuchsia_paver_Configuration configuration_;
+ fuchsia_paver_Asset asset_;
+ };
+ // Only valid when command == Command::kDataFile.
+ char path_[PATH_MAX];
+ };
+
+ // Buffer used for stashing data from tftp until it can be written out to the paver.
+ fzl::OwnedVmoMapper buffer_mapper_;
+ // Buffer write offset.
+ std::atomic<size_t> write_offset_ = 0;
+ std::atomic<unsigned int> buf_refcount_ = 0;
+ thrd_t buf_thrd_ = 0;
+ sync_completion_t data_ready_;
+
+ // Timeout monitor thread uses before timing out.
+ zx::duration timeout_ = zx::sec(5 * TFTP_TIMEOUT_SECS);
+};
+
+} // namespace netsvc
diff --git a/zircon/system/core/netsvc/payload-streamer.cpp b/zircon/system/core/netsvc/payload-streamer.cpp
new file mode 100644
index 0000000..1158651
--- /dev/null
+++ b/zircon/system/core/netsvc/payload-streamer.cpp
@@ -0,0 +1,63 @@
+// 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 "payload-streamer.h"
+
+#include <lib/async/default.h>
+
+namespace netsvc {
+
+PayloadStreamer::PayloadStreamer(zx::channel chan, ReadCallback callback)
+ : read_(std::move(callback)) {
+ fidl_bind(async_get_default_dispatcher(), chan.release(),
+ reinterpret_cast<fidl_dispatch_t*>(fuchsia_paver_PayloadStream_dispatch),
+ this, &ops_);
+}
+
+zx_status_t PayloadStreamer::RegisterVmo(zx_handle_t vmo_handle, fidl_txn_t* txn) {
+ zx::vmo vmo(vmo_handle);
+
+ if (vmo_) {
+ vmo_.reset();
+ mapper_.Unmap();
+ }
+
+ vmo_ = std::move(vmo);
+ auto status = mapper_.Map(vmo_, 0, 0, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
+ return fuchsia_paver_PayloadStreamRegisterVmo_reply(txn, status);
+}
+
+zx_status_t PayloadStreamer::ReadData(fidl_txn_t* txn) {
+ fuchsia_paver_ReadResult result = {};
+ if (!vmo_) {
+ result.tag = fuchsia_paver_ReadResultTag_err;
+ result.err = ZX_ERR_BAD_STATE;
+ return fuchsia_paver_PayloadStreamReadData_reply(txn, &result);
+ }
+ if (eof_reached_) {
+ result.tag = fuchsia_paver_ReadResultTag_eof;
+ result.eof = true;
+ return fuchsia_paver_PayloadStreamReadData_reply(txn, &result);
+ }
+
+ size_t actual;
+ auto status = read_(mapper_.start(), read_offset_, mapper_.size(), &actual);
+ if (status != ZX_OK) {
+ result.tag = fuchsia_paver_ReadResultTag_err;
+ result.err = status;
+ } else if (actual == 0) {
+ eof_reached_ = true;
+ result.tag = fuchsia_paver_ReadResultTag_eof;
+ result.eof = true;
+ } else {
+ result.tag = fuchsia_paver_ReadResultTag_info;
+ result.info.offset = 0;
+ result.info.size = actual;
+ read_offset_ += actual;
+ }
+
+ return fuchsia_paver_PayloadStreamReadData_reply(txn, &result);
+}
+
+} // namespace netsvc
diff --git a/zircon/system/core/netsvc/payload-streamer.h b/zircon/system/core/netsvc/payload-streamer.h
new file mode 100644
index 0000000..98fbb7c
--- /dev/null
+++ b/zircon/system/core/netsvc/payload-streamer.h
@@ -0,0 +1,50 @@
+// 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.
+
+#pragma once
+
+#include <fbl/function.h>
+#include <fbl/unique_fd.h>
+#include <fuchsia/paver/c/fidl.h>
+#include <lib/fidl-utils/bind.h>
+#include <lib/fzl/vmo-mapper.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/vmo.h>
+#include <zircon/types.h>
+
+namespace netsvc {
+
+// Reads the data into the vmo at offset, size. Can block.
+using ReadCallback = fbl::Function<zx_status_t(void* /*buf*/, size_t /*offset*/, size_t /*size*/,
+ size_t* /*actual*/)>;
+
+class PayloadStreamer {
+public:
+ PayloadStreamer(zx::channel chan, ReadCallback callback);
+
+ PayloadStreamer(const PayloadStreamer&) = delete;
+ PayloadStreamer& operator=(const PayloadStreamer&) = delete;
+ PayloadStreamer(PayloadStreamer&&) = delete;
+ PayloadStreamer& operator=(PayloadStreamer&&) = delete;
+
+ zx_status_t RegisterVmo(zx_handle_t vmo_handle, fidl_txn_t* txn);
+
+ zx_status_t ReadData(fidl_txn_t* txn);
+
+private:
+ using Binder = fidl::Binder<PayloadStreamer>;
+
+ static constexpr fuchsia_paver_PayloadStream_ops_t ops_ = {
+ .RegisterVmo = Binder::BindMember<&PayloadStreamer::RegisterVmo>,
+ .ReadData = Binder::BindMember<&PayloadStreamer::ReadData>,
+ };
+
+ ReadCallback read_;
+ zx::vmo vmo_;
+ fzl::VmoMapper mapper_;
+ size_t read_offset_ = 0;
+ bool eof_reached_ = false;
+};
+
+} // namespace netsvc
diff --git a/zircon/system/core/netsvc/test/file-api-test.cpp b/zircon/system/core/netsvc/test/file-api-test.cpp
new file mode 100644
index 0000000..0fa6541
--- /dev/null
+++ b/zircon/system/core/netsvc/test/file-api-test.cpp
@@ -0,0 +1,247 @@
+// 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 "file-api.h"
+
+#include <memory>
+
+#include <fcntl.h>
+
+#include <fuchsia/sysinfo/c/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fidl-utils/bind.h>
+#include <zxtest/zxtest.h>
+
+namespace {
+
+class FakePaver : public netsvc::PaverInterface {
+public:
+ bool InProgress() override { return in_progress_; }
+ zx_status_t exit_code() override { return exit_code_; }
+ void reset_exit_code() override { exit_code_ = ZX_OK; }
+
+ tftp_status OpenWrite(const char* filename, size_t size) override {
+ in_progress_ = true;
+ return TFTP_NO_ERROR;
+ }
+ tftp_status Write(const void* data, size_t* length, off_t offset) override {
+ if (!in_progress_) {
+ return TFTP_ERR_INTERNAL;
+ }
+ exit_code_ = ZX_OK;
+ return TFTP_NO_ERROR;
+ }
+ void Close() override {
+ in_progress_ = false;
+ }
+
+ void set_exit_code(zx_status_t exit_code) {
+ exit_code_ = exit_code;
+ }
+
+private:
+ bool in_progress_ = false;
+ zx_status_t exit_code_ = ZX_OK;
+};
+
+constexpr char kReadData[] = "laksdfjsadfa";
+constexpr char kFakeData[] = "lalala";
+
+class FakeNetCopy : public netsvc::NetCopyInterface {
+public:
+ int Open(const char* filename, uint32_t arg, size_t* file_size) override {
+ if (arg == O_RDONLY) {
+ *file_size = sizeof(kReadData);
+ }
+ return 0;
+ }
+ ssize_t Read(void* data_out, std::optional<off_t> offset, size_t max_len) override {
+ const size_t len = std::min(sizeof(kReadData), max_len);
+ memcpy(data_out, kReadData, len);
+ return len;
+ }
+ ssize_t Write(const char* data, std::optional<off_t> offset, size_t length) override {
+ return length;
+ }
+ int Close() override {
+ return 0;
+ }
+ void AbortWrite() override {}
+};
+
+class FakeSysinfo {
+public:
+ FakeSysinfo(async_dispatcher_t* dispatcher) {
+ zx::channel remote;
+ ASSERT_OK(zx::channel::create(0, &remote, &svc_chan_));
+ fidl_bind(dispatcher, remote.release(),
+ reinterpret_cast<fidl_dispatch_t*>(fuchsia_sysinfo_Device_dispatch),
+ this, &ops_);
+ }
+
+ zx_status_t GetRootJob(fidl_txn_t* txn) {
+ return fuchsia_sysinfo_DeviceGetRootJob_reply(txn, ZX_ERR_NOT_SUPPORTED, ZX_HANDLE_INVALID);
+ }
+
+ zx_status_t GetRootResource(fidl_txn_t* txn) {
+ return fuchsia_sysinfo_DeviceGetRootResource_reply(txn, ZX_ERR_NOT_SUPPORTED,
+ ZX_HANDLE_INVALID);
+ }
+
+ zx_status_t GetHypervisorResource(fidl_txn_t* txn) {
+ return fuchsia_sysinfo_DeviceGetHypervisorResource_reply(txn, ZX_ERR_NOT_SUPPORTED,
+ ZX_HANDLE_INVALID);
+ }
+
+ zx_status_t GetBoardName(fidl_txn_t* txn) {
+ return fuchsia_sysinfo_DeviceGetBoardName_reply(txn, ZX_OK, board_, 32);
+ }
+
+ zx_status_t GetInterruptControllerInfo(fidl_txn_t* txn) {
+ return fuchsia_sysinfo_DeviceGetInterruptControllerInfo_reply(txn, ZX_ERR_NOT_SUPPORTED,
+ nullptr);
+ }
+
+ zx::channel& svc_chan() { return svc_chan_; }
+
+ void set_board_name(const char* board) {
+ strncpy(board_, board, 32);
+ }
+
+private:
+ using Binder = fidl::Binder<FakeSysinfo>;
+
+ zx::channel svc_chan_;
+
+ char board_[32] = {};
+
+ static constexpr fuchsia_sysinfo_Device_ops_t ops_ = {
+ .GetRootJob = Binder::BindMember<&FakeSysinfo::GetRootJob>,
+ .GetRootResource = Binder::BindMember<&FakeSysinfo::GetRootResource>,
+ .GetHypervisorResource = Binder::BindMember<&FakeSysinfo::GetHypervisorResource>,
+ .GetBoardName = Binder::BindMember<&FakeSysinfo::GetBoardName>,
+ .GetInterruptControllerInfo = Binder::BindMember<&FakeSysinfo::GetInterruptControllerInfo>,
+ };
+};
+
+} // namespace
+
+class FileApiTest : public zxtest::Test {
+protected:
+ FileApiTest()
+ : loop_(&kAsyncLoopConfigNoAttachToThread),
+ fake_sysinfo_(loop_.dispatcher()),
+ file_api_(true, std::make_unique<FakeNetCopy>(), std::move(fake_sysinfo_.svc_chan()),
+ &fake_paver_) {
+
+ loop_.StartThread("file-api-test-loop");
+ }
+
+ async::Loop loop_;
+ FakePaver fake_paver_;
+ FakeSysinfo fake_sysinfo_;
+ netsvc::FileApi file_api_;
+};
+
+TEST_F(FileApiTest, OpenReadNetCopy) {
+ ASSERT_EQ(file_api_.OpenRead("file"), sizeof(kReadData));
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, OpenReadFailedPave) {
+ fake_paver_.set_exit_code(ZX_ERR_INTERNAL);
+ ASSERT_NE(file_api_.OpenRead("file"), sizeof(kReadData));
+}
+
+TEST_F(FileApiTest, OpenWriteNetCopy) {
+ ASSERT_EQ(file_api_.OpenWrite("file", 10), TFTP_NO_ERROR);
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, OpenWriteBoardName) {
+ ASSERT_EQ(file_api_.OpenWrite(NB_BOARD_NAME_FILENAME, 10), TFTP_NO_ERROR);
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, OpenWritePaver) {
+ ASSERT_EQ(file_api_.OpenWrite(NB_IMAGE_PREFIX, 10), TFTP_NO_ERROR);
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, OpenWriteWhilePaving) {
+ ASSERT_EQ(file_api_.OpenWrite(NB_IMAGE_PREFIX, 10), TFTP_NO_ERROR);
+ ASSERT_NE(file_api_.OpenWrite(NB_IMAGE_PREFIX, 10), TFTP_NO_ERROR);
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, OpenReadWhilePaving) {
+ ASSERT_EQ(file_api_.OpenWrite(NB_IMAGE_PREFIX, 10), TFTP_NO_ERROR);
+ ASSERT_LT(file_api_.OpenRead("file"), 0);
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, OpenWriteFailedPave) {
+ fake_paver_.set_exit_code(ZX_ERR_INTERNAL);
+ ASSERT_NE(file_api_.OpenWrite("file", 10), TFTP_NO_ERROR);
+}
+
+TEST_F(FileApiTest, WriteNetCopy) {
+ ASSERT_EQ(file_api_.OpenWrite("file", 10), TFTP_NO_ERROR);
+ size_t len = sizeof(kFakeData);
+ ASSERT_EQ(file_api_.Write(kFakeData, &len, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(len, sizeof(kFakeData));
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, WriteBoardName) {
+ fake_sysinfo_.set_board_name(kFakeData);
+ ASSERT_EQ(file_api_.OpenWrite(NB_BOARD_NAME_FILENAME, 10), TFTP_NO_ERROR);
+ size_t len = sizeof(kFakeData);
+ ASSERT_EQ(file_api_.Write(kFakeData, &len, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(len, sizeof(kFakeData));
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, WriteWrongBoardName) {
+ fake_sysinfo_.set_board_name("other");
+ ASSERT_EQ(file_api_.OpenWrite(NB_BOARD_NAME_FILENAME, 10), TFTP_NO_ERROR);
+ size_t len = sizeof(kFakeData);
+ ASSERT_NE(file_api_.Write(kFakeData, &len, 0), TFTP_NO_ERROR);
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, WritePaver) {
+ ASSERT_EQ(file_api_.OpenWrite(NB_IMAGE_PREFIX, 10), TFTP_NO_ERROR);
+ size_t len = sizeof(kFakeData);
+ ASSERT_EQ(file_api_.Write(kFakeData, &len, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(len, sizeof(kFakeData));
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, WriteAfterClose) {
+ ASSERT_EQ(file_api_.OpenWrite("file", 10), TFTP_NO_ERROR);
+ file_api_.Close();
+ size_t len = sizeof(kFakeData);
+ ASSERT_NE(file_api_.Write(kFakeData, &len, 0), TFTP_NO_ERROR);
+}
+
+TEST_F(FileApiTest, WriteNoLength) {
+ ASSERT_EQ(file_api_.OpenWrite(NB_IMAGE_PREFIX, 10), TFTP_NO_ERROR);
+ ASSERT_NE(file_api_.Write(kFakeData, nullptr, 0), TFTP_NO_ERROR);
+ file_api_.Close();
+}
+
+TEST_F(FileApiTest, WriteWithoutOpen) {
+ size_t len = sizeof(kFakeData);
+ ASSERT_NE(file_api_.Write(kFakeData, &len, 0), TFTP_NO_ERROR);
+}
+
+TEST_F(FileApiTest, AbortNetCopyWrite) {
+ ASSERT_EQ(file_api_.OpenWrite("file", 10), TFTP_NO_ERROR);
+ size_t len = sizeof(kFakeData);
+ ASSERT_EQ(file_api_.Write(kFakeData, &len, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(len, sizeof(kFakeData));
+ file_api_.Abort();
+}
+
diff --git a/zircon/system/core/netsvc/test/paver-test.cpp b/zircon/system/core/netsvc/test/paver-test.cpp
new file mode 100644
index 0000000..8c7eb72
--- /dev/null
+++ b/zircon/system/core/netsvc/test/paver-test.cpp
@@ -0,0 +1,400 @@
+// 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 <fs/pseudo-dir.h>
+#include <fs/service.h>
+#include <fs/synchronous-vfs.h>
+#include <fuchsia/paver/c/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fidl-utils/bind.h>
+#include <lib/zx/channel.h>
+#include <zircon/boot/netboot.h>
+#include <zxtest/zxtest.h>
+
+namespace {
+constexpr char kFakeData[] = "lalala";
+}
+
+TEST(PaverTest, Constructor) {
+ netsvc::Paver paver(zx::channel);
+}
+
+TEST(PaverTest, GetSingleton) {
+ ASSERT_NOT_NULL(netsvc::Paver::Get());
+}
+
+TEST(PaverTest, InitialInProgressFalse) {
+ zx::channel chan;
+ netsvc::Paver paver_(std::move(chan));
+ ASSERT_FALSE(paver_.InProgress());
+}
+
+TEST(PaverTest, InitialExitCodeValid) {
+ zx::channel chan;
+ netsvc::Paver paver_(std::move(chan));
+ ASSERT_OK(paver_.exit_code());
+}
+
+namespace {
+
+enum class Command {
+ kUnknown,
+ kQueryActiveConfiguration,
+ kSetActiveConfiguration,
+ kMarkActiveConfigurationSuccessful,
+ kForceRecoveryConfiguration,
+ kWriteAsset,
+ kWriteVolumes,
+ kWriteBootloader,
+ kWriteDataFile,
+ kWipeVolumes,
+};
+
+class FakePaver {
+public:
+ zx_status_t Connect(async_dispatcher_t* dispatcher, zx::channel request) {
+ return fidl_bind(dispatcher, request.release(),
+ reinterpret_cast<fidl_dispatch_t*>(fuchsia_paver_Paver_dispatch),
+ this, &ops_);
+ }
+
+ zx_status_t QueryActiveConfiguration(fidl_txn_t* txn) {
+ last_command_ = Command::kQueryActiveConfiguration;
+ return fuchsia_paver_PaverQueryActiveConfiguration_reply(txn, ZX_ERR_NOT_SUPPORTED,
+ nullptr);
+ }
+
+ zx_status_t SetActiveConfiguration(fuchsia_paver_Configuration configuration,
+ fidl_txn_t* txn) {
+ last_command_ = Command::kSetActiveConfiguration;
+ return fuchsia_paver_PaverSetActiveConfiguration_reply(txn, ZX_ERR_NOT_SUPPORTED);
+ }
+
+ zx_status_t MarkActiveConfigurationSuccessful(fidl_txn_t* txn) {
+ last_command_ = Command::kMarkActiveConfigurationSuccessful;
+ return fuchsia_paver_PaverMarkActiveConfigurationSuccessful_reply(txn,
+ ZX_ERR_NOT_SUPPORTED);
+ }
+
+ zx_status_t ForceRecoveryConfiguration(fidl_txn_t* txn) {
+ last_command_ = Command::kForceRecoveryConfiguration;
+ return fuchsia_paver_PaverForceRecoveryConfiguration_reply(txn, ZX_ERR_NOT_SUPPORTED);
+ }
+
+ zx_status_t WriteAsset(fuchsia_paver_Configuration configuration,
+ fuchsia_paver_Asset asset, const fuchsia_mem_Buffer* payload,
+ fidl_txn_t* txn) {
+ last_command_ = Command::kWriteAsset;
+ zx_handle_close(payload->vmo);
+ auto status = payload->size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
+ return fuchsia_paver_PaverWriteAsset_reply(txn, status);
+ }
+
+ zx_status_t WriteVolumes(zx_handle_t payload_stream, fidl_txn_t* txn) {
+ last_command_ = Command::kWriteVolumes;
+ zx::channel stream(payload_stream);
+ // Register VMO.
+ zx::vmo vmo;
+ auto status = zx::vmo::create(1024, 0, &vmo);
+ if (status != ZX_OK) {
+ return fuchsia_paver_PaverWriteVolumes_reply(txn, status);
+ }
+ auto io_status = fuchsia_paver_PayloadStreamRegisterVmo(stream.get(), vmo.release(),
+ &status);
+ status = io_status == ZX_OK ? status : io_status;
+ if (status != ZX_OK) {
+ return fuchsia_paver_PaverWriteVolumes_reply(txn, status);
+ }
+ // Stream until EOF.
+ status = [&]() {
+ size_t data_transferred = 0;
+ for (;;) {
+ fuchsia_paver_ReadResult result;
+ auto io_status = fuchsia_paver_PayloadStreamReadData(stream.get(), &result);
+ if (io_status != ZX_OK) {
+ return io_status;
+ }
+ switch (result.tag) {
+ case fuchsia_paver_ReadResultTag_err:
+ return result.err;
+ case fuchsia_paver_ReadResultTag_eof:
+ return data_transferred == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
+ case fuchsia_paver_ReadResultTag_info:
+ data_transferred += result.info.size;
+ continue;
+ default:
+ return ZX_ERR_INTERNAL;
+ }
+ }
+ }();
+
+ return fuchsia_paver_PaverWriteVolumes_reply(txn, status);
+ }
+
+ zx_status_t WriteBootloader(const fuchsia_mem_Buffer* payload, fidl_txn_t* txn) {
+ last_command_ = Command::kWriteBootloader;
+ zx_handle_close(payload->vmo);
+ auto status = payload->size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
+ return fuchsia_paver_PaverWriteBootloader_reply(txn, status);
+ }
+
+ zx_status_t WriteDataFile(const char* filename, size_t filename_len,
+ const fuchsia_mem_Buffer* payload, fidl_txn_t* txn) {
+ last_command_ = Command::kWriteDataFile;
+ zx_handle_close(payload->vmo);
+ auto status = payload->size == expected_payload_size_ ? ZX_OK : ZX_ERR_INVALID_ARGS;
+ return fuchsia_paver_PaverWriteDataFile_reply(txn, status);
+ }
+
+ zx_status_t WipeVolumes(fidl_txn_t* txn) {
+ last_command_ = Command::kWipeVolumes;
+ auto status = ZX_OK;
+ return fuchsia_paver_PaverWipeVolumes_reply(txn, status);
+ }
+
+ Command last_command() { return last_command_; }
+ void set_expected_payload_size(size_t size) { expected_payload_size_ = size; }
+
+private:
+ using Binder = fidl::Binder<FakePaver>;
+
+ Command last_command_ = Command::kUnknown;
+ size_t expected_payload_size_ = 0;
+
+ static constexpr fuchsia_paver_Paver_ops_t ops_ = {
+ .QueryActiveConfiguration = Binder::BindMember<&FakePaver::QueryActiveConfiguration>,
+ .SetActiveConfiguration = Binder::BindMember<&FakePaver::SetActiveConfiguration>,
+ .MarkActiveConfigurationSuccessful =
+ Binder::BindMember<&FakePaver::MarkActiveConfigurationSuccessful>,
+ .ForceRecoveryConfiguration = Binder::BindMember<&FakePaver::ForceRecoveryConfiguration>,
+ .WriteAsset = Binder::BindMember<&FakePaver::WriteAsset>,
+ .WriteVolumes = Binder::BindMember<&FakePaver::WriteVolumes>,
+ .WriteBootloader = Binder::BindMember<&FakePaver::WriteBootloader>,
+ .WriteDataFile = Binder::BindMember<&FakePaver::WriteDataFile>,
+ .WipeVolumes = Binder::BindMember<&FakePaver::WipeVolumes>,
+ };
+};
+
+class FakeSvc {
+public:
+ explicit FakeSvc(async_dispatcher_t* dispatcher)
+ : dispatcher_(dispatcher), vfs_(dispatcher) {
+ auto root_dir = fbl::MakeRefCounted<fs::PseudoDir>();
+ root_dir->AddEntry(fuchsia_paver_Paver_Name,
+ fbl::MakeRefCounted<fs::Service>([this](zx::channel request) {
+ return fake_paver_.Connect(dispatcher_, std::move(request));
+ }));
+
+ zx::channel svc_remote;
+ ASSERT_OK(zx::channel::create(0, &svc_local_, &svc_remote));
+
+ vfs_.ServeDirectory(root_dir, std::move(svc_remote));
+ }
+
+ FakePaver& fake_paver() { return fake_paver_; }
+ zx::channel& svc_chan() { return svc_local_; }
+
+private:
+ async_dispatcher_t* dispatcher_;
+ fs::SynchronousVfs vfs_;
+ FakePaver fake_paver_;
+ zx::channel svc_local_;
+};
+
+} // namespace
+
+class PaverTest : public zxtest::Test {
+protected:
+ PaverTest()
+ : loop_(&kAsyncLoopConfigNoAttachToThread),
+ fake_svc_(loop_.dispatcher()),
+ paver_(std::move(fake_svc_.svc_chan())) {
+
+ paver_.set_timeout(zx::msec(500));
+ loop_.StartThread("paver-test-loop");
+ }
+
+ ~PaverTest() {
+ // Need to make sure paver thread exits.
+ Wait();
+ loop_.Shutdown();
+ }
+
+ void Wait() {
+ while (paver_.InProgress())
+ continue;
+ }
+
+ async::Loop loop_;
+ FakeSvc fake_svc_;
+ netsvc::Paver paver_;
+};
+
+TEST_F(PaverTest, OpenWriteInvalidFile) {
+ char invalid_file_name[32] = {};
+ ASSERT_NE(paver_.OpenWrite(invalid_file_name, 0), TFTP_NO_ERROR);
+ paver_.Close();
+}
+
+TEST_F(PaverTest, OpenWriteInvalidSize) {
+ ASSERT_NE(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 0), TFTP_NO_ERROR);
+}
+
+TEST_F(PaverTest, OpenWriteValidFile) {
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
+ paver_.Close();
+}
+
+TEST_F(PaverTest, OpenTwice) {
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
+ ASSERT_NE(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
+ paver_.Close();
+}
+
+TEST_F(PaverTest, WriteWithoutOpen) {
+ size_t size = sizeof(kFakeData);
+ ASSERT_NE(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+}
+
+TEST_F(PaverTest, WriteAfterClose) {
+ size_t size = sizeof(kFakeData);
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
+ paver_.Close();
+ // TODO(surajmalhotra): Should we ensure this fails?
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+}
+
+TEST_F(PaverTest, TimeoutNoWrites) {
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
+ paver_.Close();
+ Wait();
+ ASSERT_NE(paver_.exit_code(), ZX_OK);
+}
+
+TEST_F(PaverTest, TimeoutPartialWrite) {
+ size_t size = sizeof(kFakeData);
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ paver_.Close();
+ Wait();
+ ASSERT_NE(paver_.exit_code(), ZX_OK);
+}
+
+TEST_F(PaverTest, WriteCompleteSingle) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(size);
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, size), TFTP_NO_ERROR);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ paver_.Close();
+ Wait();
+ ASSERT_EQ(paver_.exit_code(), ZX_OK);
+ ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteBootloader);
+}
+
+TEST_F(PaverTest, WriteCompleteManySmallWrites) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(1024);
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 1024), TFTP_NO_ERROR);
+ for (size_t offset = 0; offset < 1024; offset += sizeof(kFakeData)) {
+ size = std::min(sizeof(kFakeData), 1024 - offset);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, offset), TFTP_NO_ERROR);
+ ASSERT_EQ(size, std::min(sizeof(kFakeData), 1024 - offset));
+ }
+ paver_.Close();
+ Wait();
+ ASSERT_OK(paver_.exit_code());
+}
+
+TEST_F(PaverTest, Overwrite) {
+ size_t size = sizeof(kFakeData);
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 2), TFTP_NO_ERROR);
+ ASSERT_NE(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ paver_.Close();
+ Wait();
+ ASSERT_NE(paver_.exit_code(), ZX_OK);
+}
+
+TEST_F(PaverTest, CloseChannelBetweenWrites) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(2 * size);
+ ASSERT_EQ(paver_.OpenWrite(NB_BOOTLOADER_FILENAME, 2 * size), TFTP_NO_ERROR);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ loop_.Shutdown();
+ ASSERT_EQ(paver_.Write(kFakeData, &size, size), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ paver_.Close();
+ Wait();
+ ASSERT_EQ(paver_.exit_code(), ZX_ERR_PEER_CLOSED);
+}
+
+TEST_F(PaverTest, WriteZirconA) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(size);
+ ASSERT_EQ(paver_.OpenWrite(NB_ZIRCONA_FILENAME, size), TFTP_NO_ERROR);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ paver_.Close();
+ Wait();
+ ASSERT_EQ(paver_.exit_code(), ZX_OK);
+ ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
+}
+
+TEST_F(PaverTest, WriteVbMetaA) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(size);
+ ASSERT_EQ(paver_.OpenWrite(NB_VBMETAA_FILENAME, size), TFTP_NO_ERROR);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ paver_.Close();
+ Wait();
+ ASSERT_EQ(paver_.exit_code(), ZX_OK);
+ ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteAsset);
+}
+
+TEST_F(PaverTest, WriteSshAuth) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(size);
+ ASSERT_EQ(paver_.OpenWrite(NB_SSHAUTH_FILENAME, size), TFTP_NO_ERROR);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ paver_.Close();
+ Wait();
+ ASSERT_EQ(paver_.exit_code(), ZX_OK);
+ ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteDataFile);
+}
+
+TEST_F(PaverTest, WriteFvm) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(size);
+ ASSERT_EQ(paver_.OpenWrite(NB_FVM_FILENAME, size), TFTP_NO_ERROR);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, 0), TFTP_NO_ERROR);
+ ASSERT_EQ(size, sizeof(kFakeData));
+ paver_.Close();
+ Wait();
+ ASSERT_EQ(paver_.exit_code(), ZX_OK);
+ ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteVolumes);
+}
+
+TEST_F(PaverTest, WriteFvmManySmallWrites) {
+ size_t size = sizeof(kFakeData);
+ fake_svc_.fake_paver().set_expected_payload_size(1024);
+ ASSERT_EQ(paver_.OpenWrite(NB_FVM_FILENAME, 1024), TFTP_NO_ERROR);
+ for (size_t offset = 0; offset < 1024; offset += sizeof(kFakeData)) {
+ size = std::min(sizeof(kFakeData), 1024 - offset);
+ ASSERT_EQ(paver_.Write(kFakeData, &size, offset), TFTP_NO_ERROR);
+ ASSERT_EQ(size, std::min(sizeof(kFakeData), 1024 - offset));
+ }
+ paver_.Close();
+ Wait();
+ ASSERT_OK(paver_.exit_code());
+ ASSERT_EQ(fake_svc_.fake_paver().last_command(), Command::kWriteVolumes);
+}
diff --git a/zircon/system/core/netsvc/test/payload-streamer-test.cpp b/zircon/system/core/netsvc/test/payload-streamer-test.cpp
new file mode 100644
index 0000000..634fba2
--- /dev/null
+++ b/zircon/system/core/netsvc/test/payload-streamer-test.cpp
@@ -0,0 +1,144 @@
+// 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 "payload-streamer.h"
+
+#include <optional>
+
+#include <lib/async-loop/cpp/loop.h>
+#include <zxtest/zxtest.h>
+
+class PayloadStreamerTest : public zxtest::Test {
+protected:
+ PayloadStreamerTest()
+ : loop_(&kAsyncLoopConfigAttachToThread) {
+
+ }
+
+ static zx_status_t DefaultCallback(void* buf, size_t offset, size_t size, size_t* actual) {
+ *actual = size;
+ return ZX_OK;
+ }
+
+ void StartStreamer(netsvc::ReadCallback callback = DefaultCallback) {
+ zx::channel server;
+ ASSERT_OK(zx::channel::create(0, &client_, &server));
+
+ payload_streamer_.emplace(std::move(server), std::move(callback));
+ loop_.StartThread("payload-streamer-test-loop");
+ }
+
+ async::Loop loop_;
+ zx::channel client_;
+ std::optional<netsvc::PayloadStreamer> payload_streamer_;
+};
+
+TEST_F(PayloadStreamerTest, RegisterVmo) {
+ StartStreamer();
+
+ zx::vmo vmo;
+ ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
+
+ zx_status_t status;
+ ASSERT_OK(fuchsia_paver_PayloadStreamRegisterVmo(client_.get(), vmo.release(), &status));
+ ASSERT_OK(status);
+}
+
+TEST_F(PayloadStreamerTest, RegisterVmoTwice) {
+ StartStreamer();
+
+ zx::vmo vmo;
+ ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
+
+ zx_status_t status;
+ ASSERT_OK(fuchsia_paver_PayloadStreamRegisterVmo(client_.get(), vmo.release(), &status));
+ ASSERT_OK(status);
+
+ ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
+
+ ASSERT_OK(fuchsia_paver_PayloadStreamRegisterVmo(client_.get(), vmo.release(), &status));
+ ASSERT_OK(status);
+}
+
+TEST_F(PayloadStreamerTest, ReadData) {
+ StartStreamer();
+
+ zx::vmo vmo;
+ ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
+
+ zx_status_t status;
+ ASSERT_OK(fuchsia_paver_PayloadStreamRegisterVmo(client_.get(), vmo.release(), &status));
+ ASSERT_OK(status);
+
+ fuchsia_paver_ReadResult result;
+ ASSERT_OK(fuchsia_paver_PayloadStreamReadData(client_.get(), &result));
+ ASSERT_EQ(result.tag, fuchsia_paver_ReadResultTag_info);
+ ASSERT_EQ(result.info.offset, 0);
+ ASSERT_EQ(result.info.size, ZX_PAGE_SIZE);
+}
+
+TEST_F(PayloadStreamerTest, ReadDataWithoutRegisterVmo) {
+ StartStreamer();
+
+ fuchsia_paver_ReadResult result;
+ ASSERT_OK(fuchsia_paver_PayloadStreamReadData(client_.get(), &result));
+ ASSERT_EQ(result.tag, fuchsia_paver_ReadResultTag_err);
+ ASSERT_NE(result.err, ZX_OK);
+}
+
+TEST_F(PayloadStreamerTest, ReadDataHalfFull) {
+ StartStreamer([](void* buf, size_t offset, size_t size, size_t* actual) {
+ *actual = size / 2;
+ return ZX_OK;
+ });
+
+ zx::vmo vmo;
+ ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
+
+ zx_status_t status;
+ ASSERT_OK(fuchsia_paver_PayloadStreamRegisterVmo(client_.get(), vmo.release(), &status));
+ ASSERT_OK(status);
+
+ fuchsia_paver_ReadResult result;
+ ASSERT_OK(fuchsia_paver_PayloadStreamReadData(client_.get(), &result));
+ ASSERT_EQ(result.tag, fuchsia_paver_ReadResultTag_info);
+ ASSERT_EQ(result.info.offset, 0);
+ ASSERT_EQ(result.info.size, ZX_PAGE_SIZE / 2);
+}
+
+TEST_F(PayloadStreamerTest, ReadEof) {
+ StartStreamer([](void* buf, size_t offset, size_t size, size_t* actual) {
+ *actual = 0;
+ return ZX_OK;
+ });
+
+ zx::vmo vmo;
+ ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
+
+ zx_status_t status;
+ ASSERT_OK(fuchsia_paver_PayloadStreamRegisterVmo(client_.get(), vmo.release(), &status));
+ ASSERT_OK(status);
+
+ fuchsia_paver_ReadResult result;
+ ASSERT_OK(fuchsia_paver_PayloadStreamReadData(client_.get(), &result));
+ ASSERT_EQ(result.tag, fuchsia_paver_ReadResultTag_eof);
+}
+
+TEST_F(PayloadStreamerTest, ReadFailure) {
+ StartStreamer([](void* buf, size_t offset, size_t size, size_t* actual) {
+ return ZX_ERR_INTERNAL;
+ });
+
+ zx::vmo vmo;
+ ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo));
+
+ zx_status_t status;
+ ASSERT_OK(fuchsia_paver_PayloadStreamRegisterVmo(client_.get(), vmo.release(), &status));
+ ASSERT_OK(status);
+
+ fuchsia_paver_ReadResult result;
+ ASSERT_OK(fuchsia_paver_PayloadStreamReadData(client_.get(), &result));
+ ASSERT_EQ(result.tag, fuchsia_paver_ReadResultTag_err);
+ ASSERT_NE(result.err, ZX_OK);
+}
diff --git a/zircon/system/core/netsvc/test/tftp-test.cpp b/zircon/system/core/netsvc/test/tftp-test.cpp
new file mode 100644
index 0000000..e17006c
--- /dev/null
+++ b/zircon/system/core/netsvc/test/tftp-test.cpp
@@ -0,0 +1,64 @@
+// 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 "tftp.h"
+
+#include <inet6/netifc.h>
+#include <zxtest/zxtest.h>
+
+#include "file-api.h"
+
+void update_timeouts() {}
+bool netbootloader() { return false; }
+const char* nodename() { return "test"; }
+void netboot_run_cmd(const char* cmd) {}
+
+void udp6_recv(void* data, size_t len,
+ const ip6_addr_t* daddr, uint16_t dport,
+ const ip6_addr_t* saddr, uint16_t sport) {}
+
+void netifc_recv(void* data, size_t len) {}
+bool netifc_send_pending() { return false; }
+
+namespace {
+
+class FakeFileApi : public netsvc::FileApiInterface {
+public:
+ ssize_t OpenRead(const char* filename) override { return 10; }
+ tftp_status OpenWrite(const char* filename, size_t size) override { return ZX_OK; }
+ tftp_status Read(void* data, size_t* length, off_t offset) override { return ZX_OK; }
+ tftp_status Write(const void* data, size_t* length, off_t offset) override { return ZX_OK; }
+ void Close() override {}
+ void Abort() override {}
+
+ bool is_write() override { return false; }
+ const char* filename() override { return "filename"; }
+};
+
+} // namespace
+
+extern netsvc::FileApiInterface* g_file_api;
+
+class TftpTest : public zxtest::Test {
+protected:
+ TftpTest() {
+ g_file_api = &fake_file_api_;
+ }
+
+ ~TftpTest() {
+ g_file_api = nullptr;
+ }
+
+ FakeFileApi fake_file_api_;
+};
+
+TEST_F(TftpTest, NextTimeout) {
+ ASSERT_EQ(tftp_next_timeout(), ZX_TIME_INFINITE);
+}
+
+TEST_F(TftpTest, HasPending) {
+ ASSERT_FALSE(tftp_has_pending());
+}
+
+// TODO(surajmalhotra): Synthesize some tftp packets for additional tests.
diff --git a/zircon/system/core/netsvc/tftp.cpp b/zircon/system/core/netsvc/tftp.cpp
index d46a6bc..4bafc4d 100644
--- a/zircon/system/core/netsvc/tftp.cpp
+++ b/zircon/system/core/netsvc/tftp.cpp
@@ -2,19 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include <errno.h>
-#include <fcntl.h>
+#include "tftp.h"
+
#include <stddef.h>
#include <stdio.h>
#include <string.h>
-#include <threads.h>
#include <unistd.h>
-#include <atomic>
-
#include <inet6/inet6.h>
#include <lib/fdio/spawn.h>
-#include <lib/sync/completion.h>
#include <tftp/tftp.h>
#include <zircon/assert.h>
#include <zircon/boot/netboot.h>
@@ -23,48 +19,17 @@
#include <zircon/syscalls.h>
#include <zircon/time.h>
-#include "board-name.h"
+#include "file-api.h"
#include "netsvc.h"
#define SCRATCHSZ 2048
-#define TFTP_TIMEOUT_SECS 1
+extern bool xfer_active;
-#define NB_IMAGE_PREFIX_LEN (strlen(NB_IMAGE_PREFIX))
-#define NB_FILENAME_PREFIX_LEN (strlen(NB_FILENAME_PREFIX))
+// Visible for test injection.
+netsvc::FileApiInterface* g_file_api = nullptr;
-// Identifies what the file being streamed over TFTP should be
-// used for.
-enum netfile_type_t {
- netboot, // A bootfs file
- paver, // A disk image which should be paved to disk
- board_name, // A file containing the board name.
- // Expected to return error if it doesn't match the current board name.
-};
-
-struct file_info_t {
- bool is_write;
- char filename[PATH_MAX + 1];
- netfile_type_t type;
-
- // Only valid when type == netfile_type_t::netboot.
- nbfile* netboot_file;
-
- // Only valid when type == netfile_type_t::paver.
- struct {
- int fd; // Pipe to paver process
- size_t size; // Total size of file
- zx_handle_t process;
-
- // Buffer used for stashing data from tftp until it can be written out to the paver
- zx_handle_t buffer_handle;
- uint8_t* buffer;
- std::atomic<unsigned int> buf_refcount;
- std::atomic<size_t> offset; // Buffer write offset (read offset is stored locally)
- thrd_t buf_copy_thrd;
- sync_completion_t data_ready; // Allows read thread to block on buffer writes
- } paver;
-};
+namespace {
struct transport_info_t {
ip6_addr_t dest_addr;
@@ -72,385 +37,41 @@
uint32_t timeout_ms;
};
-static char tftp_session_scratch[SCRATCHSZ];
+char tftp_session_scratch[SCRATCHSZ];
char tftp_out_scratch[SCRATCHSZ];
-static size_t last_msg_size = 0;
-static tftp_session* session = NULL;
-static file_info_t file_info;
-static transport_info_t transport_info;
+size_t last_msg_size = 0;
+tftp_session* session = nullptr;
+transport_info_t transport_info;
-std::atomic<bool> paving_in_progress = false;
-std::atomic<int> paver_exit_code = 0;
-zx_time_t tftp_next_timeout = ZX_TIME_INFINITE;
+zx_time_t g_tftp_next_timeout = ZX_TIME_INFINITE;
-static ssize_t file_open_read(const char* filename, void* cookie) {
- // Make sure all in-progress paving options have completed
- if (atomic_load(&paving_in_progress) == true) {
- return TFTP_ERR_SHOULD_WAIT;
- }
- if (std::atomic_load(&paver_exit_code) != 0) {
- printf("paver exited with error: %d\n", std::atomic_load(&paver_exit_code));
- std::atomic_store(&paver_exit_code, 0);
- return TFTP_ERR_IO;
- }
- file_info_t* file_info = reinterpret_cast<file_info_t*>(cookie);
- file_info->is_write = false;
- strncpy(file_info->filename, filename, PATH_MAX);
- file_info->filename[PATH_MAX] = '\0';
- file_info->netboot_file = NULL;
- size_t file_size;
- if (netfile_open(filename, O_RDONLY, &file_size) == 0) {
- return static_cast<ssize_t>(file_size);
- }
- return TFTP_ERR_NOT_FOUND;
+ssize_t file_open_read(const char* filename, void* cookie) {
+ auto* file_api = reinterpret_cast<netsvc::FileApiInterface*>(cookie);
+ return file_api->OpenRead(filename);
}
-static zx_status_t alloc_paver_buffer(file_info_t* file_info, size_t size) {
- zx_status_t status;
- status = zx_vmo_create(size, 0, &file_info->paver.buffer_handle);
- if (status != ZX_OK) {
- printf("netsvc: unable to allocate buffer VMO\n");
- return status;
- }
- zx_object_set_property(file_info->paver.buffer_handle, ZX_PROP_NAME, "paver", 5);
- uintptr_t buffer;
- status = zx_vmar_map(zx_vmar_root_self(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0,
- file_info->paver.buffer_handle, 0, size, &buffer);
- if (status != ZX_OK) {
- printf("netsvc: unable to map buffer\n");
- zx_handle_close(file_info->paver.buffer_handle);
- return status;
- }
- file_info->paver.buffer = reinterpret_cast<uint8_t*>(buffer);
- return ZX_OK;
+tftp_status file_open_write(const char* filename, size_t size, void* cookie) {
+ auto* file_api = reinterpret_cast<netsvc::FileApiInterface*>(cookie);
+ return file_api->OpenWrite(filename, size);
}
-static zx_status_t dealloc_paver_buffer(file_info_t* file_info) {
- zx_status_t status =
- zx_vmar_unmap(zx_vmar_root_self(), reinterpret_cast<uintptr_t>(file_info->paver.buffer),
- file_info->paver.size);
- if (status != ZX_OK) {
- printf("netsvc: failed to unmap paver buffer: %s\n", zx_status_get_string(status));
- goto done;
- }
-
- status = zx_handle_close(file_info->paver.buffer_handle);
- if (status != ZX_OK) {
- printf("netsvc: failed to close paver buffer handle: %s\n", zx_status_get_string(status));
- }
-
-done:
- file_info->paver.buffer = NULL;
- return status;
+tftp_status file_read(void* data, size_t* length, off_t offset, void* cookie) {
+ auto* file_api = reinterpret_cast<netsvc::FileApiInterface*>(cookie);
+ return file_api->Read(data, length, offset);
}
-static int drain_pipe(void* arg) {
- char buf[4096];
- int fd = static_cast<int>(reinterpret_cast<intptr_t>(arg));
-
- ssize_t sz;
- while ((sz = read(fd, buf, sizeof(buf) - 1)) > 0) {
- // ensure null termination
- buf[sz] = '\0';
- printf("%s", buf);
- }
-
- close(fd);
- return static_cast<int>(sz);
+tftp_status file_write(const void* data, size_t* length, off_t offset, void* cookie) {
+ auto* file_api = reinterpret_cast<netsvc::FileApiInterface*>(cookie);
+ return file_api->Write(data, length, offset);
}
-// Pushes all data from the paver buffer (filled by netsvc) into the paver input pipe. When
-// there's no data to copy, blocks on data_ready until more data is written into the buffer.
-static int paver_copy_buffer(void* arg) {
- file_info_t* file_info = reinterpret_cast<file_info_t*>(arg);
- size_t read_ndx = 0;
- int result = 0;
- zx_time_t last_reported = zx_clock_get_monotonic();
- while (read_ndx < file_info->paver.size) {
- sync_completion_reset(&file_info->paver.data_ready);
- size_t write_ndx = atomic_load(&file_info->paver.offset);
- if (write_ndx == read_ndx) {
- // 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 (sync_completion_wait(&file_info->paver.data_ready, ZX_SEC(5 * TFTP_TIMEOUT_SECS)) ==
- ZX_OK) {
- continue;
- }
- printf("netsvc: timed out while waiting for data in paver-copy thread\n");
- result = TFTP_ERR_TIMED_OUT;
- goto done;
- }
- while (read_ndx < write_ndx) {
- ssize_t r = write(file_info->paver.fd, &file_info->paver.buffer[read_ndx],
- write_ndx - read_ndx);
- if (r <= 0) {
- printf("netsvc: couldn't write to paver fd: %ld\n", r);
- result = TFTP_ERR_IO;
- goto done;
- }
- read_ndx += r;
- zx_time_t curr_time = zx_clock_get_monotonic();
- if (zx_time_sub_time(curr_time, last_reported) >= ZX_SEC(1)) {
- float complete =
- (static_cast<float>(read_ndx) / static_cast<float>(file_info->paver.size)) *
- 100.f;
- printf("netsvc: paver write progress %0.1f%%\n", complete);
- last_reported = curr_time;
- }
- }
- }
-done:
- close(file_info->paver.fd);
-
- unsigned int refcount = std::atomic_fetch_sub(&file_info->paver.buf_refcount, 1u);
- if (refcount == 1) {
- dealloc_paver_buffer(file_info);
- }
-
- // wait for the paver to complete, as executing the paver concurrently has
- // undefined behavior.
- zx_signals_t signals;
- zx_object_wait_one(file_info->paver.process, ZX_TASK_TERMINATED, zx_deadline_after(ZX_SEC(10)),
- &signals);
-
- zx_info_process_t proc_info;
- zx_object_get_info(file_info->paver.process, ZX_INFO_PROCESS, &proc_info, sizeof(proc_info),
- NULL, NULL);
-
- std::atomic_store(&paver_exit_code, static_cast<int>(proc_info.return_code));
- zx_handle_close(file_info->paver.process);
-
- if (result != 0) {
- printf("netsvc: copy exited prematurely (%d): expect paver errors\n", result);
- }
-
- // Extra protection against double-close.
- file_info->filename[0] = '\0';
- atomic_store(&paving_in_progress, false);
- return result;
+void file_close(void* cookie) {
+ auto* file_api = reinterpret_cast<netsvc::FileApiInterface*>(cookie);
+ return file_api->Close();
}
-static tftp_status paver_open_write(const char* filename, size_t size, file_info_t* file_info) {
- // paving an image to disk
- const char* argv[] = {"/boot/bin/install-disk-image", NULL, NULL, NULL, NULL};
-
- if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_BOARD_NAME_HOST_FILENAME)) {
- printf("netsvc: Running board name validation\n");
- file_info->type = board_name;
- return TFTP_NO_ERROR;
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_FVM_HOST_FILENAME)) {
- printf("netsvc: Running FVM Paver\n");
- argv[1] = "install-fvm";
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_BOOTLOADER_HOST_FILENAME)) {
- printf("netsvc: Running BOOTLOADER Paver\n");
- argv[1] = "install-bootloader";
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONA_HOST_FILENAME)) {
- printf("netsvc: Running ZIRCON-A Paver\n");
- argv[1] = "install-zircona";
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONB_HOST_FILENAME)) {
- printf("netsvc: Running ZIRCON-B Paver\n");
- argv[1] = "install-zirconb";
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_ZIRCONR_HOST_FILENAME)) {
- printf("netsvc: Running ZIRCON-R Paver\n");
- argv[1] = "install-zirconr";
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_VBMETAA_HOST_FILENAME)) {
- printf("netsvc: Running VBMETA-A Paver\n");
- argv[1] = "install-vbmetaa";
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_VBMETAB_HOST_FILENAME)) {
- printf("netsvc: Running VBMETA-B Paver\n");
- argv[1] = "install-vbmetab";
- } else if (!strcmp(filename + NB_IMAGE_PREFIX_LEN, NB_SSHAUTH_HOST_FILENAME)) {
- printf("netsvc: Installing SSH authorized_keys\n");
- argv[1] = "install-data-file";
- argv[2] = "--path";
- argv[3] = "ssh/authorized_keys";
- } else {
- fprintf(stderr, "netsvc: Unknown Paver\n");
- return TFTP_ERR_IO;
- }
-
- int fds[2];
- if (pipe(fds)) {
- return TFTP_ERR_IO;
- }
-
- int logfds[2];
- if (pipe(logfds)) {
- close(fds[0]);
- close(fds[1]);
- return TFTP_ERR_IO;
- }
-
- fdio_spawn_action_t actions[] = {
- {.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = "paver"}},
- {.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
- .fd = {.local_fd = fds[0], .target_fd = STDIN_FILENO}},
- {.action = FDIO_SPAWN_ACTION_TRANSFER_FD,
- .fd = {.local_fd = logfds[1], .target_fd = STDERR_FILENO}},
- };
-
- zx_status_t status =
- fdio_spawn_etc(ZX_HANDLE_INVALID, FDIO_SPAWN_CLONE_ALL, argv[0], argv, NULL,
- countof(actions), actions, &file_info->paver.process, NULL);
-
- if (status != ZX_OK) {
- printf("netsvc: tftp couldn't launch paver\n");
- goto err_close_fds;
- }
-
- thrd_t log_thrd;
- if ((thrd_create(&log_thrd, drain_pipe,
- reinterpret_cast<void*>(static_cast<uintptr_t>(logfds[0])))) == thrd_success) {
- thrd_detach(log_thrd);
- } else {
- printf("netsvc: couldn't create paver log message redirection thread\n");
- goto err_close_fds;
- }
-
- if ((status = alloc_paver_buffer(file_info, size)) != ZX_OK) {
- goto err_close_fds;
- }
-
- file_info->type = paver;
- file_info->paver.fd = fds[1];
- file_info->paver.size = size;
- // Both the netsvc thread and the paver copy thread access the buffer, and either
- // may be done with it first so we use a refcount to decide when to deallocate it
- std::atomic_store(&file_info->paver.buf_refcount, 2u);
- std::atomic_store(&file_info->paver.offset, 0ul);
- std::atomic_store(&paver_exit_code, 0);
- std::atomic_store(&paving_in_progress, true);
-
- if ((thrd_create(&file_info->paver.buf_copy_thrd, paver_copy_buffer,
- reinterpret_cast<void*>(file_info))) != thrd_success) {
- printf("netsvc: unable to launch buffer copy thread\n");
- status = ZX_ERR_NO_RESOURCES;
- goto dealloc_buffer;
- }
- thrd_detach(file_info->paver.buf_copy_thrd);
-
- return TFTP_NO_ERROR;
-
-dealloc_buffer:
- dealloc_paver_buffer(file_info);
-
-err_close_fds:
- close(fds[1]);
- close(logfds[0]);
- return status;
-}
-
-static tftp_status file_open_write(const char* filename, size_t size, void* cookie) {
- // Make sure all in-progress paving options have completed
- if (atomic_load(&paving_in_progress) == true) {
- return TFTP_ERR_SHOULD_WAIT;
- }
- if (atomic_load(&paver_exit_code) != 0) {
- atomic_store(&paver_exit_code, 0);
- return TFTP_ERR_IO;
- }
-
- file_info_t* file_info = reinterpret_cast<file_info_t*>(cookie);
- file_info->is_write = true;
- strncpy(file_info->filename, filename, PATH_MAX);
- file_info->filename[PATH_MAX] = '\0';
-
- if (netbootloader && !strncmp(filename, NB_FILENAME_PREFIX, NB_FILENAME_PREFIX_LEN)) {
- // netboot
- file_info->type = netboot;
- file_info->netboot_file = netboot_get_buffer(filename, size);
- if (file_info->netboot_file != NULL) {
- return TFTP_NO_ERROR;
- }
- } else if (netbootloader & !strncmp(filename, NB_IMAGE_PREFIX, NB_IMAGE_PREFIX_LEN)) {
- // paver
- tftp_status status = paver_open_write(filename, size, file_info);
- if (status != TFTP_NO_ERROR) {
- file_info->filename[0] = '\0';
- }
- return status;
- } else {
- // netcp
- if (netfile_open(filename, O_WRONLY, NULL) == 0) {
- return TFTP_NO_ERROR;
- }
- }
- return TFTP_ERR_INVALID_ARGS;
-}
-
-static tftp_status file_read(void* data, size_t* length, off_t offset, void* cookie) {
- if (length == NULL) {
- return TFTP_ERR_INVALID_ARGS;
- }
- ssize_t read_len = netfile_offset_read(data, offset, *length);
- if (read_len < 0) {
- return TFTP_ERR_IO;
- }
- *length = static_cast<size_t>(read_len);
- return TFTP_NO_ERROR;
-}
-
-static tftp_status file_write(const void* data, size_t* length, off_t offset, void* cookie) {
- if (length == NULL) {
- return TFTP_ERR_INVALID_ARGS;
- }
- file_info_t* file_info = reinterpret_cast<file_info_t*>(cookie);
- if (file_info->type == netboot && file_info->netboot_file != NULL) {
- nbfile* nb_file = file_info->netboot_file;
- if ((static_cast<size_t>(offset) > nb_file->size) || (offset + *length) > nb_file->size) {
- return TFTP_ERR_INVALID_ARGS;
- }
- memcpy(nb_file->data + offset, data, *length);
- nb_file->offset = offset + *length;
- return TFTP_NO_ERROR;
- } else if (file_info->type == paver) {
- if (!atomic_load(&paving_in_progress)) {
- printf("netsvc: paver exited prematurely with %d\n", atomic_load(&paver_exit_code));
- atomic_store(&paver_exit_code, 0);
- return TFTP_ERR_IO;
- }
-
- if ((static_cast<size_t>(offset) > file_info->paver.size) ||
- (offset + *length) > file_info->paver.size) {
- return TFTP_ERR_INVALID_ARGS;
- }
- memcpy(&file_info->paver.buffer[offset], data, *length);
- size_t new_offset = offset + *length;
- atomic_store(&file_info->paver.offset, new_offset);
- // Wake the paver thread, if it is waiting for data
- sync_completion_signal(&file_info->paver.data_ready);
- return TFTP_NO_ERROR;
- } else if (file_info->type == board_name) {
- return check_board_name((const char*)data, *length)
- ? TFTP_NO_ERROR
- : TFTP_ERR_BAD_STATE;
- } else {
- ssize_t write_result =
- netfile_offset_write(reinterpret_cast<const char*>(data), offset, *length);
- if (static_cast<size_t>(write_result) == *length) {
- return TFTP_NO_ERROR;
- }
- if (write_result == -EBADF) {
- return TFTP_ERR_BAD_STATE;
- }
- return TFTP_ERR_IO;
- }
-}
-
-static void file_close(void* cookie) {
- file_info_t* file_info = reinterpret_cast<file_info_t*>(cookie);
- if (file_info->type == netboot && file_info->netboot_file == NULL) {
- netfile_close();
- } else if (file_info->type == paver) {
- unsigned int refcount = std::atomic_fetch_sub(&file_info->paver.buf_refcount, 1u);
- if (refcount == 1) {
- dealloc_paver_buffer(file_info);
- }
- }
-}
-
-static tftp_status transport_send(void* data, size_t len, void* transport_cookie) {
+tftp_status transport_send(void* data, size_t len, void* transport_cookie) {
transport_info_t* transport_info = reinterpret_cast<transport_info_t*>(transport_cookie);
zx_status_t status = udp6_send(data, len, &transport_info->dest_addr, transport_info->dest_port,
NB_TFTP_OUTGOING_PORT, true);
@@ -461,28 +82,30 @@
// The timeout is relative to sending instead of receiving a packet, since there are some
// received packets we want to ignore (duplicate ACKs).
if (transport_info->timeout_ms != 0) {
- tftp_next_timeout = zx_deadline_after(ZX_MSEC(transport_info->timeout_ms));
+ g_tftp_next_timeout = zx_deadline_after(ZX_MSEC(transport_info->timeout_ms));
update_timeouts();
}
return TFTP_NO_ERROR;
}
-static int transport_timeout_set(uint32_t timeout_ms, void* transport_cookie) {
+int transport_timeout_set(uint32_t timeout_ms, void* transport_cookie) {
transport_info_t* transport_info = reinterpret_cast<transport_info_t*>(transport_cookie);
transport_info->timeout_ms = timeout_ms;
return 0;
}
-extern bool xfer_active;
-
-static void initialize_connection(const ip6_addr_t* saddr, uint16_t sport) {
+void initialize_connection(const ip6_addr_t* saddr, uint16_t sport) {
int ret = tftp_init(&session, tftp_session_scratch, sizeof(tftp_session_scratch));
if (ret != TFTP_NO_ERROR) {
printf("netsvc: failed to initiate tftp session\n");
- session = NULL;
+ session = nullptr;
return;
}
+ if (g_file_api == nullptr) {
+ g_file_api = new netsvc::FileApi(netbootloader());
+ }
+
// Initialize file interface
tftp_file_interface file_ifc = {file_open_read, file_open_write, file_read, file_write,
file_close};
@@ -498,26 +121,35 @@
xfer_active = true;
}
-static void end_connection() {
- session = NULL;
- tftp_next_timeout = ZX_TIME_INFINITE;
+void end_connection() {
+ session = nullptr;
+ g_tftp_next_timeout = ZX_TIME_INFINITE;
xfer_active = false;
}
+void report_metrics() {
+ char buf[256];
+ if (session && tftp_get_metrics(session, buf, sizeof(buf)) == TFTP_NO_ERROR) {
+ printf("netsvc: metrics: %s\n", buf);
+ }
+}
+
+} // namespace
+
+zx_time_t tftp_next_timeout() { return g_tftp_next_timeout; }
+
void tftp_timeout_expired() {
tftp_status result =
tftp_timeout(session, tftp_out_scratch, &last_msg_size, sizeof(tftp_out_scratch),
- &transport_info.timeout_ms, &file_info);
+ &transport_info.timeout_ms, g_file_api);
if (result == TFTP_ERR_TIMED_OUT) {
printf("netsvc: excessive timeouts, dropping tftp connection\n");
- file_close(&file_info);
+ g_file_api->Abort();
end_connection();
- netfile_abort_write();
} else if (result < 0) {
printf("netsvc: failed to generate timeout response, dropping tftp connection\n");
- file_close(&file_info);
+ g_file_api->Abort();
end_connection();
- netfile_abort_write();
} else {
if (last_msg_size > 0) {
tftp_status send_result =
@@ -529,13 +161,6 @@
}
}
-static void report_metrics() {
- char buf[256];
- if (session && tftp_get_metrics(session, buf, sizeof(buf)) == TFTP_NO_ERROR) {
- printf("netsvc: metrics: %s\n", buf);
- }
-}
-
void tftp_recv(void* data, size_t len, const ip6_addr_t* daddr, uint16_t dport,
const ip6_addr_t* saddr, uint16_t sport) {
if (dport == NB_TFTP_INCOMING_PORT) {
@@ -560,21 +185,20 @@
.outbuf_sz = &last_msg_size,
.err_msg = err_msg,
.err_msg_sz = sizeof(err_msg)};
- tftp_status status = tftp_handle_msg(session, &transport_info, &file_info, &handler_opts);
+ tftp_status status = tftp_handle_msg(session, &transport_info, g_file_api, &handler_opts);
switch (status) {
case TFTP_NO_ERROR:
return;
case TFTP_TRANSFER_COMPLETED:
- printf("netsvc: tftp %s of file %s completed\n", file_info.is_write ? "write" : "read",
- file_info.filename);
+ printf("netsvc: tftp %s of file %s completed\n", g_file_api->is_write() ? "write" : "read",
+ g_file_api->filename());
report_metrics();
break;
case TFTP_ERR_SHOULD_WAIT:
break;
default:
printf("netsvc: %s\n", err_msg);
- netfile_abort_write();
- file_close(&file_info);
+ g_file_api->Abort();
report_metrics();
break;
}
@@ -588,7 +212,7 @@
void tftp_send_next() {
last_msg_size = sizeof(tftp_out_scratch);
tftp_prepare_data(session, tftp_out_scratch, &last_msg_size, &transport_info.timeout_ms,
- &file_info);
+ g_file_api);
if (last_msg_size) {
transport_send(tftp_out_scratch, last_msg_size, &transport_info);
}
diff --git a/zircon/system/core/netsvc/tftp.h b/zircon/system/core/netsvc/tftp.h
new file mode 100644
index 0000000..5a5e65a
--- /dev/null
+++ b/zircon/system/core/netsvc/tftp.h
@@ -0,0 +1,19 @@
+// 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.
+
+#pragma once
+
+#include <inet6/inet6.h>
+
+#define TFTP_TIMEOUT_SECS 1
+
+zx_time_t tftp_next_timeout();
+
+void tftp_recv(void* data, size_t len, const ip6_addr_t* daddr, uint16_t dport,
+ const ip6_addr_t* saddr, uint16_t sport);
+
+void tftp_timeout_expired();
+
+bool tftp_has_pending();
+void tftp_send_next();
diff --git a/zircon/system/utest/BUILD.gn b/zircon/system/utest/BUILD.gn
index f6ecff4..ef284e2 100644
--- a/zircon/system/utest/BUILD.gn
+++ b/zircon/system/utest/BUILD.gn
@@ -23,6 +23,7 @@
"$zx/system/core/devmgr/devhost:devhost-test",
"$zx/system/core/devmgr/fshost:block-watcher-test",
"$zx/system/core/devmgr/fshost:fshost-test",
+ "$zx/system/core/netsvc:netsvc-test",
"$zx/system/core/svchost:crashsvc-test",
"$zx/system/core/virtcon:virtual-console-test",
"$zx/system/dev/backlight/sg-micro:sgm37603a-test",