[go: nahoru, domu]

[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_;