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