blob: b76d35f6b2298a4477f98b5a64ed648d1103033c [file]
// Copyright 2026 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 <fidl/fuchsia.kernel/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/userboot/testing/fixture.h>
#include <lib/zbi-format/internal/bootfs.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zbitl/image.h>
#include <lib/zbitl/vmo.h>
#include <lib/zx/job.h>
#include <lib/zx/resource.h>
#include <lib/zx/vmo.h>
#include <zircon/processargs.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace {
using ::testing::HasSubstr;
using UserbootTests = userboot::testing::Fixture;
const zx::vmo& GetVdsoVmo() {
static const zx::vmo vdso{zx_take_startup_handle(PA_HND(PA_VMO_VDSO, 0))};
return vdso;
}
zx::vmo CreateBootfsVmo(const std::vector<std::pair<std::string, std::vector<uint8_t>>>& files) {
uint32_t total_dirsize = 0;
for (const auto& [name, content] : files) {
uint32_t name_len = static_cast<uint32_t>(name.length() + 1);
total_dirsize += ZBI_BOOTFS_DIRENT_SIZE(name_len);
}
uint32_t data_start_offset = ZBI_BOOTFS_PAGE_ALIGN(sizeof(zbi_bootfs_header_t) + total_dirsize);
uint32_t total_bootfs_size = data_start_offset;
for (const auto& [name, content] : files) {
total_bootfs_size += ZBI_BOOTFS_PAGE_ALIGN(static_cast<uint32_t>(content.size()));
}
std::vector<uint8_t> bootfs_buf(total_bootfs_size, 0);
auto* header = reinterpret_cast<zbi_bootfs_header_t*>(bootfs_buf.data());
header->magic = ZBI_BOOTFS_MAGIC;
header->dirsize = total_dirsize;
uint32_t current_dir_off = sizeof(zbi_bootfs_header_t);
uint32_t current_data_off = data_start_offset;
for (const auto& [name, content] : files) {
uint32_t name_len = static_cast<uint32_t>(name.length() + 1);
auto* dirent = reinterpret_cast<zbi_bootfs_dirent_t*>(&bootfs_buf[current_dir_off]);
dirent->name_len = name_len;
dirent->data_len = static_cast<uint32_t>(content.size());
dirent->data_off = current_data_off;
memcpy(dirent->name, name.c_str(), name_len);
if (!content.empty()) {
memcpy(&bootfs_buf[current_data_off], content.data(), content.size());
}
current_dir_off += ZBI_BOOTFS_DIRENT_SIZE(name_len);
current_data_off += ZBI_BOOTFS_PAGE_ALIGN(static_cast<uint32_t>(content.size()));
}
zx::vmo bootfs_vmo;
EXPECT_EQ(zx::vmo::create(bootfs_buf.size(), 0, &bootfs_vmo), ZX_OK);
EXPECT_EQ(bootfs_vmo.write(bootfs_buf.data(), 0, bootfs_buf.size()), ZX_OK);
return bootfs_vmo;
}
zx::vmo CreateZbiVmo(const std::string& cmdline, const zx::vmo& bootfs_vmo) {
zx::vmo zbi_vmo;
EXPECT_EQ(zx::vmo::create(0, ZX_VMO_RESIZABLE, &zbi_vmo), ZX_OK);
zbitl::Image image(zx::unowned_vmo{zbi_vmo});
EXPECT_TRUE(image.clear().is_ok());
if (!cmdline.empty()) {
auto res = image.Append(zbi_header_t{.type = ZBI_TYPE_CMDLINE}, zbitl::AsBytes(cmdline));
EXPECT_TRUE(res.is_ok());
}
uint64_t bootfs_size = 0;
EXPECT_EQ(bootfs_vmo.get_size(&bootfs_size), ZX_OK);
std::vector<uint8_t> bootfs_data(bootfs_size);
EXPECT_EQ(bootfs_vmo.read(bootfs_data.data(), 0, bootfs_size), ZX_OK);
auto res =
image.Append(zbi_header_t{.type = ZBI_TYPE_STORAGE_BOOTFS}, zbitl::AsBytes(bootfs_data));
EXPECT_TRUE(res.is_ok());
return zbi_vmo;
}
std::vector<zx::handle> CreateUserbootHandles(zx::job root_job, const zx::vmo& zbi_vmo) {
std::vector<zx::handle> handles;
EXPECT_TRUE(root_job.is_valid());
EXPECT_EQ(root_job.set_property(ZX_PROP_NAME, "root", 4), ZX_OK);
handles.push_back(std::move(root_job));
zx::result client = component::Connect<fuchsia_kernel::VmexResource>();
EXPECT_TRUE(client.is_ok());
if (client.is_ok()) {
fidl::WireResult result = fidl::WireCall(client.value())->Get();
EXPECT_TRUE(result.ok());
if (result.ok()) {
zx::resource system_resource = std::move(result.value().resource);
handles.push_back(std::move(system_resource));
}
}
zx::vmo zbi_dup;
EXPECT_EQ(zbi_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &zbi_dup), ZX_OK);
EXPECT_EQ(zbi_dup.set_property(ZX_PROP_NAME, "zbi", 3), ZX_OK);
handles.push_back(std::move(zbi_dup));
const zx::vmo& vdso_vmo = GetVdsoVmo();
EXPECT_TRUE(vdso_vmo.is_valid());
zx::vmo vdso_dup;
EXPECT_EQ(vdso_vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vdso_dup), ZX_OK);
EXPECT_EQ(vdso_dup.set_property(ZX_PROP_NAME, "vdso/stable", 11), ZX_OK);
handles.push_back(std::move(vdso_dup));
return handles;
}
TEST_F(UserbootTests, UserbootRust) {
auto child_vmo = userboot::testing::GetExecutable("/pkg/test/userboot-lib-static-pie-test");
ASSERT_TRUE(child_vmo.is_ok()) << child_vmo.status_string();
uint64_t child_size = 0;
ASSERT_EQ(child_vmo->get_size(&child_size), ZX_OK);
std::vector<uint8_t> child_bytes(child_size);
ASSERT_EQ(child_vmo->read(child_bytes.data(), 0, child_size), ZX_OK);
zx::vmo bootfs_vmo = CreateBootfsVmo({{"bin/userboot-child", child_bytes}});
zx::vmo zbi_vmo = CreateZbiVmo("userboot.next=bin/userboot-child", bootfs_vmo);
userboot::testing::TestJob test_job;
test_job.Init();
std::vector<zx::handle> handles = CreateUserbootHandles(test_job.Get(), zbi_vmo);
ASSERT_NO_FATAL_FAILURE(Launch("/pkg/bin/userboot_rust", std::move(handles)));
auto result = Wait();
ASSERT_TRUE(result.is_ok());
EXPECT_EQ(*result, 0);
std::string log = FinishLog();
EXPECT_THAT(log, HasSubstr("Started child process: bin/userboot-child")) << log;
EXPECT_THAT(log, HasSubstr("Hello from userland!")) << log;
}
TEST_F(UserbootTests, UserbootRustTest) {
auto child_vmo = userboot::testing::GetExecutable("/pkg/test/userboot-lib-static-pie-test");
ASSERT_TRUE(child_vmo.is_ok()) << child_vmo.status_string();
uint64_t child_size = 0;
ASSERT_EQ(child_vmo->get_size(&child_size), ZX_OK);
std::vector<uint8_t> child_bytes(child_size);
ASSERT_EQ(child_vmo->read(child_bytes.data(), 0, child_size), ZX_OK);
zx::vmo bootfs_vmo = CreateBootfsVmo({{"test/userboot-child", child_bytes}});
zx::vmo zbi_vmo = CreateZbiVmo("userboot.test.next=test/userboot-child", bootfs_vmo);
userboot::testing::TestJob test_job;
test_job.Init();
std::vector<zx::handle> handles = CreateUserbootHandles(test_job.Get(), zbi_vmo);
ASSERT_NO_FATAL_FAILURE(Launch("/pkg/bin/userboot_test_rust", std::move(handles)));
auto result = Wait();
ASSERT_TRUE(result.is_ok());
EXPECT_EQ(*result, 0);
std::string log = FinishLog();
EXPECT_THAT(log, HasSubstr("Started child process: test/userboot-child")) << log;
EXPECT_THAT(log, HasSubstr("Hello from userland!")) << log;
}
} // namespace