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