// Copyright 2017 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { bool test_memfs_null() { BEGIN_TEST; async::Loop loop(&kAsyncLoopConfigNoAttachToThread); ASSERT_EQ(loop.StartThread(), ZX_OK); memfs_filesystem_t* vfs; zx_handle_t root; ASSERT_EQ(memfs_create_filesystem(loop.dispatcher(), &vfs, &root), ZX_OK); ASSERT_EQ(zx_handle_close(root), ZX_OK); sync_completion_t unmounted; memfs_free_filesystem(vfs, &unmounted); ASSERT_EQ(sync_completion_wait(&unmounted, ZX_SEC(3)), ZX_OK); END_TEST; } bool test_memfs_basic() { BEGIN_TEST; async::Loop loop(&kAsyncLoopConfigNoAttachToThread); ASSERT_EQ(loop.StartThread(), ZX_OK); // Create a memfs filesystem, acquire a file descriptor memfs_filesystem_t* vfs; zx_handle_t root; ASSERT_EQ(memfs_create_filesystem(loop.dispatcher(), &vfs, &root), ZX_OK); uint32_t type = PA_FDIO_REMOTE; int fd; ASSERT_EQ(fdio_create_fd(&root, &type, 1, &fd), ZX_OK); // Access files within the filesystem. DIR* d = fdopendir(fd); // Create a file const char* filename = "file-a"; fd = openat(dirfd(d), filename, O_CREAT | O_RDWR); ASSERT_GE(fd, 0); const char* data = "hello"; ssize_t datalen = strlen(data); ASSERT_EQ(write(fd, data, datalen), datalen); ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0); char buf[32]; ASSERT_EQ(read(fd, buf, sizeof(buf)), datalen); ASSERT_EQ(memcmp(buf, data, datalen), 0); // Readdir the file struct dirent* de; ASSERT_NONNULL((de = readdir(d))); ASSERT_EQ(strcmp(de->d_name, "."), 0); ASSERT_NONNULL((de = readdir(d))); ASSERT_EQ(strcmp(de->d_name, filename), 0); ASSERT_NULL(readdir(d)); ASSERT_EQ(closedir(d), 0); sync_completion_t unmounted; memfs_free_filesystem(vfs, &unmounted); ASSERT_EQ(sync_completion_wait(&unmounted, ZX_SEC(3)), ZX_OK); END_TEST; } bool test_memfs_limit_pages() { BEGIN_TEST; constexpr ssize_t kPageSize = static_cast(PAGE_SIZE); fbl::Vector page_limits = {1, 2, 5, 50}; for (const auto& page_limit : page_limits) { async::Loop loop(&kAsyncLoopConfigNoAttachToThread); ASSERT_EQ(loop.StartThread(), ZX_OK); // Create a memfs filesystem, acquire a file descriptor memfs_filesystem_t* vfs; zx_handle_t root; ASSERT_EQ(memfs_create_filesystem_with_page_limit(loop.dispatcher(), static_cast(page_limit), &vfs, &root), ZX_OK); uint32_t type = PA_FDIO_REMOTE; int raw_root_fd; ASSERT_EQ(fdio_create_fd(&root, &type, 1, &raw_root_fd), ZX_OK); fbl::unique_fd root_fd(raw_root_fd); // Access files within the filesystem. DIR* d = fdopendir(root_fd.get()); // Create a file const char* filename = "file-a"; fbl::unique_fd fd = fbl::unique_fd(openat(dirfd(d), filename, O_CREAT | O_RDWR)); ASSERT_GE(fd.get(), 0); auto data = fbl::unique_ptr(new uint8_t[page_limit * kPageSize + 1]); auto data_back = fbl::unique_ptr(new uint8_t[page_limit * kPageSize + 1]); for (ssize_t i = 0; i < page_limit * kPageSize + 1; i++) { data[i] = static_cast(i % 100); data_back[i] = 0; } // 1. Write some data which is exactly kPageSize * page_limit bytes long ASSERT_EQ(write(fd.get(), &data[0], kPageSize * page_limit), kPageSize * page_limit); ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0); ASSERT_EQ(read(fd.get(), &data_back[0], kPageSize * page_limit), kPageSize * page_limit); ASSERT_EQ(memcmp(&data[0], &data_back[0], kPageSize * page_limit), 0); // 2. Try to write to a second file. This should fail since the first file has already // taken up all the available pages. const char* filename_another = "file-b"; int fd_another = openat(dirfd(d), filename_another, O_CREAT | O_RDWR); ASSERT_GE(fd_another, 0); ASSERT_EQ(write(fd_another, &data[0], 1), -1); ASSERT_EQ(errno, ENOSPC); ASSERT_EQ(unlinkat(dirfd(d), filename_another, 0), 0); ASSERT_EQ(close(fd_another), 0); // 3. Overwriting the file should succeed because it does not result in extra allocations. ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0); ASSERT_EQ(write(fd.get(), &data[0], kPageSize * page_limit), kPageSize * page_limit); // 4. Write some data which is exactly (kPageSize * page_limit + 1) bytes long. // This should fail. ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0); errno = 0; ASSERT_EQ(write(fd.get(), &data[0], kPageSize * page_limit + 1), -1); ASSERT_EQ(errno, ENOSPC); // 5. Write kPageSize * page_limit data then unlink&close&open the file, repeat a few times. for (size_t i = 0; i < 3; i++) { ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0); errno = 0; ASSERT_EQ(write(fd.get(), &data[0], kPageSize * page_limit), kPageSize * page_limit); ASSERT_EQ(errno, 0); ASSERT_EQ(unlinkat(dirfd(d), "file-a", 0), 0); fd = fbl::unique_fd(openat(dirfd(d), filename, O_CREAT | O_RDWR)); ASSERT_GE(fd.get(), 0); } // Teardown ASSERT_EQ(closedir(d), 0); sync_completion_t unmounted; memfs_free_filesystem(vfs, &unmounted); ASSERT_EQ(sync_completion_wait(&unmounted, ZX_SEC(3)), ZX_OK); } END_TEST; } bool test_memfs_install() { BEGIN_TEST; async::Loop loop(&kAsyncLoopConfigNoAttachToThread); ASSERT_EQ(loop.StartThread(), ZX_OK); ASSERT_EQ(memfs_install_at(loop.dispatcher(), "/mytmp"), ZX_OK); int fd = open("/mytmp", O_DIRECTORY | O_RDONLY); ASSERT_GE(fd, 0); // Access files within the filesystem. DIR* d = fdopendir(fd); // Create a file const char* filename = "file-a"; fd = openat(dirfd(d), filename, O_CREAT | O_RDWR); ASSERT_GE(fd, 0); const char* data = "hello"; ssize_t datalen = strlen(data); ASSERT_EQ(write(fd, data, datalen), datalen); ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0); char buf[32]; ASSERT_EQ(read(fd, buf, sizeof(buf)), datalen); ASSERT_EQ(memcmp(buf, data, datalen), 0); // Readdir the file struct dirent* de; ASSERT_NONNULL((de = readdir(d))); ASSERT_EQ(strcmp(de->d_name, "."), 0); ASSERT_NONNULL((de = readdir(d))); ASSERT_EQ(strcmp(de->d_name, filename), 0); ASSERT_NULL(readdir(d)); ASSERT_EQ(closedir(d), 0); ASSERT_EQ(memfs_install_at(loop.dispatcher(), "/mytmp"), ZX_ERR_ALREADY_EXISTS); loop.Shutdown(); // No way to clean up the namespace entry. See ZX-2013 for more details. END_TEST; } bool test_memfs_close_during_access() { BEGIN_TEST; async::Loop loop(&kAsyncLoopConfigNoAttachToThread); ASSERT_EQ(loop.StartThread(), ZX_OK); // Create a memfs filesystem, acquire a file descriptor memfs_filesystem_t* vfs; zx_handle_t root; ASSERT_EQ(memfs_create_filesystem(loop.dispatcher(), &vfs, &root), ZX_OK); uint32_t type = PA_FDIO_REMOTE; int fd; ASSERT_EQ(fdio_create_fd(&root, &type, 1, &fd), ZX_OK); // Access files within the filesystem. DIR* d = fdopendir(fd); ASSERT_NONNULL(d); thrd_t worker; struct thread_args { DIR* d; sync_completion_t spinning{}; } args { .d = d, }; ASSERT_EQ(thrd_create(&worker, [](void* arg) { thread_args* args = reinterpret_cast(arg); DIR* d = args->d; int fd = openat(dirfd(d), "foo", O_CREAT | O_RDWR); while (true) { if (close(fd)) { return errno == EPIPE ? 0 : -1; } if ((fd = openat(dirfd(d), "foo", O_RDWR)) < 0) { return errno == EPIPE ? 0 : -1; } sync_completion_signal(&args->spinning); } }, &args), thrd_success); ASSERT_EQ(sync_completion_wait(&args.spinning, ZX_SEC(3)), ZX_OK); sync_completion_t unmounted; memfs_free_filesystem(vfs, &unmounted); ASSERT_EQ(sync_completion_wait(&unmounted, ZX_SEC(3)), ZX_OK); int result; ASSERT_EQ(thrd_join(worker, &result), thrd_success); ASSERT_EQ(result, 0); // Now that the filesystem has terminated, we should be // unable to access it. ASSERT_LT(openat(dirfd(d), "foo", O_CREAT | O_RDWR), 0); ASSERT_EQ(errno, EPIPE, "Expected connection to remote server to be closed"); // Since the filesystem has terminated, this will // only close the client side of the connection. ASSERT_EQ(closedir(d), 0); END_TEST; } } // namespace BEGIN_TEST_CASE(memfs_tests) RUN_TEST(test_memfs_null) RUN_TEST(test_memfs_basic) RUN_TEST(test_memfs_limit_pages) RUN_TEST(test_memfs_install) RUN_TEST(test_memfs_close_during_access) END_TEST_CASE(memfs_tests)