| // Copyright 2019 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/fdio/fd.h> |
| #include <lib/zx/stream.h> |
| #include <lib/zx/vmo.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <future> |
| #include <limits> |
| |
| #include <fbl/unique_fd.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/testing/predicates/status.h" |
| #include "src/storage/memfs/memfs.h" |
| #include "src/storage/memfs/vnode_dir.h" |
| |
| namespace memfs { |
| namespace { |
| |
| constexpr uint64_t kMaxFileSize = std::numeric_limits<zx_off_t>::max() - (1ull << 17) + 1; |
| |
| TEST(MemfsTests, TestMemfsBasic) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| |
| // Create a memfs filesystem, acquire a file descriptor |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [memfs, root] = result.value(); |
| auto shutdown = fit::defer([&memfs = memfs]() { |
| std::promise<zx_status_t> promise; |
| memfs->Shutdown([&promise](zx_status_t status) { promise.set_value(status); }); |
| ASSERT_EQ(promise.get_future().get(), ZX_OK); |
| }); |
| |
| auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| ASSERT_EQ(memfs->ServeDirectory(std::move(root), std::move(server)), ZX_OK); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| fbl::unique_fd fd; |
| ASSERT_EQ(fdio_fd_create(client.TakeChannel().release(), fd.reset_and_get_address()), ZX_OK); |
| |
| // Access files within the filesystem. |
| DIR* d = fdopendir(fd.get()); |
| ASSERT_NE(d, nullptr); |
| auto close_dir = fit::defer([d]() { ASSERT_EQ(closedir(d), 0) << strerror(errno); }); |
| |
| // Create a file |
| constexpr char filename[] = "file-a"; |
| { |
| fbl::unique_fd fd(openat(dirfd(d), filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)); |
| ASSERT_TRUE(fd.is_valid()) << strerror(errno); |
| constexpr char data[] = "hello"; |
| constexpr ssize_t datalen = sizeof(data); |
| ASSERT_EQ(write(fd.get(), data, datalen), datalen); |
| ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0); |
| char buf[32]; |
| ASSERT_EQ(read(fd.get(), buf, sizeof(buf)), datalen); |
| ASSERT_EQ(memcmp(buf, data, datalen), 0); |
| } |
| |
| // Readdir the file |
| struct dirent* de; |
| ASSERT_NE((de = readdir(d)), nullptr); |
| ASSERT_EQ(strcmp(de->d_name, "."), 0); |
| ASSERT_NE((de = readdir(d)), nullptr); |
| ASSERT_EQ(strcmp(de->d_name, filename), 0); |
| ASSERT_EQ(readdir(d), nullptr); |
| } |
| |
| TEST(MemfsTests, TestMemfsAppend) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| |
| // Create a memfs filesystem, acquire a file descriptor |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [memfs, root] = result.value(); |
| auto shutdown = fit::defer([&memfs = memfs]() { |
| std::promise<zx_status_t> promise; |
| memfs->Shutdown([&promise](zx_status_t status) { promise.set_value(status); }); |
| ASSERT_EQ(promise.get_future().get(), ZX_OK); |
| }); |
| |
| auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| ASSERT_EQ(memfs->ServeDirectory(std::move(root), std::move(server)), ZX_OK); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| fbl::unique_fd fd; |
| ASSERT_EQ(fdio_fd_create(client.TakeChannel().release(), fd.reset_and_get_address()), ZX_OK); |
| |
| // Access files within the filesystem. |
| DIR* d = fdopendir(fd.get()); |
| ASSERT_NE(d, nullptr); |
| auto close_dir = fit::defer([d]() { ASSERT_EQ(closedir(d), 0) << strerror(errno); }); |
| |
| // Create a file |
| constexpr char filename[] = "file-a"; |
| { |
| fbl::unique_fd fd(openat(dirfd(d), filename, O_CREAT | O_RDWR | O_APPEND, S_IRUSR | S_IWUSR)); |
| ASSERT_TRUE(fd.is_valid()) << strerror(errno); |
| { |
| constexpr char data[] = "hello"; |
| constexpr ssize_t datalen = sizeof(data) - 1; |
| ASSERT_EQ(write(fd.get(), data, datalen), datalen); |
| } |
| ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0); |
| { |
| constexpr char data[] = ", world"; |
| constexpr ssize_t datalen = sizeof(data) - 1; |
| ASSERT_EQ(write(fd.get(), data, datalen), datalen); |
| } |
| ASSERT_EQ(lseek(fd.get(), 0, SEEK_CUR), 12); |
| ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0); |
| { |
| constexpr char data[] = "hello, world"; |
| constexpr ssize_t datalen = sizeof(data) - 1; |
| char buf[32]; |
| ASSERT_EQ(read(fd.get(), buf, sizeof(buf)), datalen); |
| ASSERT_EQ(memcmp(buf, data, datalen), 0); |
| } |
| } |
| } |
| |
| TEST(MemfsTests, TestMemfsCloseDuringAccess) { |
| for (int i = 0; i < 100; i++) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| |
| // Create a memfs filesystem, acquire a file descriptor |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [memfs, root] = result.value(); |
| auto shutdown = fit::defer([&memfs = memfs]() { |
| std::promise<zx_status_t> promise; |
| memfs->Shutdown([&promise](zx_status_t status) { promise.set_value(status); }); |
| ASSERT_EQ(promise.get_future().get(), ZX_OK); |
| }); |
| |
| auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| ASSERT_EQ(memfs->ServeDirectory(std::move(root), std::move(server)), ZX_OK); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| fbl::unique_fd fd; |
| ASSERT_EQ(fdio_fd_create(client.TakeChannel().release(), fd.reset_and_get_address()), ZX_OK); |
| |
| // Access files within the filesystem. |
| DIR* d = fdopendir(fd.get()); |
| ASSERT_NE(d, nullptr); |
| auto close_dir = fit::defer([d]() { ASSERT_EQ(closedir(d), 0) << strerror(errno); }); |
| |
| // Create the file. |
| constexpr char filename[] = "foo"; |
| { |
| fbl::unique_fd fd(openat(dirfd(d), filename, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)); |
| ASSERT_TRUE(fd.is_valid()) << strerror(errno); |
| } |
| |
| std::thread worker([d, filename]() { |
| for (size_t i = 0;; ++i) { |
| fbl::unique_fd fd(openat(dirfd(d), filename, O_RDWR)); |
| if (!fd.is_valid()) { |
| ASSERT_EQ(errno, EPIPE) << "fd=" << fd.get() << "\n" |
| << "i=" << i << "\n" |
| << strerror(errno); |
| break; |
| } |
| if (close(fd.get()) != 0) { |
| ASSERT_EQ(errno, EPIPE) << "fd=" << fd.get() << "\n" |
| << "i=" << i << "\n" |
| << strerror(errno); |
| break; |
| } |
| } |
| }); |
| |
| shutdown.call(); |
| |
| worker.join(); |
| |
| // Now that the filesystem has terminated, we should be |
| // unable to access it. |
| ASSERT_LT(openat(dirfd(d), filename, O_RDWR), 0); |
| ASSERT_EQ(errno, EPIPE) << "Expected connection to remote server to be closed: " |
| << strerror(errno); |
| } |
| } |
| |
| TEST(MemfsTests, TestMemfsOverflow) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| // Create a memfs filesystem, acquire a file descriptor |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [memfs, root] = result.value(); |
| auto shutdown = fit::defer([&memfs = memfs]() { |
| std::promise<zx_status_t> promise; |
| memfs->Shutdown([&promise](zx_status_t status) { promise.set_value(status); }); |
| ASSERT_EQ(promise.get_future().get(), ZX_OK); |
| }); |
| |
| auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| ASSERT_EQ(memfs->ServeDirectory(std::move(root), std::move(server)), ZX_OK); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| fbl::unique_fd root_fd; |
| ASSERT_EQ(fdio_fd_create(client.TakeChannel().release(), root_fd.reset_and_get_address()), ZX_OK); |
| |
| // Access files within the filesystem. |
| DIR* d = fdopendir(root_fd.get()); |
| ASSERT_NE(d, nullptr); |
| auto close_dir = fit::defer([d]() { ASSERT_EQ(closedir(d), 0) << strerror(errno); }); |
| |
| // 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 (https://fxbug.dev/42108793). |
| 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.is_valid()) << strerror(errno); |
| ASSERT_EQ(pwrite(fd.get(), buf, 199, 0), 199); |
| ASSERT_EQ(pwrite(fd.get(), buf, 226, 0xfffffffffffff801), -1); |
| ASSERT_EQ(errno, EINVAL) << strerror(errno); |
| } |
| |
| TEST(MemfsTests, TestMemfsDetachLinkedFilesystem) { |
| async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| // Create a memfs filesystem, acquire a file descriptor |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [memfs, root] = result.value(); |
| auto shutdown = fit::defer([&memfs = memfs]() { |
| std::promise<zx_status_t> promise; |
| memfs->Shutdown([&promise](zx_status_t status) { promise.set_value(status); }); |
| ASSERT_EQ(promise.get_future().get(), ZX_OK); |
| }); |
| |
| auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| |
| ASSERT_EQ(memfs->ServeDirectory(std::move(root), std::move(server)), ZX_OK); |
| ASSERT_EQ(loop.StartThread(), ZX_OK); |
| |
| fbl::unique_fd root_fd; |
| ASSERT_EQ(fdio_fd_create(client.TakeChannel().release(), root_fd.reset_and_get_address()), ZX_OK); |
| |
| // Access files within the filesystem. |
| DIR* d = fdopendir(root_fd.get()); |
| ASSERT_NE(d, nullptr); |
| auto close_dir = fit::defer([d]() { ASSERT_EQ(closedir(d), 0) << strerror(errno); }); |
| |
| // Leave a regular file. |
| fbl::unique_fd fd(openat(dirfd(d), "file", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)); |
| ASSERT_TRUE(fd.is_valid()) << strerror(errno); |
| |
| // 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)); |
| } |
| |
| TEST(MemfsTest, DirectoryLifetime) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| } |
| |
| TEST(MemfsTest, CreateFile) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [vfs, root] = result.value(); |
| zx::result file = root->Create("foobar", fs::CreationType::kFile); |
| ASSERT_TRUE(file.is_ok()) << file.status_string(); |
| auto directory = static_cast<fbl::RefPtr<fs::Vnode>>(root); |
| fs::VnodeAttributes directory_attr, file_attr; |
| ASSERT_OK(directory->GetAttributes(&directory_attr)); |
| ASSERT_OK(file->GetAttributes(&file_attr)); |
| |
| // Directory created before file. |
| ASSERT_LE(directory_attr.creation_time, file_attr.creation_time); |
| |
| // Observe that the modify time of the directory is larger than the file. |
| // This implies "the file is created, then the directory is updated". |
| ASSERT_GE(directory_attr.modification_time, file_attr.modification_time); |
| } |
| |
| TEST(MemfsTest, SubdirectoryUpdateTime) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [vfs, root] = result.value(); |
| zx::result index = root->Create("index", fs::CreationType::kFile); |
| ASSERT_TRUE(index.is_ok()) << index.status_string(); |
| zx::result subdirectory = root->Create("subdirectory", fs::CreationType::kDirectory); |
| ASSERT_TRUE(subdirectory.is_ok()) << subdirectory.status_string(); |
| |
| // Write a file at "subdirectory/file". |
| zx::result file = subdirectory->Create("file", fs::CreationType::kFile); |
| ASSERT_TRUE(file.is_ok()) << file.status_string(); |
| file->DidModifyStream(); |
| |
| // Overwrite a file at "index". |
| index->DidModifyStream(); |
| |
| fs::VnodeAttributes subdirectory_attr, index_attr; |
| ASSERT_OK(subdirectory->GetAttributes(&subdirectory_attr)); |
| ASSERT_OK(index->GetAttributes(&index_attr)); |
| |
| // "index" was written after "subdirectory". |
| ASSERT_LE(subdirectory_attr.modification_time, index_attr.modification_time); |
| } |
| |
| TEST(MemfsTest, SubPageContentSize) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [vfs, root] = result.value(); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(zx_system_get_page_size(), 0, &vmo)); |
| |
| // Set the content size to a non page aligned value. |
| zx_off_t content_size = zx_system_get_page_size() / 2; |
| EXPECT_OK(vmo.set_property(ZX_PROP_VMO_CONTENT_SIZE, &content_size, sizeof(content_size))); |
| |
| // Duplicate the handle to create the VMO file so we can use the original handle for testing. |
| zx::vmo vmo_dup; |
| ASSERT_OK(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup)); |
| // Create a VMO file sized to its content. |
| ASSERT_OK(root->CreateFromVmo("vmo", vmo_dup.release(), 0, content_size)); |
| |
| // Lookup the VMO and request its representation. |
| fbl::RefPtr<fs::Vnode> vmo_vnode; |
| ASSERT_OK(root->Lookup("vmo", &vmo_vnode)); |
| zx::vmo vnode_vmo; |
| ASSERT_EQ(ZX_OK, vmo_vnode->GetVmo(fuchsia_io::wire::VmoFlags::kRead, &vnode_vmo)); |
| |
| // We expect no cloning to have happened, this means we should have a handle to our original VMO. |
| // We can verify this by comparing koids. |
| zx_info_handle_basic_t original_vmo_info; |
| ASSERT_OK(vmo.get_info(ZX_INFO_HANDLE_BASIC, &original_vmo_info, sizeof(original_vmo_info), |
| nullptr, nullptr)); |
| zx_info_handle_basic_t vnode_vmo_info; |
| ASSERT_OK(vnode_vmo.get_info(ZX_INFO_HANDLE_BASIC, &vnode_vmo_info, sizeof(vnode_vmo_info), |
| nullptr, nullptr)); |
| EXPECT_EQ(original_vmo_info.koid, vnode_vmo_info.koid); |
| } |
| |
| TEST(MemfsTest, LocalClone) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [vfs, root] = result.value(); |
| |
| zx_off_t vmo_size = zx_system_get_page_size() * static_cast<zx_off_t>(2); |
| zx_off_t vmo_offset = vmo_size / 2; |
| |
| // Offset is required to be page aligned and non-zero. |
| ASSERT_EQ(vmo_offset % zx_system_get_page_size(), 0ull); |
| ASSERT_NE(vmo_offset, 0ull); |
| |
| zx::vmo vmo; |
| ASSERT_OK(zx::vmo::create(vmo_size, 0, &vmo)); |
| |
| zx_info_handle_basic_t original_vmo_info; |
| ASSERT_OK(vmo.get_info(ZX_INFO_HANDLE_BASIC, &original_vmo_info, sizeof(original_vmo_info), |
| nullptr, nullptr)); |
| |
| // Create a file from a VMO using a non-zero offset after which we should NOT get an exact copy. |
| zx::vmo vmo_dup; |
| fbl::RefPtr<fs::Vnode> vmo_vnode; |
| zx_info_handle_basic_t vnode_vmo_info; |
| |
| ASSERT_OK(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup)); |
| ASSERT_OK(root->CreateFromVmo("vmo1", vmo_dup.release(), vmo_offset, vmo_size - vmo_offset)); |
| ASSERT_OK(root->Lookup("vmo1", &vmo_vnode)); |
| zx::vmo vnode_vmo; |
| ASSERT_EQ(ZX_OK, vmo_vnode->GetVmo(fuchsia_io::wire::VmoFlags::kRead, &vnode_vmo)); |
| ASSERT_OK(vnode_vmo.get_info(ZX_INFO_HANDLE_BASIC, &vnode_vmo_info, sizeof(vnode_vmo_info), |
| nullptr, nullptr)); |
| EXPECT_NE(original_vmo_info.koid, vnode_vmo_info.koid); |
| |
| // Create a file from a VMO using a smaller size, after which we should NOT get an exact copy. |
| ASSERT_OK(vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_dup)); |
| ASSERT_OK(root->CreateFromVmo("vmo2", vmo_dup.release(), 0, vmo_size - 1)); |
| ASSERT_OK(root->Lookup("vmo2", &vmo_vnode)); |
| ASSERT_EQ(ZX_OK, vmo_vnode->GetVmo(fuchsia_io::wire::VmoFlags::kRead, &vnode_vmo)); |
| ASSERT_OK(vnode_vmo.get_info(ZX_INFO_HANDLE_BASIC, &vnode_vmo_info, sizeof(vnode_vmo_info), |
| nullptr, nullptr)); |
| EXPECT_NE(original_vmo_info.koid, vnode_vmo_info.koid); |
| } |
| |
| TEST(MemfsTest, TruncateZerosTail) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [vfs, root] = result.value(); |
| |
| zx::result file = root->Create("file", fs::CreationType::kFile); |
| ASSERT_TRUE(file.is_ok()) << file.status_string(); |
| |
| zx::result<zx::stream> stream = file->CreateStream(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE); |
| ASSERT_TRUE(stream.is_ok()) << stream.status_string(); |
| ASSERT_TRUE(stream->is_valid()); |
| |
| std::string data = "file-contents"; |
| zx_iovec_t iov = { |
| .buffer = data.data(), |
| .capacity = data.size(), |
| }; |
| size_t actual = 0; |
| ASSERT_OK(stream->writev_at(0, 500, &iov, 1, &actual)); |
| ASSERT_EQ(actual, data.size()); |
| |
| // Shrink the file to before the write. |
| file->Truncate(300); |
| // Grow the file back to after the write. |
| file->Truncate(600); |
| |
| // Verify the data is gone. |
| std::vector<char> file_contents(data.size(), 0); |
| iov.buffer = file_contents.data(); |
| ASSERT_OK(stream->readv_at(0, 500, &iov, 1, &actual)); |
| ASSERT_EQ(actual, data.size()); |
| EXPECT_THAT(file_contents, testing::Each('\0')); |
| } |
| |
| TEST(MemfsTest, WriteMaxFileSize) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [vfs, root] = result.value(); |
| |
| zx::result file = root->Create("file", fs::CreationType::kFile); |
| ASSERT_TRUE(file.is_ok()) << file.status_string(); |
| |
| zx::result<zx::stream> stream = file->CreateStream(ZX_STREAM_MODE_READ | ZX_STREAM_MODE_WRITE); |
| ASSERT_TRUE(stream.is_ok()) << stream.status_string(); |
| ASSERT_TRUE(stream->is_valid()); |
| |
| std::string data = "1"; |
| zx_iovec_t iov = { |
| .buffer = data.data(), |
| .capacity = data.size(), |
| }; |
| size_t actual = 0; |
| ASSERT_OK(stream->writev_at(0, kMaxFileSize - 1, &iov, 1, &actual)); |
| ASSERT_EQ(actual, data.size()); |
| fs::VnodeAttributes attributes; |
| ASSERT_OK(file->GetAttributes(&attributes)); |
| ASSERT_EQ(attributes.content_size, kMaxFileSize); |
| |
| // Try to write beyond the max file size. |
| ASSERT_STATUS(stream->writev_at(0, kMaxFileSize, &iov, 1, &actual), ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| TEST(MemfsTest, TruncateToMaxFileSize) { |
| async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread); |
| |
| zx::result result = memfs::Memfs::Create(loop.dispatcher(), "<tmp>"); |
| ASSERT_TRUE(result.is_ok()) << result.status_string(); |
| auto& [vfs, root] = result.value(); |
| |
| zx::result file = root->Create("file", fs::CreationType::kFile); |
| ASSERT_OK(file.status_value()); |
| ASSERT_OK(file->Truncate(kMaxFileSize)); |
| fs::VnodeAttributes attributes; |
| ASSERT_OK(file->GetAttributes(&attributes)); |
| ASSERT_EQ(attributes.content_size, kMaxFileSize); |
| |
| // Try to truncate beyond the max file size. |
| ASSERT_STATUS(file->Truncate(kMaxFileSize + 1), ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| } // namespace |
| } // namespace memfs |