| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/lib/loader_service/loader_service_test_fixture.h" |
| |
| #include <fuchsia/kernel/llcpp/fidl.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fidl/llcpp/memory.h> |
| #include <lib/fidl/llcpp/string_view.h> |
| #include <lib/zx/channel.h> |
| #include <lib/zx/object.h> |
| #include <lib/zx/resource.h> |
| #include <lib/zx/status.h> |
| #include <zircon/errors.h> |
| #include <zircon/limits.h> |
| |
| #include <string_view> |
| |
| #define ASSERT_OK(expr) ASSERT_EQ(ZX_OK, expr) |
| #define EXPECT_OK(expr) EXPECT_EQ(ZX_OK, expr) |
| |
| namespace loader { |
| namespace test { |
| |
| namespace fldsvc = ::llcpp::fuchsia::ldsvc; |
| namespace fkernel = ::llcpp::fuchsia::kernel; |
| |
| namespace { |
| |
| zx_rights_t get_rights(const zx::object_base& handle) { |
| zx_info_handle_basic_t info; |
| zx_status_t status = handle.get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), nullptr, nullptr); |
| return status == ZX_OK ? info.rights : ZX_RIGHT_NONE; |
| } |
| |
| } // namespace |
| |
| void LoaderServiceTest::TearDown() { |
| if (vfs_) { |
| std::optional<zx_status_t> shutdown_status; |
| vfs_->Shutdown([&](zx_status_t status) { shutdown_status = status; }); |
| RunLoopUntil([&]() { return shutdown_status.has_value(); }); |
| ASSERT_OK(shutdown_status.value()); |
| } |
| } |
| |
| void LoaderServiceTest::CreateTestDirectory(std::vector<TestDirectoryEntry> config, |
| fbl::unique_fd* root_fd) { |
| ASSERT_FALSE(vfs_); |
| ASSERT_FALSE(root_dir_); |
| |
| ASSERT_OK(memfs::Vfs::Create("<tmp>", &vfs_, &root_dir_)); |
| vfs_->SetDispatcher(fs_loop_.dispatcher()); |
| |
| for (auto entry : config) { |
| ASSERT_NO_FATAL_FAILURE(AddDirectoryEntry(root_dir_, entry)); |
| } |
| |
| zx::channel client, server; |
| ASSERT_OK(zx::channel::create(0, &client, &server)); |
| ASSERT_OK(vfs_->ServeDirectory(fbl::RefPtr(root_dir_), std::move(server))); |
| |
| // Must start fs_loop before fdio_fd_create, since that will attempt to Describe the directory. |
| ASSERT_OK(fs_loop_.StartThread("fs_loop")); |
| ASSERT_OK(fdio_fd_create(client.release(), root_fd->reset_and_get_address())); |
| |
| // The loader needs a separate thread from the FS because it uses synchronous fd-based I/O. |
| ASSERT_OK(loader_loop_.StartThread("loader_loop")); |
| } |
| |
| void LoaderServiceTest::AddDirectoryEntry(const fbl::RefPtr<memfs::VnodeDir>& root, |
| TestDirectoryEntry entry) { |
| ASSERT_FALSE(entry.path.empty() || entry.path.front() == '/' || entry.path.back() == '/'); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(ZX_PAGE_SIZE, 0, &vmo)); |
| ASSERT_OK(vmo.write(entry.file_contents.data(), 0, entry.file_contents.size())); |
| if (entry.executable) { |
| auto vmex_rsrc = GetVmexResource(); |
| ASSERT_OK(vmex_rsrc.status_value()); |
| ASSERT_TRUE(vmex_rsrc.value()->is_valid()); |
| ASSERT_OK(vmo.replace_as_executable(*vmex_rsrc.value(), &vmo)); |
| } |
| |
| fbl::RefPtr<memfs::VnodeDir> dir(root); |
| std::string_view view(entry.path); |
| while (true) { |
| size_t next = view.find('/'); |
| if (next == std::string_view::npos) { |
| // No more subdirectories; create vnode for vmo, then done! |
| ASSERT_FALSE(view.empty()); |
| ASSERT_OK(dir->CreateFromVmo(view, vmo.release(), 0, entry.file_contents.size())); |
| return; |
| } else { |
| // Create subdirectory if it doesn't already exist. |
| std::string_view subdir(view.substr(0, next)); |
| ASSERT_FALSE(subdir.empty()); |
| |
| fbl::RefPtr<fs::Vnode> out; |
| zx_status_t status = dir->Lookup(subdir, &out); |
| if (status == ZX_ERR_NOT_FOUND) { |
| status = dir->Create(subdir, S_IFDIR, &out); |
| } |
| ASSERT_OK(status); |
| |
| dir = fbl::RefPtr<memfs::VnodeDir>::Downcast(std::move(out)); |
| view.remove_prefix(next + 1); |
| } |
| } |
| } |
| |
| void LoaderServiceTest::LoadObject(fldsvc::Loader::SyncClient& client, std::string name, |
| zx::status<std::string> expected) { |
| auto result = client.LoadObject(fidl::unowned_str(name)); |
| ASSERT_TRUE(result.ok()); |
| auto response = result.Unwrap(); |
| ASSERT_EQ(expected.status_value(), response->rv); |
| |
| zx::vmo vmo = std::move(response->object); |
| if (expected.is_error()) { |
| ASSERT_FALSE(vmo.is_valid()); |
| } else { |
| ASSERT_TRUE(vmo.is_valid()); |
| ASSERT_EQ(get_rights(vmo) & ZX_RIGHT_EXECUTE, ZX_RIGHT_EXECUTE); |
| |
| char data[ZX_PAGE_SIZE] = {}; |
| ASSERT_OK(vmo.read(data, 0, ZX_PAGE_SIZE)); |
| ASSERT_EQ(std::string(data), expected.value()); |
| } |
| } |
| |
| void LoaderServiceTest::Config(fldsvc::Loader::SyncClient& client, std::string config, |
| zx::status<zx_status_t> expected) { |
| auto result = client.Config(fidl::StringView(fidl::unowned_ptr(config.data()), config.size())); |
| ASSERT_EQ(result.status(), expected.status_value()); |
| if (expected.is_ok()) { |
| ASSERT_EQ(result.Unwrap()->rv, expected.value()); |
| } |
| } |
| |
| // static |
| zx::status<zx::unowned_resource> LoaderServiceTest::GetVmexResource() { |
| static const std::string kVmexResourcePath = "/svc/" + std::string(fkernel::VmexResource::Name); |
| |
| static zx::resource vmex_resource; |
| if (!vmex_resource.is_valid()) { |
| zx::channel client, server; |
| auto status = zx::make_status(zx::channel::create(0, &client, &server)); |
| if (status.is_error()) { |
| return status.take_error(); |
| } |
| status = zx::make_status(fdio_service_connect(kVmexResourcePath.c_str(), server.release())); |
| if (status.is_error()) { |
| return status.take_error(); |
| } |
| |
| auto result = fkernel::VmexResource::Call::Get(client.borrow()); |
| if (!result.ok()) { |
| return zx::error(result.status()); |
| } |
| vmex_resource = std::move(result->vmex_resource); |
| } |
| return zx::ok(vmex_resource.borrow()); |
| } |
| |
| } // namespace test |
| } // namespace loader |