1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <assert.h> 6#include <errno.h> 7#include <dirent.h> 8#include <fcntl.h> 9#include <limits.h> 10#include <stdbool.h> 11#include <stdint.h> 12#include <stdio.h> 13#include <stdlib.h> 14#include <string.h> 15#include <sys/stat.h> 16#include <unistd.h> 17 18#include <fbl/unique_fd.h> 19#include <fuchsia/io/c/fidl.h> 20#include <lib/fzl/fdio.h> 21#include <lib/zx/channel.h> 22#include <zircon/device/vfs.h> 23#include <zircon/compiler.h> 24#include <zircon/syscalls.h> 25 26#include "filesystems.h" 27#include "misc.h" 28 29typedef struct { 30 // Buffer containing cached messages 31 uint8_t buf[fuchsia_io_MAX_BUF]; 32 // Offset into 'buf' of next message 33 uint8_t* ptr; 34 // Maximum size of buffer 35 size_t size; 36} watch_buffer_t; 37 38// Try to read from the channel when it should be empty. 39bool check_for_empty(watch_buffer_t* wb, const zx::channel& c) { 40 char name[NAME_MAX + 1]; 41 ASSERT_NULL(wb->ptr); 42 ASSERT_EQ(c.read(0, &name, sizeof(name), nullptr, nullptr, 0, nullptr), ZX_ERR_SHOULD_WAIT); 43 return true; 44} 45 46bool check_local_event(watch_buffer_t* wb, const char* expected, uint8_t event) { 47 size_t expected_len = strlen(expected); 48 if (wb->ptr != nullptr) { 49 // Used a cached event 50 ASSERT_EQ(wb->ptr[0], event); 51 ASSERT_EQ(wb->ptr[1], expected_len); 52 ASSERT_EQ(memcmp(wb->ptr + 2, expected, expected_len), 0); 53 wb->ptr = (uint8_t*)((uintptr_t)(wb->ptr) + expected_len + 2); 54 ASSERT_LE((uintptr_t)wb->ptr, (uintptr_t) wb->buf + wb->size); 55 if ((uintptr_t) wb->ptr == (uintptr_t) wb->buf + wb->size) { 56 wb->ptr = nullptr; 57 } 58 return true; 59 } 60 return false; 61} 62 63// Try to read the 'expected' name off the channel. 64bool check_for_event(watch_buffer_t* wb, const zx::channel& c, const char* expected, 65 uint8_t event) { 66 if (wb->ptr != nullptr) { 67 return check_local_event(wb, expected, event); 68 } 69 70 zx_signals_t observed; 71 ASSERT_EQ(c.wait_one(ZX_CHANNEL_READABLE, zx::deadline_after(zx::sec(5)), &observed), ZX_OK); 72 ASSERT_EQ(observed & ZX_CHANNEL_READABLE, ZX_CHANNEL_READABLE); 73 uint32_t actual; 74 ASSERT_EQ(c.read(0, wb->buf, sizeof(wb->buf), &actual, nullptr, 0, nullptr), ZX_OK); 75 wb->size = actual; 76 wb->ptr = wb->buf; 77 return check_local_event(wb, expected, event); 78} 79 80bool test_watcher_add(void) { 81 BEGIN_TEST; 82 83 if (!test_info->supports_watchers) { 84 return true; 85 } 86 87 ASSERT_EQ(mkdir("::dir", 0666), 0); 88 DIR* dir = opendir("::dir"); 89 ASSERT_NONNULL(dir); 90 zx::channel client, server; 91 ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK); 92 fzl::FdioCaller caller(fbl::unique_fd(dirfd(dir))); 93 zx_status_t status; 94 ASSERT_EQ(fuchsia_io_DirectoryWatch(caller.borrow_channel(), fuchsia_io_WATCH_MASK_ADDED, 0, 95 server.release(), &status), ZX_OK); 96 ASSERT_EQ(status, ZX_OK); 97 98 watch_buffer_t wb; 99 memset(&wb, 0, sizeof(wb)); 100 101 // The channel should be empty 102 ASSERT_TRUE(check_for_empty(&wb, client)); 103 104 // Creating a file in the directory should trigger the watcher 105 int fd = open("::dir/foo", O_RDWR | O_CREAT); 106 ASSERT_GT(fd, 0); 107 ASSERT_EQ(close(fd), 0); 108 ASSERT_TRUE(check_for_event(&wb, client, "foo", fuchsia_io_WATCH_EVENT_ADDED)); 109 110 // Renaming into directory should trigger the watcher 111 ASSERT_EQ(rename("::dir/foo", "::dir/bar"), 0); 112 ASSERT_TRUE(check_for_event(&wb, client, "bar", fuchsia_io_WATCH_EVENT_ADDED)); 113 114 // Linking into directory should trigger the watcher 115 ASSERT_EQ(link("::dir/bar", "::dir/blat"), 0); 116 ASSERT_TRUE(check_for_event(&wb, client, "blat", fuchsia_io_WATCH_EVENT_ADDED)); 117 118 // Clean up 119 ASSERT_EQ(unlink("::dir/bar"), 0); 120 ASSERT_EQ(unlink("::dir/blat"), 0); 121 122 // There shouldn't be anything else sitting around on the channel 123 ASSERT_TRUE(check_for_empty(&wb, client)); 124 125 // The fd is still owned by "dir". 126 caller.release().release(); 127 ASSERT_EQ(closedir(dir), 0); 128 ASSERT_EQ(rmdir("::dir"), 0); 129 130 END_TEST; 131} 132 133bool test_watcher_existing(void) { 134 BEGIN_TEST; 135 136 if (!test_info->supports_watchers) { 137 return true; 138 } 139 140 ASSERT_EQ(mkdir("::dir", 0666), 0); 141 DIR* dir = opendir("::dir"); 142 ASSERT_NONNULL(dir); 143 144 // Create a couple files in the directory 145 int fd = open("::dir/foo", O_RDWR | O_CREAT); 146 ASSERT_GT(fd, 0); 147 ASSERT_EQ(close(fd), 0); 148 fd = open("::dir/bar", O_RDWR | O_CREAT); 149 ASSERT_GT(fd, 0); 150 ASSERT_EQ(close(fd), 0); 151 152 // These files should be visible to the watcher through the "EXISTING" 153 // mechanism. 154 zx::channel client, server; 155 ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK); 156 fzl::FdioCaller caller(fbl::unique_fd(dirfd(dir))); 157 zx_status_t status; 158 uint32_t mask = fuchsia_io_WATCH_MASK_ADDED | fuchsia_io_WATCH_MASK_EXISTING | fuchsia_io_WATCH_MASK_IDLE; 159 ASSERT_EQ(fuchsia_io_DirectoryWatch(caller.borrow_channel(), mask, 0, 160 server.release(), &status), ZX_OK); 161 ASSERT_EQ(status, ZX_OK); 162 watch_buffer_t wb; 163 memset(&wb, 0, sizeof(wb)); 164 165 // The channel should see the contents of the directory 166 ASSERT_TRUE(check_for_event(&wb, client, ".", fuchsia_io_WATCH_EVENT_EXISTING)); 167 ASSERT_TRUE(check_for_event(&wb, client, "foo", fuchsia_io_WATCH_EVENT_EXISTING)); 168 ASSERT_TRUE(check_for_event(&wb, client, "bar", fuchsia_io_WATCH_EVENT_EXISTING)); 169 ASSERT_TRUE(check_for_event(&wb, client, "", fuchsia_io_WATCH_EVENT_IDLE)); 170 ASSERT_TRUE(check_for_empty(&wb, client)); 171 172 // Now, if we choose to add additional files, they'll show up separately 173 // with an "ADD" event. 174 fd = open("::dir/baz", O_RDWR | O_CREAT); 175 ASSERT_GT(fd, 0); 176 ASSERT_EQ(close(fd), 0); 177 ASSERT_TRUE(check_for_event(&wb, client, "baz", fuchsia_io_WATCH_EVENT_ADDED)); 178 ASSERT_TRUE(check_for_empty(&wb, client)); 179 180 // If we create a secondary watcher with the "EXISTING" request, we'll 181 // see all files in the directory, but the first watcher won't see anything. 182 zx::channel client2; 183 ASSERT_EQ(zx::channel::create(0, &client2, &server), ZX_OK); 184 ASSERT_EQ(fuchsia_io_DirectoryWatch(caller.borrow_channel(), mask, 0, 185 server.release(), &status), ZX_OK); 186 ASSERT_EQ(status, ZX_OK); 187 watch_buffer_t wb2; 188 memset(&wb2, 0, sizeof(wb2)); 189 ASSERT_TRUE(check_for_event(&wb2, client2, ".", fuchsia_io_WATCH_EVENT_EXISTING)); 190 ASSERT_TRUE(check_for_event(&wb2, client2, "foo", fuchsia_io_WATCH_EVENT_EXISTING)); 191 ASSERT_TRUE(check_for_event(&wb2, client2, "bar", fuchsia_io_WATCH_EVENT_EXISTING)); 192 ASSERT_TRUE(check_for_event(&wb2, client2, "baz", fuchsia_io_WATCH_EVENT_EXISTING)); 193 ASSERT_TRUE(check_for_event(&wb2, client2, "", fuchsia_io_WATCH_EVENT_IDLE)); 194 ASSERT_TRUE(check_for_empty(&wb2, client2)); 195 ASSERT_TRUE(check_for_empty(&wb, client)); 196 197 // Clean up 198 ASSERT_EQ(unlink("::dir/foo"), 0); 199 ASSERT_EQ(unlink("::dir/bar"), 0); 200 ASSERT_EQ(unlink("::dir/baz"), 0); 201 202 // There shouldn't be anything else sitting around on either channel 203 ASSERT_TRUE(check_for_empty(&wb, client)); 204 ASSERT_TRUE(check_for_empty(&wb2, client2)); 205 206 // The fd is still owned by "dir". 207 caller.release().release(); 208 ASSERT_EQ(closedir(dir), 0); 209 ASSERT_EQ(rmdir("::dir"), 0); 210 211 END_TEST; 212} 213 214bool test_watcher_removed(void) { 215 BEGIN_TEST; 216 217 if (!test_info->supports_watchers) { 218 return true; 219 } 220 221 ASSERT_EQ(mkdir("::dir", 0666), 0); 222 DIR* dir = opendir("::dir"); 223 ASSERT_NONNULL(dir); 224 225 zx::channel client, server; 226 ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK); 227 uint32_t mask = fuchsia_io_WATCH_MASK_ADDED | fuchsia_io_WATCH_MASK_REMOVED; 228 zx_status_t status; 229 fzl::FdioCaller caller(fbl::unique_fd(dirfd(dir))); 230 ASSERT_EQ(fuchsia_io_DirectoryWatch(caller.borrow_channel(), mask, 0, 231 server.release(), &status), ZX_OK); 232 ASSERT_EQ(status, ZX_OK); 233 234 watch_buffer_t wb; 235 memset(&wb, 0, sizeof(wb)); 236 237 ASSERT_TRUE(check_for_empty(&wb, client)); 238 239 int fd = openat(dirfd(dir), "foo", O_CREAT | O_RDWR | O_EXCL); 240 ASSERT_GT(fd, 0); 241 ASSERT_EQ(close(fd), 0); 242 243 ASSERT_TRUE(check_for_event(&wb, client, "foo", fuchsia_io_WATCH_EVENT_ADDED)); 244 ASSERT_TRUE(check_for_empty(&wb, client)); 245 246 ASSERT_EQ(rename("::dir/foo", "::dir/bar"), 0); 247 248 ASSERT_TRUE(check_for_event(&wb, client, "foo", fuchsia_io_WATCH_EVENT_REMOVED)); 249 ASSERT_TRUE(check_for_event(&wb, client, "bar", fuchsia_io_WATCH_EVENT_ADDED)); 250 ASSERT_TRUE(check_for_empty(&wb, client)); 251 252 ASSERT_EQ(unlink("::dir/bar"), 0); 253 ASSERT_TRUE(check_for_event(&wb, client, "bar", fuchsia_io_WATCH_EVENT_REMOVED)); 254 ASSERT_TRUE(check_for_empty(&wb, client)); 255 256 // The fd is still owned by "dir". 257 caller.release().release(); 258 ASSERT_EQ(closedir(dir), 0); 259 ASSERT_EQ(rmdir("::dir"), 0); 260 261 END_TEST; 262} 263 264RUN_FOR_ALL_FILESYSTEMS(directory_watcher_tests, 265 RUN_TEST_MEDIUM(test_watcher_add) 266 RUN_TEST_MEDIUM(test_watcher_existing) 267 RUN_TEST_MEDIUM(test_watcher_removed) 268) 269