| // Copyright 2019 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.fshost/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <fidl/fuchsia.io/cpp/wire_test_base.h> |
| #include <fidl/fuchsia.process.lifecycle/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fidl-async/bind.h> |
| #include <lib/fidl/llcpp/server.h> |
| #include <lib/zx/channel.h> |
| #include <zircon/errors.h> |
| #include <zircon/fidl.h> |
| |
| #include <memory> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/ref_ptr.h> |
| #include <gtest/gtest.h> |
| |
| #include "config.h" |
| #include "fs-manager.h" |
| #include "src/lib/storage/vfs/cpp/pseudo_dir.h" |
| #include "src/lib/storage/vfs/cpp/synchronous_vfs.h" |
| #include "src/storage/fshost/block-watcher.h" |
| |
| namespace fshost { |
| namespace { |
| |
| namespace fio = fuchsia_io; |
| |
| // Test that the manager Shutdown fails if ReadyForShutdown is not called. |
| TEST(FsManagerTestCase, ShutdownBeforeReadyFails) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| FsManager manager(nullptr); |
| auto config = EmptyConfig(); |
| BlockWatcher watcher(manager, &config); |
| ASSERT_EQ(manager.Initialize({}, {}, config, watcher), ZX_OK); |
| |
| sync_completion_t callback_called; |
| manager.Shutdown([callback_called = &callback_called](zx_status_t status) { |
| sync_completion_signal(callback_called); |
| }); |
| EXPECT_FALSE(sync_completion_signaled(&callback_called)); |
| manager.ReadyForShutdown(); |
| sync_completion_wait(&callback_called, ZX_TIME_INFINITE); |
| } |
| |
| // Test that the manager performs the shutdown procedure correctly with respect to externally |
| // observable behaviors. |
| TEST(FsManagerTestCase, ShutdownSignalsCompletion) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| FsManager manager(nullptr); |
| auto config = EmptyConfig(); |
| BlockWatcher watcher(manager, &config); |
| ASSERT_EQ(manager.Initialize({}, {}, config, watcher), ZX_OK); |
| |
| manager.ReadyForShutdown(); |
| // The manager should not have exited yet: No one has asked for the shutdown. |
| EXPECT_FALSE(manager.IsShutdown()); |
| |
| // Once we trigger shutdown, we expect a shutdown signal. |
| sync_completion_t callback_called; |
| manager.Shutdown([callback_called = &callback_called](zx_status_t status) { |
| EXPECT_EQ(status, ZX_OK); |
| sync_completion_signal(callback_called); |
| }); |
| manager.WaitForShutdown(); |
| EXPECT_EQ(sync_completion_wait(&callback_called, ZX_TIME_INFINITE), ZX_OK); |
| |
| // It's an error if shutdown gets called twice, but we expect the callback to still get called |
| // with the appropriate error message since the shutdown function has no return value. |
| sync_completion_reset(&callback_called); |
| manager.Shutdown([callback_called = &callback_called](zx_status_t status) { |
| EXPECT_EQ(status, ZX_ERR_INTERNAL); |
| sync_completion_signal(callback_called); |
| }); |
| EXPECT_EQ(sync_completion_wait(&callback_called, ZX_TIME_INFINITE), ZX_OK); |
| } |
| |
| // Test that the manager shuts down the filesystems given a call on the lifecycle channel |
| TEST(FsManagerTestCase, LifecycleStop) { |
| zx::status create_lifecycle = fidl::CreateEndpoints<fuchsia_process_lifecycle::Lifecycle>(); |
| ASSERT_TRUE(create_lifecycle.is_ok()) << create_lifecycle.status_string(); |
| auto [lifecycle_client, lifecycle_server] = std::move(create_lifecycle).value(); |
| |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| FsManager manager(nullptr); |
| auto config = DefaultConfig(); |
| BlockWatcher watcher(manager, &config); |
| ASSERT_EQ(manager.Initialize({}, std::move(lifecycle_server), config, watcher), ZX_OK); |
| |
| manager.ReadyForShutdown(); |
| // The manager should not have exited yet: No one has asked for an unmount. |
| EXPECT_FALSE(manager.IsShutdown()); |
| |
| // Call stop on the lifecycle channel |
| auto result = fidl::WireCall(lifecycle_client)->Stop(); |
| ASSERT_EQ(result.status(), ZX_OK); |
| |
| // the lifecycle channel should be closed now |
| zx_signals_t pending; |
| EXPECT_EQ( |
| lifecycle_client.channel().wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite(), &pending), |
| ZX_OK); |
| EXPECT_TRUE(pending & ZX_CHANNEL_PEER_CLOSED); |
| |
| // Now we expect a shutdown signal. |
| manager.WaitForShutdown(); |
| } |
| |
| class MockDirectoryOpener : public fidl::testing::WireTestBase<fuchsia_io::Directory> { |
| public: |
| void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override { |
| ADD_FAILURE() << "Unexpected call to MockDirectoryOpener: " << name; |
| completer.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void Open(OpenRequestView request, OpenCompleter::Sync& completer) override { |
| saved_open_flags_ = request->flags; |
| saved_open_count_ += 1; |
| saved_path_ = request->path.get(); |
| } |
| |
| fio::wire::OpenFlags saved_open_flags() const { return saved_open_flags_; } |
| uint32_t saved_open_count() const { return saved_open_count_; } |
| const std::string saved_path() const { return saved_path_; } |
| |
| private: |
| // Test fields used for validation. |
| fio::wire::OpenFlags saved_open_flags_ = {}; |
| uint32_t saved_open_count_ = 0; |
| std::string saved_path_; |
| }; |
| |
| TEST(FsManagerTestCase, InstallFsAfterShutdownWillFail) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| FsManager manager(nullptr); |
| auto config = EmptyConfig(); |
| config.durable() = true; |
| BlockWatcher watcher(manager, &config); |
| ASSERT_EQ(manager.Initialize({}, {}, config, watcher), ZX_OK); |
| |
| manager.ReadyForShutdown(); |
| manager.Shutdown([](zx_status_t status) { EXPECT_EQ(status, ZX_OK); }); |
| manager.WaitForShutdown(); |
| |
| EXPECT_FALSE(manager.TakeMountPointServerEnd(FsManager::MountPoint::kDurable).has_value()); |
| } |
| |
| TEST(FsManagerTestCase, ReportFailureOnUncleanUnmount) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| FsManager manager(nullptr); |
| auto config = EmptyConfig(); |
| config.durable() = true; |
| BlockWatcher watcher(manager, &config); |
| ASSERT_EQ(manager.Initialize({}, {}, config, watcher), ZX_OK); |
| |
| std::optional endpoints_or = manager.TakeMountPointServerEnd(FsManager::MountPoint::kDurable); |
| ASSERT_TRUE(endpoints_or.has_value()); |
| auto [export_root, server_end] = std::move(endpoints_or.value()); |
| server_end.Close(ZX_ERR_INTERNAL); |
| |
| manager.ReadyForShutdown(); |
| |
| zx_status_t shutdown_status = ZX_OK; |
| manager.Shutdown([&shutdown_status](zx_status_t status) { shutdown_status = status; }); |
| manager.WaitForShutdown(); |
| |
| // We closed the server end we got back, which should cause shutdown to receive PEER_CLOSED when |
| // it tries to shut down the filesystem. |
| ASSERT_EQ(shutdown_status, ZX_ERR_PEER_CLOSED); |
| } |
| |
| } // namespace |
| } // namespace fshost |