1// Copyright 2018 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 <fs/managed-vfs.h>
6#include <fs/synchronous-vfs.h>
7#include <fs/vnode.h>
8#include <fs/vfs.h>
9#include <fuchsia/io/c/fidl.h>
10#include <lib/async/cpp/task.h>
11#include <lib/async-loop/cpp/loop.h>
12#include <lib/zx/channel.h>
13#include <lib/sync/completion.h>
14#include <zircon/assert.h>
15
16#include <unittest/unittest.h>
17
18// Used to minimize boilerplate for invoking FIDL requests
19// under highly controlled situations.
20#include <lib/fdio/../../../private-fidl.h>
21
22namespace {
23
24class FdCountVnode : public fs::Vnode {
25public:
26    FdCountVnode() : fd_count_(0) {}
27    virtual ~FdCountVnode() {
28        ZX_ASSERT(fd_count_ == 0);
29    }
30
31    int fds() const {
32        return fd_count_;
33    }
34
35    zx_status_t Open(uint32_t, fbl::RefPtr<Vnode>* redirect) final {
36        fd_count_++;
37        return ZX_OK;
38    }
39
40    zx_status_t Close() final {
41        fd_count_--;
42        ZX_ASSERT(fd_count_ >= 0);
43        return ZX_OK;
44    }
45
46private:
47    int fd_count_;
48};
49
50class AsyncTearDownVnode : public FdCountVnode {
51public:
52    AsyncTearDownVnode(sync_completion_t* completions) :
53        callback_(nullptr), completions_(completions) {}
54
55    ~AsyncTearDownVnode() {
56        // C) Tear down the Vnode.
57        ZX_ASSERT(fds() == 0);
58        sync_completion_signal(&completions_[2]);
59    }
60
61private:
62    void Sync(fs::Vnode::SyncCallback callback) final {
63        callback_ = fbl::move(callback);
64        thrd_t thrd;
65        ZX_ASSERT(thrd_create(&thrd, &AsyncTearDownVnode::SyncThread, this) == thrd_success);
66        thrd_detach(thrd);
67    }
68
69    static int SyncThread(void* arg) {
70        fs::Vnode::SyncCallback callback;
71        {
72            fbl::RefPtr<AsyncTearDownVnode> vn =
73                    fbl::WrapRefPtr(reinterpret_cast<AsyncTearDownVnode*>(arg));
74            // A) Identify when the sync has started being processed.
75            sync_completion_signal(&vn->completions_[0]);
76            // B) Wait until the connection has been closed.
77            sync_completion_wait(&vn->completions_[1], ZX_TIME_INFINITE);
78            callback = fbl::move(vn->callback_);
79        }
80        callback(ZX_OK);
81        return 0;
82    }
83
84    fs::Vnode::SyncCallback callback_;
85    sync_completion_t* completions_;
86};
87
88bool send_sync(const zx::channel& client) {
89    BEGIN_HELPER;
90    fuchsia_io_NodeSyncRequest request;
91    request.hdr.txid = 5;
92    request.hdr.ordinal = fuchsia_io_NodeSyncOrdinal;
93    ASSERT_EQ(client.write(0, &request, sizeof(request), nullptr, 0), ZX_OK);
94    END_HELPER;
95}
96
97// Helper function which creates a VFS with a served Vnode,
98// starts a sync request, and then closes the connection to the client
99// in the middle of the async callback.
100//
101// This helps tests get ready to try handling a tricky teardown.
102bool sync_start(sync_completion_t* completions, async::Loop* loop,
103                fbl::unique_ptr<fs::ManagedVfs>* vfs) {
104    BEGIN_HELPER;
105    *vfs = fbl::make_unique<fs::ManagedVfs>(loop->dispatcher());
106    ASSERT_EQ(loop->StartThread(), ZX_OK);
107
108    auto vn = fbl::AdoptRef(new AsyncTearDownVnode(completions));
109    zx::channel client;
110    zx::channel server;
111    ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
112    ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
113    ASSERT_EQ(vn->Serve(vfs->get(), fbl::move(server), 0), ZX_OK);
114    vn = nullptr;
115
116    ASSERT_TRUE(send_sync(client));
117
118    // A) Wait for sync to begin.
119    sync_completion_wait(&completions[0], ZX_TIME_INFINITE);
120
121    client.reset();
122    END_HELPER;
123}
124
125// Test a case where the VFS object is shut down outside the dispatch loop.
126bool test_unposted_teardown() {
127    BEGIN_TEST;
128
129     async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
130    sync_completion_t completions[3];
131    fbl::unique_ptr<fs::ManagedVfs> vfs;
132
133    ASSERT_TRUE(sync_start(completions, &loop, &vfs));
134
135    // B) Let sync complete.
136    sync_completion_signal(&completions[1]);
137
138    sync_completion_t* vnode_destroyed = &completions[2];
139    sync_completion_t shutdown_done;
140    vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
141        ZX_ASSERT(status == ZX_OK);
142        // C) Issue an explicit shutdown, check that the Vnode has
143        // already torn down.
144        ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
145        sync_completion_signal(&shutdown_done);
146    });
147    ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
148    vfs = nullptr;
149
150    END_TEST;
151}
152
153// Test a case where the VFS object is shut down as a posted request to the
154// dispatch loop.
155bool test_posted_teardown() {
156    BEGIN_TEST;
157
158     async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
159    sync_completion_t completions[3];
160    fbl::unique_ptr<fs::ManagedVfs> vfs;
161
162    ASSERT_TRUE(sync_start(completions, &loop, &vfs));
163
164    // B) Let sync complete.
165    sync_completion_signal(&completions[1]);
166
167    sync_completion_t* vnode_destroyed = &completions[2];
168    sync_completion_t shutdown_done;
169    ASSERT_EQ(async::PostTask(loop.dispatcher(), [&]() {
170        vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
171            ZX_ASSERT(status == ZX_OK);
172            // C) Issue an explicit shutdown, check that the Vnode has
173            // already torn down.
174            ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
175            sync_completion_signal(&shutdown_done);
176        });
177    }), ZX_OK);
178    ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
179    vfs = nullptr;
180
181    END_TEST;
182}
183
184// Test a case where the VFS object destroyed inside the callback to Shutdown.
185bool test_teardown_delete_this() {
186    BEGIN_TEST;
187
188     async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
189    sync_completion_t completions[3];
190    fbl::unique_ptr<fs::ManagedVfs> vfs;
191
192    ASSERT_TRUE(sync_start(completions, &loop, &vfs));
193
194    // B) Let sync complete.
195    sync_completion_signal(&completions[1]);
196
197    sync_completion_t* vnode_destroyed = &completions[2];
198    sync_completion_t shutdown_done;
199    fs::ManagedVfs* raw_vfs = vfs.release();
200    raw_vfs->Shutdown([&raw_vfs, &vnode_destroyed, &shutdown_done](zx_status_t status) {
201        ZX_ASSERT(status == ZX_OK);
202        // C) Issue an explicit shutdown, check that the Vnode has
203        // already torn down.
204        ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
205        delete raw_vfs;
206        sync_completion_signal(&shutdown_done);
207    });
208    ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
209
210    END_TEST;
211}
212
213// Test a case where the VFS object is shut down before a background async
214// callback gets the chance to complete.
215bool test_teardown_slow_async_callback() {
216    BEGIN_TEST;
217
218     async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
219    sync_completion_t completions[3];
220    fbl::unique_ptr<fs::ManagedVfs> vfs;
221
222    ASSERT_TRUE(sync_start(completions, &loop, &vfs));
223
224    sync_completion_t* vnode_destroyed = &completions[2];
225    sync_completion_t shutdown_done;
226    vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
227        ZX_ASSERT(status == ZX_OK);
228        // C) Issue an explicit shutdown, check that the Vnode has
229        // already torn down.
230        //
231        // Note: Will not be invoked until (B) completes.
232        ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
233        sync_completion_signal(&shutdown_done);
234    });
235
236    // Shutdown should be waiting for our sync to finish.
237    ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_MSEC(10)), ZX_ERR_TIMED_OUT);
238
239    // B) Let sync complete.
240    sync_completion_signal(&completions[1]);
241    ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
242    vfs = nullptr;
243
244    END_TEST;
245}
246
247// Test a case where the VFS object is shut down while a clone request
248// is concurrently trying to open a new connection.
249bool test_teardown_slow_clone() {
250    BEGIN_TEST;
251
252     async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
253    sync_completion_t completions[3];
254    auto vfs = fbl::make_unique<fs::ManagedVfs>(loop.dispatcher());
255    ASSERT_EQ(loop.StartThread(), ZX_OK);
256
257    auto vn = fbl::AdoptRef(new AsyncTearDownVnode(completions));
258    zx::channel client, server;
259    ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
260    ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
261    ASSERT_EQ(vn->Serve(vfs.get(), fbl::move(server), 0), ZX_OK);
262    vn = nullptr;
263
264    // A) Wait for sync to begin.
265    // Block the connection to the server in a sync, while simultanously
266    // sending a request to open a new connection.
267    send_sync(client);
268    sync_completion_wait(&completions[0], ZX_TIME_INFINITE);
269
270    zx::channel client2, server2;
271    ASSERT_EQ(zx::channel::create(0, &client2, &server2), ZX_OK);
272    ASSERT_EQ(fidl_clone_request(client.get(), server2.release(), 0), ZX_OK);
273
274    // The connection is now:
275    // - In a sync callback,
276    // - Enqueued with a clone request,
277    // - Closed.
278    client.reset();
279
280    sync_completion_t* vnode_destroyed = &completions[2];
281    sync_completion_t shutdown_done;
282    vfs->Shutdown([&vnode_destroyed, &shutdown_done](zx_status_t status) {
283        ZX_ASSERT(status == ZX_OK);
284        // C) Issue an explicit shutdown, check that the Vnode has
285        // already torn down.
286        //
287        // Note: Will not be invoked until (B) completes.
288        ZX_ASSERT(sync_completion_wait(vnode_destroyed, ZX_SEC(0)) == ZX_OK);
289        sync_completion_signal(&shutdown_done);
290    });
291
292    // Shutdown should be waiting for our sync to finish.
293    ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_MSEC(10)), ZX_ERR_TIMED_OUT);
294
295    // B) Let sync complete. This should result in a successful termination
296    // of the filesystem, even with the pending clone request.
297    sync_completion_signal(&completions[1]);
298    ASSERT_EQ(sync_completion_wait(&shutdown_done, ZX_SEC(3)), ZX_OK);
299    vfs = nullptr;
300
301    END_TEST;
302}
303
304bool test_synchronous_teardown() {
305    BEGIN_TEST;
306     async::Loop loop(&kAsyncLoopConfigNoAttachToThread);
307    ASSERT_EQ(loop.StartThread(), ZX_OK);
308    zx::channel client;
309
310    {
311        // Tear down the VFS while the async loop is running.
312        auto vfs = fbl::make_unique<fs::SynchronousVfs>(loop.dispatcher());
313        auto vn = fbl::AdoptRef(new FdCountVnode());
314        zx::channel server;
315        ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
316        ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
317        ASSERT_EQ(vn->Serve(vfs.get(), fbl::move(server), 0), ZX_OK);
318    }
319
320    loop.Quit();
321
322    {
323        // Tear down the VFS while the async loop is not running.
324        auto vfs = fbl::make_unique<fs::SynchronousVfs>(loop.dispatcher());
325        auto vn = fbl::AdoptRef(new FdCountVnode());
326        zx::channel server;
327        ASSERT_EQ(zx::channel::create(0, &client, &server), ZX_OK);
328        ASSERT_EQ(vn->Open(0, nullptr), ZX_OK);
329        ASSERT_EQ(vn->Serve(vfs.get(), fbl::move(server), 0), ZX_OK);
330    }
331
332    {
333        // Tear down the VFS with no active connections.
334        auto vfs = fbl::make_unique<fs::SynchronousVfs>(loop.dispatcher());
335    }
336
337    END_TEST;
338}
339
340} // namespace
341
342BEGIN_TEST_CASE(teardown_tests)
343RUN_TEST(test_unposted_teardown)
344RUN_TEST(test_posted_teardown)
345RUN_TEST(test_teardown_delete_this)
346RUN_TEST(test_teardown_slow_async_callback)
347RUN_TEST(test_teardown_slow_clone)
348RUN_TEST(test_synchronous_teardown)
349END_TEST_CASE(teardown_tests)
350