| // 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 <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <threads.h> |
| #include <unistd.h> |
| |
| #include <fbl/unique_ptr.h> |
| #include <fbl/unique_fd.h> |
| #include <fbl/vector.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/memfs/memfs.h> |
| #include <unittest/unittest.h> |
| #include <zircon/processargs.h> |
| #include <zircon/syscalls.h> |
| |
| namespace { |
| |
| bool TestMemfsNull() { |
| 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::duration::infinite().get()), ZX_OK); |
| |
| END_TEST; |
| } |
| |
| bool TestMemfsBasic() { |
| 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); |
| int fd; |
| ASSERT_EQ(fdio_fd_create(root, &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::duration::infinite().get()), ZX_OK); |
| |
| END_TEST; |
| } |
| |
| bool TestMemfsLimitPages() { |
| BEGIN_TEST; |
| |
| constexpr ssize_t kPageSize = static_cast<ssize_t>(PAGE_SIZE); |
| fbl::Vector<ssize_t> 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<size_t>(page_limit), |
| &vfs, &root), ZX_OK); |
| int raw_root_fd = -1; |
| ASSERT_EQ(fdio_fd_create(root, &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<uint8_t[]>(new uint8_t[page_limit * kPageSize + 1]); |
| auto data_back = fbl::unique_ptr<uint8_t[]>(new uint8_t[page_limit * kPageSize + 1]); |
| for (ssize_t i = 0; i < page_limit * kPageSize + 1; i++) { |
| data[i] = static_cast<uint8_t>(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::duration::infinite().get()), ZX_OK); |
| } |
| |
| END_TEST; |
| } |
| |
| bool TestMemfsInstall() { |
| 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 TestMemfsCloseDuringAccess() { |
| BEGIN_TEST; |
| |
| for (int i = 0; i < 100; i++) { |
| 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); |
| int fd = -1; |
| ASSERT_EQ(fdio_fd_create(root, &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<thread_args*>(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::duration::infinite().get()), ZX_OK); |
| |
| sync_completion_t unmounted; |
| memfs_free_filesystem(vfs, &unmounted); |
| ASSERT_EQ(sync_completion_wait(&unmounted, zx::duration::infinite().get()), 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; |
| } |
| |
| bool TestMemfsOverflow() { |
| 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); |
| int root_fd; |
| ASSERT_EQ(fdio_fd_create(root, &root_fd), ZX_OK); |
| |
| // Access files within the filesystem. |
| DIR* d = fdopendir(root_fd); |
| ASSERT_NONNULL(d); |
| |
| // Issue writes to the file in an order that previously would have triggered |
| // an overflow in the memfs write path. |
| // |
| // Values provided mimic the bug reported by syzkaller (ZX-3791). |
| uint8_t buf[4096]; |
| memset(buf, 'a', sizeof(buf)); |
| fbl::unique_fd fd(openat(dirfd(d), "file", O_CREAT | O_RDWR)); |
| ASSERT_TRUE(fd); |
| ASSERT_EQ(pwrite(fd.get(), buf, 199, 0), 199); |
| ASSERT_EQ(pwrite(fd.get(), buf, 226, 0xfffffffffffff801), -1); |
| ASSERT_EQ(errno, EFBIG); |
| |
| ASSERT_EQ(closedir(d), 0); |
| sync_completion_t unmounted; |
| memfs_free_filesystem(vfs, &unmounted); |
| ASSERT_EQ(sync_completion_wait(&unmounted, zx::duration::infinite().get()), ZX_OK); |
| END_TEST; |
| } |
| |
| bool TestMemfsDetachLinkedFilesystem() { |
| 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); |
| int root_fd; |
| ASSERT_EQ(fdio_fd_create(root, &root_fd), ZX_OK); |
| |
| // Access files within the filesystem. |
| DIR* d = fdopendir(root_fd); |
| ASSERT_NONNULL(d); |
| |
| // Leave a regular file. |
| fbl::unique_fd fd(openat(dirfd(d), "file", O_CREAT | O_RDWR)); |
| ASSERT_TRUE(fd); |
| |
| // Leave an empty subdirectory. |
| ASSERT_EQ(0, mkdirat(dirfd(d), "empty-subdirectory", 0)); |
| |
| // Leave a subdirectory with children. |
| ASSERT_EQ(0, mkdirat(dirfd(d), "subdirectory", 0)); |
| ASSERT_EQ(0, mkdirat(dirfd(d), "subdirectory/child", 0)); |
| |
| ASSERT_EQ(closedir(d), 0); |
| |
| sync_completion_t unmounted; |
| memfs_free_filesystem(vfs, &unmounted); |
| ASSERT_EQ(sync_completion_wait(&unmounted, zx::duration::infinite().get()), ZX_OK); |
| END_TEST; |
| } |
| |
| } // namespace |
| |
| BEGIN_TEST_CASE(memfs_tests) |
| RUN_TEST(TestMemfsNull) |
| RUN_TEST(TestMemfsBasic) |
| RUN_TEST(TestMemfsLimitPages) |
| RUN_TEST(TestMemfsInstall) |
| RUN_TEST(TestMemfsCloseDuringAccess) |
| RUN_TEST(TestMemfsOverflow) |
| RUN_TEST(TestMemfsDetachLinkedFilesystem) |
| END_TEST_CASE(memfs_tests) |