// Copyright 2021 The Chromium 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 "base/test/scoped_dev_zero_fuchsia.h" #include #include #include #include #include #include #include #include #include #include #include #include "base/bind.h" #include "base/callback.h" #include "base/check.h" #include "base/fuchsia/fuchsia_logging.h" #include "base/message_loop/message_pump_type.h" #include "base/run_loop.h" namespace base { // ScopedDevZero::Server ------------------------------------------------------- // A helper that lives on a dedicated thread, serving up a pesudo-dir containing // a "zero" file. class ScopedDevZero::Server { public: // Creates the pseudo-dir representing /dev as `directory_request` and serves // up a "zero" file within it. `on_initialized` is run with the status. Server(fidl::InterfaceRequest directory_request, OnceCallback on_initialized); Server(const Server&) = delete; Server& operator=(const Server&) = delete; ~Server() = default; private: vfs::PseudoDir dev_dir_; }; ScopedDevZero::Server::Server( fidl::InterfaceRequest directory_request, OnceCallback on_initialized) { // VMOs are filled with zeros at construction, so create a big one and serve // it as "zero" within the given `directory_request`. All virtual pages in the // VMO are backed by the singular physical "zero page", so no memory is // allocated until a write occurs (which will never happen). On the server // end, the VMO should not take up address space on account of never being // mapped. On the read side (libfdio) it may get mapped, but only for the size // of a given read - it may also just use the zx_vmo_read syscall to avoid // ever needing to map it. zx::vmo vmo; auto status = zx::vmo::create(/*size=*/UINT32_MAX, /*options=*/0, &vmo); ZX_LOG_IF(ERROR, status != ZX_OK, status); if (status == ZX_OK) { status = dev_dir_.AddEntry( "zero", std::make_unique(std::move(vmo), /*length=*/UINT32_MAX)); ZX_LOG_IF(ERROR, status != ZX_OK, status); } if (status == ZX_OK) { status = dev_dir_.Serve(fuchsia::io::OpenFlags::RIGHT_READABLE, directory_request.TakeChannel()); ZX_LOG_IF(ERROR, status != ZX_OK, status); } std::move(on_initialized).Run(status); } // ScopedDevZero --------------------------------------------------------------- // static ScopedDevZero* ScopedDevZero::instance_ = nullptr; // static scoped_refptr ScopedDevZero::Get() { if (instance_) return WrapRefCounted(instance_); scoped_refptr result = AdoptRef(new ScopedDevZero); return result->Initialize() ? std::move(result) : nullptr; } ScopedDevZero::ScopedDevZero() : io_thread_("/dev/zero") { DCHECK_EQ(instance_, nullptr); instance_ = this; } ScopedDevZero::~ScopedDevZero() { DCHECK_EQ(instance_, this); if (global_namespace_) fdio_ns_unbind(std::exchange(global_namespace_, nullptr), "/dev"); instance_ = nullptr; } bool ScopedDevZero::Initialize() { auto status = fdio_ns_get_installed(&global_namespace_); if (status != ZX_OK) { ZX_LOG(ERROR, status); return false; } if (!io_thread_.StartWithOptions(Thread::Options(MessagePumpType::IO, 0))) return false; zx::channel client; zx::channel request; status = zx::channel::create(0, &client, &request); ZX_CHECK(status == ZX_OK, status); RunLoop run_loop; server_ = SequenceBound( io_thread_.task_runner(), fidl::InterfaceRequest(std::move(request)), base::BindOnce( [](base::OnceClosure quit_loop, zx_status_t& status, zx_status_t init_status) { status = init_status; std::move(quit_loop).Run(); }, run_loop.QuitClosure(), std::ref(status))); run_loop.Run(); if (status != ZX_OK) return false; // Install the directory holding "zero" into the global namespace as /dev. // This relies on the component not asking for any /dev entries in its // manifest, as nested namespaces are not allowed. status = fdio_ns_bind(global_namespace_, "/dev", client.release()); if (status != ZX_OK) { ZX_LOG(ERROR, status); global_namespace_ = nullptr; server_.Reset(); return false; } return true; } } // namespace base