blob: d94efefcecf3933f1bcd1096c903b0cf2b8291d3 [file] [log] [blame] [edit]
// Copyright 2017 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 <dirent.h>
#include <fcntl.h>
#include <fuchsia/io/llcpp/fidl.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/zx/channel.h>
#include <lib/zx/time.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include <fbl/unique_fd.h>
#include "src/storage/fs_test/fs_test_fixture.h"
namespace fs_test {
namespace {
namespace fio = ::llcpp::fuchsia::io;
using WatcherTest = FilesystemTest;
struct WatchBuffer {
// Buffer containing cached messages
uint8_t buf[fio::MAX_BUF];
// Offset into 'buf' of next message
uint8_t* ptr;
// Maximum size of buffer
size_t size;
};
// Try to read from the channel when it should be empty.
void CheckForEmpty(WatchBuffer* wb, const zx::channel& c) {
char name[NAME_MAX + 1];
ASSERT_EQ(wb->ptr, nullptr);
ASSERT_EQ(c.read(0, &name, nullptr, sizeof(name), 0, nullptr, nullptr), ZX_ERR_SHOULD_WAIT);
}
void CheckLocalEvent(WatchBuffer* wb, const char* expected, uint8_t event) {
size_t expected_len = strlen(expected);
ASSERT_NE(wb->ptr, nullptr);
// Used a cached event
ASSERT_EQ(wb->ptr[0], event);
ASSERT_EQ(wb->ptr[1], expected_len);
ASSERT_EQ(memcmp(wb->ptr + 2, expected, expected_len), 0);
wb->ptr = (uint8_t*)((uintptr_t)(wb->ptr) + expected_len + 2);
ASSERT_LE((uintptr_t)wb->ptr, (uintptr_t)wb->buf + wb->size);
if ((uintptr_t)wb->ptr == (uintptr_t)wb->buf + wb->size) {
wb->ptr = nullptr;
}
}
// Try to read the 'expected' name off the channel.
void CheckForEvent(WatchBuffer* wb, const zx::channel& c, const char* expected, uint8_t event) {
if (wb->ptr == nullptr) {
zx_signals_t observed;
ASSERT_EQ(c.wait_one(ZX_CHANNEL_READABLE, zx::deadline_after(zx::sec(5)), &observed), ZX_OK);
ASSERT_EQ(observed & ZX_CHANNEL_READABLE, ZX_CHANNEL_READABLE);
uint32_t actual;
ASSERT_EQ(c.read(0, wb->buf, nullptr, sizeof(wb->buf), 0, &actual, nullptr), ZX_OK);
wb->size = actual;
wb->ptr = wb->buf;
}
CheckLocalEvent(wb, expected, event);
}
TEST_P(WatcherTest, Add) {
ASSERT_EQ(mkdir(GetPath("dir").c_str(), 0666), 0);
DIR* dir = opendir(GetPath("dir").c_str());
ASSERT_NE(dir, nullptr);
zx::channel client, server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
fdio_cpp::FdioCaller caller(fbl::unique_fd(dirfd(dir)));
auto watch_result =
fio::Directory::Call::Watch(caller.channel(), fio::WATCH_MASK_ADDED, 0, std::move(server));
ASSERT_EQ(watch_result.status(), ZX_OK);
ASSERT_EQ(watch_result.Unwrap()->s, ZX_OK);
WatchBuffer wb;
memset(&wb, 0, sizeof(wb));
// The channel should be empty
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
// Creating a file in the directory should trigger the watcher
fbl::unique_fd fd(open(GetPath("dir/foo").c_str(), O_RDWR | O_CREAT));
ASSERT_TRUE(fd);
ASSERT_EQ(close(fd.release()), 0);
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "foo", fio::WATCH_EVENT_ADDED));
// Renaming into directory should trigger the watcher
ASSERT_EQ(rename(GetPath("dir/foo").c_str(), GetPath("dir/bar").c_str()), 0);
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "bar", fio::WATCH_EVENT_ADDED));
if (fs().GetTraits().supports_hard_links) {
// Linking into directory should trigger the watcher
ASSERT_EQ(link(GetPath("dir/bar").c_str(), GetPath("dir/blat").c_str()), 0);
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "blat", fio::WATCH_EVENT_ADDED));
ASSERT_EQ(unlink(GetPath("dir/blat").c_str()), 0);
}
// Clean up
ASSERT_EQ(unlink(GetPath("dir/bar").c_str()), 0) << errno;
// There shouldn't be anything else sitting around on the channel
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
// The fd is still owned by "dir".
caller.release().release();
ASSERT_EQ(closedir(dir), 0);
ASSERT_EQ(rmdir(GetPath("dir").c_str()), 0) << errno;
}
TEST_P(WatcherTest, Existing) {
ASSERT_EQ(mkdir(GetPath("dir").c_str(), 0666), 0);
DIR* dir = opendir(GetPath("dir").c_str());
ASSERT_NE(dir, nullptr);
// Create a couple files in the directory
fbl::unique_fd fd(open(GetPath("dir/foo").c_str(), O_RDWR | O_CREAT));
ASSERT_TRUE(fd);
ASSERT_EQ(close(fd.release()), 0);
fd.reset(open(GetPath("dir/bar").c_str(), O_RDWR | O_CREAT));
ASSERT_TRUE(fd);
ASSERT_EQ(close(fd.release()), 0);
// These files should be visible to the watcher through the "EXISTING"
// mechanism.
zx::channel client, server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
fdio_cpp::FdioCaller caller(fbl::unique_fd(dirfd(dir)));
uint32_t mask = fio::WATCH_MASK_ADDED | fio::WATCH_MASK_EXISTING | fio::WATCH_MASK_IDLE;
{
auto watch_result = fio::Directory::Call::Watch(caller.channel(), mask, 0, std::move(server));
ASSERT_EQ(watch_result.status(), ZX_OK);
ASSERT_EQ(watch_result.Unwrap()->s, ZX_OK);
}
WatchBuffer wb;
memset(&wb, 0, sizeof(wb));
// The channel should see the contents of the directory
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, ".", fio::WATCH_EVENT_EXISTING));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "foo", fio::WATCH_EVENT_EXISTING));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "bar", fio::WATCH_EVENT_EXISTING));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "", fio::WATCH_EVENT_IDLE));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
// Now, if we choose to add additional files, they'll show up separately
// with an "ADD" event.
fd.reset(open(GetPath("dir/baz").c_str(), O_RDWR | O_CREAT));
ASSERT_TRUE(fd);
ASSERT_EQ(close(fd.release()), 0);
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "baz", fio::WATCH_EVENT_ADDED));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
// If we create a secondary watcher with the "EXISTING" request, we'll
// see all files in the directory, but the first watcher won't see anything.
zx::channel client2;
ASSERT_EQ(zx::channel::create(0, &client2, &server), ZX_OK);
{
auto watch_result = fio::Directory::Call::Watch(caller.channel(), mask, 0, std::move(server));
ASSERT_EQ(watch_result.status(), ZX_OK);
ASSERT_EQ(watch_result.Unwrap()->s, ZX_OK);
}
WatchBuffer wb2;
memset(&wb2, 0, sizeof(wb2));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb2, client2, ".", fio::WATCH_EVENT_EXISTING));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb2, client2, "foo", fio::WATCH_EVENT_EXISTING));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb2, client2, "bar", fio::WATCH_EVENT_EXISTING));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb2, client2, "baz", fio::WATCH_EVENT_EXISTING));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb2, client2, "", fio::WATCH_EVENT_IDLE));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb2, client2));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
// Clean up
ASSERT_EQ(unlink(GetPath("dir/foo").c_str()), 0);
ASSERT_EQ(unlink(GetPath("dir/bar").c_str()), 0);
ASSERT_EQ(unlink(GetPath("dir/baz").c_str()), 0);
// There shouldn't be anything else sitting around on either channel
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb2, client2));
// The fd is still owned by "dir".
caller.release().release();
ASSERT_EQ(closedir(dir), 0);
ASSERT_EQ(rmdir(GetPath("dir").c_str()), 0);
}
TEST_P(WatcherTest, Removed) {
ASSERT_EQ(mkdir(GetPath("dir").c_str(), 0666), 0);
DIR* dir = opendir(GetPath("dir").c_str());
ASSERT_NE(dir, nullptr);
zx::channel client, server;
ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
uint32_t mask = fio::WATCH_MASK_ADDED | fio::WATCH_MASK_REMOVED;
fdio_cpp::FdioCaller caller(fbl::unique_fd(dirfd(dir)));
auto watch_result = fio::Directory::Call::Watch(caller.channel(), mask, 0, std::move(server));
ASSERT_EQ(watch_result.status(), ZX_OK);
ASSERT_EQ(watch_result.Unwrap()->s, ZX_OK);
WatchBuffer wb;
memset(&wb, 0, sizeof(wb));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
fbl::unique_fd fd(openat(dirfd(dir), "foo", O_CREAT | O_RDWR | O_EXCL));
ASSERT_TRUE(fd);
ASSERT_EQ(close(fd.release()), 0);
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "foo", fio::WATCH_EVENT_ADDED));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
ASSERT_EQ(rename(GetPath("dir/foo").c_str(), GetPath("dir/bar").c_str()), 0);
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "foo", fio::WATCH_EVENT_REMOVED));
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "bar", fio::WATCH_EVENT_ADDED));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
ASSERT_EQ(unlink(GetPath("dir/bar").c_str()), 0);
ASSERT_NO_FATAL_FAILURE(CheckForEvent(&wb, client, "bar", fio::WATCH_EVENT_REMOVED));
ASSERT_NO_FATAL_FAILURE(CheckForEmpty(&wb, client));
// The fd is still owned by "dir".
caller.release().release();
ASSERT_EQ(closedir(dir), 0);
ASSERT_EQ(rmdir(GetPath("dir").c_str()), 0) << errno;
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, WatcherTest, testing::ValuesIn(AllTestFilesystems()),
testing::PrintToStringParamName());
} // namespace
} // namespace fs_test