| // Copyright 2021 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 <fcntl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async/cpp/task.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/memfs/memfs.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <fbl/unique_fd.h> |
| #include <zxtest/zxtest.h> |
| |
| namespace { |
| |
| TEST(MemfsTests, TestMemfsNull) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| 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); |
| } |
| |
| TEST(MemfsTests, TestMemfsBasic) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| 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, S_IRUSR | S_IWUSR); |
| ASSERT_GE(fd, 0); |
| const char* data = "hello"; |
| size_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); |
| close(fd); |
| |
| // Readdir the file |
| struct dirent* de; |
| ASSERT_NOT_NULL((de = readdir(d))); |
| ASSERT_EQ(strcmp(de->d_name, "."), 0); |
| ASSERT_NOT_NULL((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); |
| } |
| |
| TEST(MemfsTests, TestMemfsAppend) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| 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 | O_APPEND, S_IRUSR | S_IWUSR); |
| ASSERT_GE(fd, 0); |
| const char* data = "hello"; |
| size_t datalen = strlen(data); |
| ASSERT_EQ(write(fd, data, datalen), datalen); |
| ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0); |
| data = ", world"; |
| datalen = strlen(data); |
| ASSERT_EQ(write(fd, data, datalen), datalen); |
| ASSERT_EQ(lseek(fd, 0, SEEK_CUR), 12); |
| ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0); |
| data = "hello, world"; |
| datalen = strlen(data); |
| char buf[32]; |
| ASSERT_EQ(read(fd, buf, sizeof(buf)), datalen); |
| ASSERT_EQ(memcmp(buf, data, datalen), 0); |
| close(fd); |
| |
| 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); |
| } |
| |
| TEST(MemfsTests, TestMemfsInstall) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| memfs_filesystem_t* fs; |
| ASSERT_EQ(memfs_install_at(loop.dispatcher(), "/mytmp", &fs), 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, S_IRUSR | S_IWUSR); |
| ASSERT_GE(fd, 0); |
| const char* data = "hello"; |
| size_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); |
| close(fd); |
| |
| // Readdir the file |
| struct dirent* de; |
| ASSERT_NOT_NULL((de = readdir(d))); |
| ASSERT_EQ(strcmp(de->d_name, "."), 0); |
| ASSERT_NOT_NULL((de = readdir(d))); |
| ASSERT_EQ(strcmp(de->d_name, filename), 0); |
| ASSERT_NULL(readdir(d)); |
| |
| ASSERT_EQ(closedir(d), 0); |
| memfs_filesystem_t* fs_2; |
| ASSERT_EQ(memfs_install_at(loop.dispatcher(), "/mytmp", &fs_2), ZX_ERR_ALREADY_EXISTS); |
| |
| // Wait for cleanup of failed memfs install. |
| sync_completion_t unmounted; |
| async::PostTask(loop.dispatcher(), [&unmounted]() { sync_completion_signal(&unmounted); }); |
| ASSERT_EQ(sync_completion_wait(&unmounted, zx::duration::infinite().get()), ZX_OK); |
| |
| // Synchronously clean up the good one. |
| sync_completion_reset(&unmounted); |
| memfs_free_filesystem(fs, &unmounted); |
| ASSERT_EQ(sync_completion_wait(&unmounted, zx::duration::infinite().get()), ZX_OK); |
| |
| loop.Shutdown(); |
| } |
| |
| TEST(MemfsTests, TestMemfsCloseDuringAccess) { |
| for (int i = 0; i < 100; i++) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| 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_NOT_NULL(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, S_IRUSR | S_IWUSR); |
| 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, S_IRUSR | S_IWUSR), 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); |
| } |
| } |
| |
| TEST(MemfsTests, TestMemfsOverflow) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| 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_NOT_NULL(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 (fxbug.dev/33581). |
| uint8_t buf[4096]; |
| memset(buf, 'a', sizeof(buf)); |
| fbl::unique_fd fd(openat(dirfd(d), "file", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)); |
| ASSERT_TRUE(fd); |
| ASSERT_EQ(pwrite(fd.get(), buf, 199, 0), 199); |
| ASSERT_EQ(pwrite(fd.get(), buf, 226, 0xfffffffffffff801), -1); |
| ASSERT_EQ(errno, EINVAL); |
| |
| 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); |
| } |
| |
| TEST(MemfsTests, TestMemfsDetachLinkedFilesystem) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| 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_NOT_NULL(d); |
| |
| // Leave a regular file. |
| fbl::unique_fd fd(openat(dirfd(d), "file", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)); |
| 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); |
| } |
| |
| } // namespace |