blob: 6d5bff733874af4e01974e93994690eb1c394547 [file] [log] [blame]
// 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