blob: 818c0d31355c43377726eca680cf928a7d8b6476 [file] [log] [blame]
// Copyright 2020 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 "inspect-manager.h"
#include <fcntl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/namespace.h>
#include <lib/fpromise/single_threaded_executor.h>
#include <lib/inspect/cpp/reader.h>
#include <sys/stat.h>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
#include "src/lib/fxl/strings/substitute.h"
#include "src/lib/storage/vfs/cpp/remote_dir.h"
#include "src/storage/memfs/scoped_memfs.h"
namespace {
constexpr char kTmpfsPath[] = "/fshost-inspect-tmp";
inspect::Hierarchy ReadInspect(const inspect::Inspector& inspector) {
// take_value() will assert if the promise result is an error.
return fpromise::run_single_threaded(inspect::ReadFromInspector(inspector)).take_value();
}
class InspectManagerTest : public zxtest::Test {
public:
InspectManagerTest() : memfs_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {}
void SetUp() override {
ASSERT_EQ(memfs_loop_.StartThread(), ZX_OK);
zx::status<ScopedMemfs> memfs =
ScopedMemfs::CreateMountedAt(memfs_loop_.dispatcher(), kTmpfsPath);
ASSERT_TRUE(memfs.is_ok());
memfs_ = std::make_unique<ScopedMemfs>(std::move(*memfs));
}
void TearDown() override { memfs_.reset(); }
protected:
fidl::ClientEnd<fuchsia_io::Directory> GetDir() {
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
EXPECT_TRUE(endpoints.is_ok());
auto [client, server] = *std::move(endpoints);
EXPECT_EQ(ZX_OK, fdio_open(kTmpfsPath,
static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kRightReadable |
fuchsia_io::wire::OpenFlags::kRightExecutable),
server.TakeChannel().release()));
return std::move(client);
}
void AddFile(const std::string& path, size_t content_size) {
std::string contents(content_size, 'X');
fbl::unique_fd fd(open(fxl::Substitute("$0/$1", kTmpfsPath, path).c_str(), O_RDWR | O_CREAT,
S_IRUSR | S_IWUSR));
ASSERT_TRUE(fd.is_valid());
ASSERT_EQ(write(fd.get(), contents.c_str(), content_size), content_size);
}
void MakeDir(const std::string& path) {
ASSERT_EQ(0, mkdir(fxl::Substitute("$0/$1", kTmpfsPath, path).c_str(), 0666));
}
void AssertValue(const inspect::Hierarchy& hierarchy, const std::vector<std::string>& path,
size_t expected, size_t other_children = 0) {
auto file_node = hierarchy.GetByPath(path);
EXPECT_NOT_NULL(file_node);
ASSERT_EQ(1, file_node->node().properties().size());
ASSERT_EQ(other_children, file_node->children().size());
auto& size_property = file_node->node().properties()[0];
EXPECT_EQ("size", size_property.name());
EXPECT_EQ(expected, size_property.Get<inspect::UintPropertyValue>().value());
}
async::Loop memfs_loop_;
std::unique_ptr<ScopedMemfs> memfs_;
};
TEST_F(InspectManagerTest, ServeStats) {
// Initialize test directory
MakeDir("a");
MakeDir("a/b");
MakeDir("a/c");
AddFile("top.txt", 12);
AddFile("a/a.txt", 13);
AddFile("a/b/b.txt", 14);
AddFile("a/c/c.txt", 15);
AddFile("a/c/d.txt", 16);
// Serve inspect stats.
auto inspect_manager = fshost::FshostInspectManager();
auto test_dir = GetDir();
inspect_manager.ServeStats("test_dir", std::move(test_dir));
// Read inspect
inspect::Hierarchy hierarchy = ReadInspect(inspect_manager.inspector());
// Assert root
ASSERT_EQ(1, hierarchy.children().size());
ASSERT_EQ(0, hierarchy.node().properties().size());
// Assert all size values.
AssertValue(hierarchy, {"test_dir_stats", "test_dir"}, 70, 2);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "top.txt"}, 12);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "a"}, 58, 3);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "a", "a.txt"}, 13);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "a", "b"}, 14, 1);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "a", "b", "b.txt"}, 14);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "a", "c"}, 31, 2);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "a", "c", "c.txt"}, 15);
AssertValue(hierarchy, {"test_dir_stats", "test_dir", "a", "c", "d.txt"}, 16);
}
// Validate that using a bad handle to serve a stats node doesn't block indefinitely.
TEST_F(InspectManagerTest, ServeStatsBadHandle) {
// Serve inspect stats using an invalid channel.
auto inspect_manager = fshost::FshostInspectManager();
fidl::ClientEnd<fuchsia_io::Directory> client_end;
ASSERT_FALSE(client_end.is_valid());
inspect_manager.ServeStats("test_dir", std::move(client_end));
inspect::Hierarchy hierarchy = ReadInspect(inspect_manager.inspector());
// Ensure the node doesn't actually exist since the callback should return an error.
ASSERT_EQ(hierarchy.GetByPath({"test_dir_stats"}), nullptr);
}
TEST_F(InspectManagerTest, DirectoryEntryIteratorGetNext) {
MakeDir("iterator-test");
for (int64_t i = 0; i < 5000; i++) {
if (i % 2 == 0) {
MakeDir(fxl::Substitute("/iterator-test/dir$0", std::to_string(i)));
} else {
AddFile(fxl::Substitute("/iterator-test/file$0", std::to_string(i)), 10);
}
}
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_TRUE(endpoints.is_ok());
auto [root, server] = *std::move(endpoints);
ASSERT_EQ(ZX_OK, fdio_open(kTmpfsPath,
static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kRightReadable |
fuchsia_io::wire::OpenFlags::kRightExecutable),
server.TakeChannel().release()));
fidl::ClientEnd<fuchsia_io::Node> test_dir_chan;
auto status = fshost::OpenNode(root, "/iterator-test", S_IFDIR, &test_dir_chan);
ASSERT_EQ(status, ZX_OK);
// The opened node must be a directory because of the |MakeDir| call above.
fidl::ClientEnd<fuchsia_io::Directory> test_dir(test_dir_chan.TakeChannel());
auto iterator = fshost::DirectoryEntriesIterator(std::move(test_dir));
int64_t found = 0;
while (auto entry = iterator.GetNext()) {
if (entry->name.find("dir") == 0) {
EXPECT_EQ(entry->size, 0);
EXPECT_TRUE(entry->is_dir);
} else {
EXPECT_EQ(entry->name.find("file"), 0);
EXPECT_EQ(entry->size, 10);
EXPECT_FALSE(entry->is_dir);
}
EXPECT_TRUE(entry->node.is_valid());
found += 1;
}
EXPECT_EQ(found, 5000);
}
TEST_F(InspectManagerTest, CorruptionEvents) {
fshost::FshostInspectManager inspect_manager;
// There should be no "corruption_events" node until an event is reported.
inspect::Hierarchy hierarchy = ReadInspect(inspect_manager.inspector());
ASSERT_EQ(hierarchy.GetByPath({"corruption_events"}), nullptr);
// Report some corruption events and make sure they show up where we expect.
inspect_manager.LogCorruption(fs_management::DiskFormat::kDiskFormatMinfs);
inspect_manager.LogCorruption(fs_management::DiskFormat::kDiskFormatFxfs);
inspect_manager.LogCorruption(fs_management::DiskFormat::kDiskFormatFxfs);
inspect_manager.LogCorruption(fs_management::DiskFormat::kDiskFormatFxfs);
hierarchy = ReadInspect(inspect_manager.inspector());
const inspect::Hierarchy* corruption_events = hierarchy.GetByPath({"corruption_events"});
ASSERT_NE(corruption_events, nullptr);
const auto* minfs_corruption_events =
corruption_events->node().get_property<inspect::UintPropertyValue>("minfs");
ASSERT_NE(minfs_corruption_events, nullptr);
ASSERT_EQ(minfs_corruption_events->value(), 1u);
const auto* fxfs_corruption_events =
corruption_events->node().get_property<inspect::UintPropertyValue>("fxfs");
ASSERT_NE(fxfs_corruption_events, nullptr);
ASSERT_EQ(fxfs_corruption_events->value(), 3u);
}
} // namespace