blob: d880750cbac2092a3ba2803a34f84e8c33e9fbea [file] [log] [blame] [edit]
// Copyright 2021 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 <fcntl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/device-watcher/cpp/device-watcher.h>
#include <lib/fdio/fd.h>
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <memory>
#include <fbl/ref_ptr.h>
#include <fbl/unique_fd.h>
#include <zxtest/zxtest.h>
#include "src/lib/storage/vfs/cpp/pseudo_dir.h"
#include "src/lib/storage/vfs/cpp/pseudo_file.h"
#include "src/lib/storage/vfs/cpp/synchronous_vfs.h"
namespace {
TEST(DeviceWatcherTest, Smoke) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto file = fbl::MakeRefCounted<fs::UnbufferedPseudoFile>(
[](fbl::String* output) { return ZX_OK; }, [](std::string_view input) { return ZX_OK; });
auto third = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(third->AddEntry("file", file));
auto second = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(second->AddEntry("third", std::move(third)));
auto first = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(first->AddEntry("second", std::move(second)));
ASSERT_OK(first->AddEntry("file", file));
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(endpoints);
auto& [client, server] = endpoints.value();
fs::SynchronousVfs vfs(loop.dispatcher());
ASSERT_OK(vfs.ServeDirectory(first, std::move(server)));
ASSERT_OK(loop.StartThread());
fbl::unique_fd dir;
ASSERT_OK(fdio_fd_create(client.TakeChannel().release(), dir.reset_and_get_address()));
ASSERT_OK(device_watcher::RecursiveWaitForFile(dir.get(), "second/third/file"));
}
TEST(DeviceWatcherTest, OpenInNamespace) {
ASSERT_OK(device_watcher::RecursiveWaitForFile("/dev/sys/test"));
ASSERT_STATUS(device_watcher::RecursiveWaitForFile("/other-test/file"), ZX_ERR_NOT_FOUND);
}
TEST(DeviceWatcherTest, DirWatcherWaitForRemoval) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto file = fbl::MakeRefCounted<fs::UnbufferedPseudoFile>(
[](fbl::String* output) { return ZX_OK; }, [](std::string_view input) { return ZX_OK; });
auto third = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(third->AddEntry("file", file));
auto second = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(second->AddEntry("third", third));
auto first = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(first->AddEntry("second", second));
ASSERT_OK(first->AddEntry("file", file));
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(endpoints);
auto& [client, server] = endpoints.value();
fs::SynchronousVfs vfs(loop.dispatcher());
ASSERT_OK(vfs.ServeDirectory(first, std::move(server)));
ASSERT_OK(loop.StartThread());
fbl::unique_fd dir;
ASSERT_OK(fdio_fd_create(client.TakeChannel().release(), dir.reset_and_get_address()));
fbl::unique_fd sub_dir(openat(dir.get(), "second/third", O_DIRECTORY | O_RDONLY));
ASSERT_OK(device_watcher::RecursiveWaitForFile(dir.get(), "second/third/file"));
// Verify removal of the root directory file
std::unique_ptr<device_watcher::DirWatcher> root_watcher;
ASSERT_OK(device_watcher::DirWatcher::Create(dir.get(), &root_watcher));
ASSERT_OK(first->RemoveEntry("file"));
ASSERT_OK(root_watcher->WaitForRemoval("file", zx::duration::infinite()));
// Verify removal of the subdirectory file
std::unique_ptr<device_watcher::DirWatcher> sub_watcher;
ASSERT_OK(device_watcher::DirWatcher::Create(sub_dir.get(), &sub_watcher));
ASSERT_OK(third->RemoveEntry("file"));
ASSERT_OK(sub_watcher->WaitForRemoval("file", zx::duration::infinite()));
}
TEST(DeviceWatcherTest, DirWatcherVerifyUnowned) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto file = fbl::MakeRefCounted<fs::UnbufferedPseudoFile>(
[](fbl::String* output) { return ZX_OK; }, [](std::string_view input) { return ZX_OK; });
auto first = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(first->AddEntry("file", file));
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(endpoints);
auto& [client, server] = endpoints.value();
fs::SynchronousVfs vfs(loop.dispatcher());
ASSERT_OK(vfs.ServeDirectory(first, std::move(server)));
ASSERT_OK(loop.StartThread());
fbl::unique_fd dir;
ASSERT_OK(fdio_fd_create(client.TakeChannel().release(), dir.reset_and_get_address()));
std::unique_ptr<device_watcher::DirWatcher> root_watcher;
ASSERT_OK(device_watcher::DirWatcher::Create(dir.get(), &root_watcher));
// Close the directory fd
ASSERT_OK(dir.reset());
// Verify the watcher can still successfully wait for removal
ASSERT_OK(first->RemoveEntry("file"));
ASSERT_OK(root_watcher->WaitForRemoval("file", zx::duration::infinite()));
}
class IterateDirectoryTest : public zxtest::Test {
protected:
IterateDirectoryTest()
: loop_(&kAsyncLoopConfigNoAttachToCurrentThread), vfs_(loop_.dispatcher()) {}
void SetUp() override {
// Set up the fake filesystem.
auto file1 = fbl::MakeRefCounted<fs::UnbufferedPseudoFile>(
[](fbl::String* output) { return ZX_OK; }, [](std::string_view input) { return ZX_OK; });
auto file2 = fbl::MakeRefCounted<fs::UnbufferedPseudoFile>(
[](fbl::String* output) { return ZX_OK; }, [](std::string_view input) { return ZX_OK; });
auto first = fbl::MakeRefCounted<fs::PseudoDir>();
ASSERT_OK(first->AddEntry("file1", file1));
ASSERT_OK(first->AddEntry("file2", file2));
zx::result endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
ASSERT_OK(endpoints);
auto& [client, server] = endpoints.value();
ASSERT_OK(vfs_.ServeDirectory(first, std::move(server)));
ASSERT_OK(loop_.StartThread());
ASSERT_OK(fdio_fd_create(client.TakeChannel().release(), dir_.reset_and_get_address()));
}
void TearDown() override { loop_.Shutdown(); }
const fbl::unique_fd& dir() { return dir_; }
private:
async::Loop loop_;
fs::SynchronousVfs vfs_;
fbl::unique_fd dir_;
};
TEST_F(IterateDirectoryTest, IterateDirectory) {
std::vector<std::string> seen;
zx_status_t status = device_watcher::IterateDirectory(
dir().get(), [&seen](std::string_view filename, zx::channel channel) {
// Collect the file names into the vector.
seen.emplace_back(filename);
return ZX_OK;
});
ASSERT_OK(status);
// Make sure the file names seen were as expected.
ASSERT_EQ(2, seen.size());
std::sort(seen.begin(), seen.end());
ASSERT_EQ("file1", seen[0]);
ASSERT_EQ("file2", seen[1]);
}
TEST_F(IterateDirectoryTest, IterateDirectoryCancelled) {
// Test that iteration is cancelled when the callback returns an error
std::vector<std::string> seen;
zx_status_t status = device_watcher::IterateDirectory(
dir().get(), [&seen](std::string_view filename, zx::channel channel) {
seen.emplace_back(filename);
return ZX_ERR_INTERNAL;
});
ASSERT_STATUS(status, ZX_ERR_INTERNAL);
// Should only have seen a single file before exiting.
ASSERT_EQ(1, seen.size());
}
TEST_F(IterateDirectoryTest, IterateDirectoryChannel) {
// Test that we can use the channel passed to the callback function to make
// fuchsia.io.Node calls.
std::vector<uint64_t> content_sizes;
zx_status_t status = device_watcher::IterateDirectory(
dir().get(), [&content_sizes](std::string_view filename, zx::channel channel) {
auto result =
fidl::WireCall(fidl::UnownedClientEnd<fuchsia_io::Node>(channel.borrow()))->GetAttr();
if (!result.ok()) {
return result.status();
}
content_sizes.push_back(result.value().attributes.content_size);
return ZX_OK;
});
ASSERT_OK(status);
ASSERT_EQ(2, content_sizes.size());
// Files are empty.
ASSERT_EQ(0, content_sizes[0]);
ASSERT_EQ(0, content_sizes[1]);
}
} // namespace