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
29 typedef 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.
check_for_empty(watch_buffer_t * wb,const zx::channel & c)39 bool 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
check_local_event(watch_buffer_t * wb,const char * expected,uint8_t event)46 bool 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.
check_for_event(watch_buffer_t * wb,const zx::channel & c,const char * expected,uint8_t event)64 bool 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
TestWatcherAdd(void)80 bool TestWatcherAdd(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
TestWatcherExisting(void)133 bool TestWatcherExisting(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
TestWatcherRemoved(void)214 bool TestWatcherRemoved(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
264 RUN_FOR_ALL_FILESYSTEMS(directory_watcher_tests,
265 RUN_TEST_MEDIUM(TestWatcherAdd)
266 RUN_TEST_MEDIUM(TestWatcherExisting)
267 RUN_TEST_MEDIUM(TestWatcherRemoved)
268 )
269