[fuchsia] Introduce ScopedDevZero.
FileUtilTest.ReadFileToStringWithUnknownFileSize opens /dev/zero and
reads from it. This new ScopedDevZero class allows us to move away from
relying on /dev/zero being provided by the runtime.
The attempt to make IPCSendFdsTest.DescriptorTest use ScopedDevZero
doesn't quite work, so it is disabled for now.
Bug: 1256502,1272424
Change-Id: Ib8cf077d40deb0063a21fe05c43ed6c7372ee198
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3256795
Auto-Submit: Greg Thompson <grt@chromium.org>
Reviewed-by: David Dorwin <ddorwin@chromium.org>
Reviewed-by: Ken Rockot <rockot@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Fabrice de Gans <fdegans@chromium.org>
Commit-Queue: Greg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#947084}
diff --git a/base/files/file_util_unittest.cc b/base/files/file_util_unittest.cc
index 6294c81..95655c4 100644
--- a/base/files/file_util_unittest.cc
+++ b/base/files/file_util_unittest.cc
@@ -38,6 +38,7 @@
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/multiprocess_test.h"
+#include "base/test/task_environment.h"
#include "base/test/test_file_util.h"
#include "base/test/test_timeouts.h"
#include "base/threading/platform_thread.h"
@@ -45,6 +46,7 @@
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
+#include "build/os_buildflags.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
#include "testing/platform_test.h"
@@ -80,6 +82,10 @@
#include "base/android/content_uri_utils.h"
#endif
+#if BUILDFLAG(IS_FUCHSIA)
+#include "base/test/scoped_dev_zero_fuchsia.h"
+#endif
+
// This macro helps avoid wrapped lines in the test structs.
#define FPL(x) FILE_PATH_LITERAL(x)
@@ -3167,6 +3173,11 @@
#if !defined(OS_WIN)
TEST_F(FileUtilTest, ReadFileToStringWithUnknownFileSize) {
+#if BUILDFLAG(IS_FUCHSIA)
+ test::TaskEnvironment task_environment;
+ auto dev_zero = ScopedDevZero::Get();
+ ASSERT_TRUE(dev_zero);
+#endif
FilePath file_path("/dev/zero");
std::string data = "temp";
diff --git a/base/test/BUILD.gn b/base/test/BUILD.gn
index bccfbdd..078e49b 100644
--- a/base/test/BUILD.gn
+++ b/base/test/BUILD.gn
@@ -261,6 +261,8 @@
"../fuchsia/test_component_context_for_process.h",
"../fuchsia/test_component_controller.cc",
"../fuchsia/test_component_controller.h",
+ "scoped_dev_zero_fuchsia.cc",
+ "scoped_dev_zero_fuchsia.h",
]
}
diff --git a/base/test/scoped_dev_zero_fuchsia.cc b/base/test/scoped_dev_zero_fuchsia.cc
new file mode 100644
index 0000000..3882445
--- /dev/null
+++ b/base/test/scoped_dev_zero_fuchsia.cc
@@ -0,0 +1,148 @@
+// 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 <fuchsia/io/cpp/fidl.h>
+#include <lib/fdio/namespace.h>
+#include <lib/fidl/cpp/interface_request.h>
+#include <lib/vfs/cpp/pseudo_dir.h>
+#include <lib/vfs/cpp/vmo_file.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/vmo.h>
+#include <stdint.h>
+#include <zircon/types.h>
+
+#include <functional>
+#include <utility>
+
+#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<::fuchsia::io::Directory> directory_request,
+ OnceCallback<void(zx_status_t status)> on_initialized);
+ Server(const Server&) = delete;
+ Server& operator=(const Server&) = delete;
+ ~Server() = default;
+
+ private:
+ vfs::PseudoDir dev_dir_;
+};
+
+ScopedDevZero::Server::Server(
+ fidl::InterfaceRequest<fuchsia::io::Directory> directory_request,
+ OnceCallback<void(zx_status_t status)> 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<vfs::VmoFile>(std::move(vmo), /*offset=*/0,
+ /*length=*/UINT32_MAX));
+ ZX_LOG_IF(ERROR, status != ZX_OK, status);
+ }
+
+ if (status == ZX_OK) {
+ status = dev_dir_.Serve(fuchsia::io::OPEN_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> ScopedDevZero::Get() {
+ if (instance_)
+ return WrapRefCounted(instance_);
+ scoped_refptr<ScopedDevZero> 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<Server>(
+ io_thread_.task_runner(),
+ fidl::InterfaceRequest<::fuchsia::io::Directory>(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
diff --git a/base/test/scoped_dev_zero_fuchsia.h b/base/test/scoped_dev_zero_fuchsia.h
new file mode 100644
index 0000000..2a0115d9
--- /dev/null
+++ b/base/test/scoped_dev_zero_fuchsia.h
@@ -0,0 +1,54 @@
+// 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.
+
+#ifndef BASE_TEST_SCOPED_DEV_ZERO_FUCHSIA_H_
+#define BASE_TEST_SCOPED_DEV_ZERO_FUCHSIA_H_
+
+#include <lib/fdio/namespace.h>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/threading/sequence_bound.h"
+#include "base/threading/thread.h"
+
+namespace base {
+
+// An object that causes /dev/zero to exist during its lifetime. A reference to
+// this class may be held by tests that require access to /dev/zero for the
+// lifetime of that need.
+class ScopedDevZero final : public RefCounted<ScopedDevZero> {
+ public:
+ REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
+
+ // Returns a reference to the process-global /dev/zero. This must only be
+ // called, and the returned reference released, on the main thread. Returns
+ // null in case of failure to create the instance. It is good practice for
+ // tests to ASSERT the returned pointer.
+ static scoped_refptr<ScopedDevZero> Get();
+
+ ScopedDevZero(const ScopedDevZero&) = delete;
+ ScopedDevZero operator=(const ScopedDevZero&) = delete;
+
+ private:
+ friend class RefCounted<ScopedDevZero>;
+ class Server;
+
+ ScopedDevZero();
+ ~ScopedDevZero();
+
+ // Spins off the server thread and binds its pesudo-dir to /dev, returning
+ // true if all goes well, or false in case of any error.
+ bool Initialize();
+
+ // A raw pointer to the process's single instance. Multiple references to this
+ // instance may be handed out to consumers.
+ static ScopedDevZero* instance_;
+ Thread io_thread_;
+ fdio_ns_t* global_namespace_ = nullptr;
+ SequenceBound<Server> server_;
+};
+
+} // namespace base
+
+#endif // BASE_TEST_SCOPED_DEV_ZERO_FUCHSIA_H_
diff --git a/build/config/fuchsia/test/minimum_capabilities.test-cmx b/build/config/fuchsia/test/minimum_capabilities.test-cmx
index 364c08a..a79b21b6 100644
--- a/build/config/fuchsia/test/minimum_capabilities.test-cmx
+++ b/build/config/fuchsia/test/minimum_capabilities.test-cmx
@@ -11,9 +11,6 @@
}
},
"sandbox": {
- "dev": [
- "zero"
- ],
"features": [
"isolated-persistent-storage",
"isolated-temp",
diff --git a/ipc/ipc_send_fds_test.cc b/ipc/ipc_send_fds_test.cc
index a81ebaf..0523d9b 100644
--- a/ipc/ipc_send_fds_test.cc
+++ b/ipc/ipc_send_fds_test.cc
@@ -33,6 +33,9 @@
#if BUILDFLAG(IS_MAC)
#include "sandbox/mac/seatbelt.h"
+#elif BUILDFLAG(IS_FUCHSIA)
+#include "base/memory/scoped_refptr.h"
+#include "base/test/scoped_dev_zero_fuchsia.h"
#endif
namespace {
@@ -107,6 +110,12 @@
class IPCSendFdsTest : public IPCChannelMojoTestBase {
protected:
+ void SetUp() override {
+#if BUILDFLAG(IS_FUCHSIA)
+ ASSERT_TRUE(dev_zero_);
+#endif
+ }
+
void RunServer() {
// Set up IPC channel and start client.
MyChannelDescriptorListener listener(-1);
@@ -134,9 +143,20 @@
EXPECT_TRUE(WaitForClientShutdown());
DestroyChannel();
}
+
+ private:
+#if BUILDFLAG(IS_FUCHSIA)
+ scoped_refptr<base::ScopedDevZero> dev_zero_ = base::ScopedDevZero::Get();
+#endif
};
-TEST_F(IPCSendFdsTest, DescriptorTest) {
+// Disabled on Fuchsia due to failures; see https://crbug.com/1272424.
+#if BUILDFLAG(IS_FUCHSIA)
+#define MAYBE_DescriptorTest DISABLED_DescriptorTest
+#else
+#define MAYBE_DescriptorTest DescriptorTest
+#endif
+TEST_F(IPCSendFdsTest, MAYBE_DescriptorTest) {
Init("SendFdsClient");
RunServer();
}
diff --git a/ipc/ipc_test_base.cc b/ipc/ipc_test_base.cc
index f2a73c4..667cb06 100644
--- a/ipc/ipc_test_base.cc
+++ b/ipc/ipc_test_base.cc
@@ -20,8 +20,6 @@
void IPCChannelMojoTestBase::Init(const std::string& test_client_name) {
handle_ = helper_.StartChild(test_client_name);
- task_environment_ =
- std::make_unique<base::test::SingleThreadTaskEnvironment>();
}
bool IPCChannelMojoTestBase::WaitForClientShutdown() {
@@ -29,8 +27,7 @@
}
void IPCChannelMojoTestBase::TearDown() {
- if (task_environment_)
- base::RunLoop().RunUntilIdle();
+ base::RunLoop().RunUntilIdle();
}
void IPCChannelMojoTestBase::CreateChannel(IPC::Listener* listener) {
diff --git a/ipc/ipc_test_base.h b/ipc/ipc_test_base.h
index dc91a16..472e044 100644
--- a/ipc/ipc_test_base.h
+++ b/ipc/ipc_test_base.h
@@ -47,7 +47,7 @@
mojo::ScopedMessagePipeHandle TakeHandle();
private:
- std::unique_ptr<base::test::SingleThreadTaskEnvironment> task_environment_;
+ base::test::SingleThreadTaskEnvironment task_environment_;
mojo::ScopedMessagePipeHandle handle_;
mojo::core::test::MultiprocessTestHelper helper_;