blob: a65bd4266b034db28e1b0ea4a4fa4093afeb2bdb [file] [log] [blame]
// 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 <assert.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <zircon/device/vfs.h>
#include <zircon/compiler.h>
#include <zircon/syscalls.h>
#include "filesystems.h"
#include "misc.h"
typedef struct {
// Buffer containing cached messages
uint8_t buf[VFS_WATCH_MSG_MAX];
// Offset into 'buf' of next message
uint8_t* ptr;
// Maximum size of buffer
size_t size;
} watch_buffer_t;
// Try to read from the channel when it should be empty.
bool check_for_empty(watch_buffer_t* wb, zx_handle_t h) {
char name[NAME_MAX + 1];
ASSERT_NULL(wb->ptr);
ASSERT_EQ(zx_channel_read(h, 0, &name, nullptr, sizeof(name), 0, nullptr, nullptr),
ZX_ERR_SHOULD_WAIT);
return true;
}
bool check_local_event(watch_buffer_t* wb, const char* expected, uint8_t event) {
size_t expected_len = strlen(expected);
if (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;
}
return true;
}
return false;
}
// Try to read the 'expected' name off the channel.
bool check_for_event(watch_buffer_t* wb, zx_handle_t h, const char* expected, uint8_t event) {
if (wb->ptr != nullptr) {
return check_local_event(wb, expected, event);
}
zx_signals_t observed;
ASSERT_EQ(zx_object_wait_one(h, 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(zx_channel_read(h, 0, wb->buf, nullptr, sizeof(wb->buf), 0,
&actual, nullptr), ZX_OK);
wb->size = actual;
wb->ptr = wb->buf;
return check_local_event(wb, expected, event);
}
bool test_watcher_add(void) {
BEGIN_TEST;
if (!test_info->supports_watchers) {
return true;
}
ASSERT_EQ(mkdir("::dir", 0666), 0);
DIR* dir = opendir("::dir");
ASSERT_NONNULL(dir);
zx_handle_t h;
vfs_watch_dir_t request;
ASSERT_EQ(zx_channel_create(0, &h, &request.channel), ZX_OK);
request.mask = VFS_WATCH_MASK_ADDED;
request.options = 0;
ASSERT_EQ(ioctl_vfs_watch_dir(dirfd(dir), &request), ZX_OK);
watch_buffer_t wb;
memset(&wb, 0, sizeof(wb));
// The channel should be empty
ASSERT_TRUE(check_for_empty(&wb, h));
// Creating a file in the directory should trigger the watcher
int fd = open("::dir/foo", O_RDWR | O_CREAT);
ASSERT_GT(fd, 0);
ASSERT_EQ(close(fd), 0);
ASSERT_TRUE(check_for_event(&wb, h, "foo", VFS_WATCH_EVT_ADDED));
// Renaming into directory should trigger the watcher
ASSERT_EQ(rename("::dir/foo", "::dir/bar"), 0);
ASSERT_TRUE(check_for_event(&wb, h, "bar", VFS_WATCH_EVT_ADDED));
// Linking into directory should trigger the watcher
ASSERT_EQ(link("::dir/bar", "::dir/blat"), 0);
ASSERT_TRUE(check_for_event(&wb, h, "blat", VFS_WATCH_EVT_ADDED));
// Clean up
ASSERT_EQ(unlink("::dir/bar"), 0);
ASSERT_EQ(unlink("::dir/blat"), 0);
// There shouldn't be anything else sitting around on the channel
ASSERT_TRUE(check_for_empty(&wb, h));
ASSERT_EQ(zx_handle_close(h), 0);
ASSERT_EQ(closedir(dir), 0);
ASSERT_EQ(rmdir("::dir"), 0);
END_TEST;
}
bool test_watcher_existing(void) {
BEGIN_TEST;
if (!test_info->supports_watchers) {
return true;
}
ASSERT_EQ(mkdir("::dir", 0666), 0);
DIR* dir = opendir("::dir");
ASSERT_NONNULL(dir);
// Create a couple files in the directory
int fd = open("::dir/foo", O_RDWR | O_CREAT);
ASSERT_GT(fd, 0);
ASSERT_EQ(close(fd), 0);
fd = open("::dir/bar", O_RDWR | O_CREAT);
ASSERT_GT(fd, 0);
ASSERT_EQ(close(fd), 0);
// These files should be visible to the watcher through the "EXISTING"
// mechanism.
zx_handle_t h;
vfs_watch_dir_t request;
ASSERT_EQ(zx_channel_create(0, &h, &request.channel), ZX_OK);
request.mask = VFS_WATCH_MASK_ADDED | VFS_WATCH_MASK_EXISTING | VFS_WATCH_MASK_IDLE;
request.options = 0;
ASSERT_EQ(ioctl_vfs_watch_dir(dirfd(dir), &request), ZX_OK);
watch_buffer_t wb;
memset(&wb, 0, sizeof(wb));
// The channel should see the contents of the directory
ASSERT_TRUE(check_for_event(&wb, h, ".", VFS_WATCH_EVT_EXISTING));
ASSERT_TRUE(check_for_event(&wb, h, "foo", VFS_WATCH_EVT_EXISTING));
ASSERT_TRUE(check_for_event(&wb, h, "bar", VFS_WATCH_EVT_EXISTING));
ASSERT_TRUE(check_for_event(&wb, h, "", VFS_WATCH_EVT_IDLE));
ASSERT_TRUE(check_for_empty(&wb, h));
// Now, if we choose to add additional files, they'll show up separately
// with an "ADD" event.
fd = open("::dir/baz", O_RDWR | O_CREAT);
ASSERT_GT(fd, 0);
ASSERT_EQ(close(fd), 0);
ASSERT_TRUE(check_for_event(&wb, h, "baz", VFS_WATCH_EVT_ADDED));
ASSERT_TRUE(check_for_empty(&wb, h));
// 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_handle_t h2;
ASSERT_EQ(zx_channel_create(0, &h2, &request.channel), ZX_OK);
ASSERT_EQ(ioctl_vfs_watch_dir(dirfd(dir), &request), ZX_OK);
watch_buffer_t wb2;
memset(&wb2, 0, sizeof(wb2));
ASSERT_TRUE(check_for_event(&wb2, h2, ".", VFS_WATCH_EVT_EXISTING));
ASSERT_TRUE(check_for_event(&wb2, h2, "foo", VFS_WATCH_EVT_EXISTING));
ASSERT_TRUE(check_for_event(&wb2, h2, "bar", VFS_WATCH_EVT_EXISTING));
ASSERT_TRUE(check_for_event(&wb2, h2, "baz", VFS_WATCH_EVT_EXISTING));
ASSERT_TRUE(check_for_event(&wb2, h2, "", VFS_WATCH_EVT_IDLE));
ASSERT_TRUE(check_for_empty(&wb2, h2));
ASSERT_TRUE(check_for_empty(&wb, h));
// Clean up
ASSERT_EQ(unlink("::dir/foo"), 0);
ASSERT_EQ(unlink("::dir/bar"), 0);
ASSERT_EQ(unlink("::dir/baz"), 0);
// There shouldn't be anything else sitting around on either channel
ASSERT_TRUE(check_for_empty(&wb, h));
ASSERT_EQ(zx_handle_close(h), 0);
ASSERT_TRUE(check_for_empty(&wb2, h2));
ASSERT_EQ(zx_handle_close(h2), 0);
ASSERT_EQ(closedir(dir), 0);
ASSERT_EQ(rmdir("::dir"), 0);
END_TEST;
}
bool test_watcher_removed(void) {
BEGIN_TEST;
if (!test_info->supports_watchers) {
return true;
}
ASSERT_EQ(mkdir("::dir", 0666), 0);
DIR* dir = opendir("::dir");
ASSERT_NONNULL(dir);
zx_handle_t h;
vfs_watch_dir_t request;
ASSERT_EQ(zx_channel_create(0, &h, &request.channel), ZX_OK);
request.mask = VFS_WATCH_MASK_ADDED | VFS_WATCH_MASK_REMOVED;
request.options = 0;
watch_buffer_t wb;
memset(&wb, 0, sizeof(wb));
ASSERT_EQ(ioctl_vfs_watch_dir(dirfd(dir), &request), ZX_OK);
ASSERT_TRUE(check_for_empty(&wb, h));
int fd = openat(dirfd(dir), "foo", O_CREAT | O_RDWR | O_EXCL);
ASSERT_GT(fd, 0);
ASSERT_EQ(close(fd), 0);
ASSERT_TRUE(check_for_event(&wb, h, "foo", VFS_WATCH_EVT_ADDED));
ASSERT_TRUE(check_for_empty(&wb, h));
ASSERT_EQ(rename("::dir/foo", "::dir/bar"), 0);
ASSERT_TRUE(check_for_event(&wb, h, "foo", VFS_WATCH_EVT_REMOVED));
ASSERT_TRUE(check_for_event(&wb, h, "bar", VFS_WATCH_EVT_ADDED));
ASSERT_TRUE(check_for_empty(&wb, h));
ASSERT_EQ(unlink("::dir/bar"), 0);
ASSERT_TRUE(check_for_event(&wb, h, "bar", VFS_WATCH_EVT_REMOVED));
ASSERT_TRUE(check_for_empty(&wb, h));
zx_handle_close(h);
ASSERT_EQ(closedir(dir), 0);
ASSERT_EQ(rmdir("::dir"), 0);
END_TEST;
}
RUN_FOR_ALL_FILESYSTEMS(directory_watcher_tests,
RUN_TEST_MEDIUM(test_watcher_add)
RUN_TEST_MEDIUM(test_watcher_existing)
RUN_TEST_MEDIUM(test_watcher_removed)
)