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