From 8df54749a5d1a0bd702346b5c1ef2888fa4244d6 Mon Sep 17 00:00:00 2001 From: "Scott E. Graves" Date: Sat, 20 Sep 2025 12:30:29 -0500 Subject: [PATCH] [unit test] Complete FUSE unit tests #22 --- .cspell/words.txt | 1 + CHANGELOG.md | 4 + .../src/drives/fuse/fuse_base.cpp | 4 +- .../src/drives/fuse/fuse_drive.cpp | 8 +- .../src/fuse_drive_fallocate_test.cpp | 245 ++++++++++++++++++ 5 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 repertory/repertory_test/src/fuse_drive_fallocate_test.cpp diff --git a/.cspell/words.txt b/.cspell/words.txt index 68b1a0e2..2a38d841 100644 --- a/.cspell/words.txt +++ b/.cspell/words.txt @@ -106,6 +106,7 @@ endfunction eventlib expect_streq expect_strne +falloc_fl_keep_size fallocate fallocate_impl fext diff --git a/CHANGELOG.md b/CHANGELOG.md index ff90c326..a6959c9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ * \#60 Implement secure key via KDF for transparent data encryption/decryption * \#61 [ui] UI theme should match repertory blue +### Changes from v2.0.7-release + +* Fixed handling of `FALLOC_FL_KEEP_SIZE` on Linux + ## v2.0.7-release diff --git a/repertory/librepertory/src/drives/fuse/fuse_base.cpp b/repertory/librepertory/src/drives/fuse/fuse_base.cpp index ccda30cb..5f80b8d6 100644 --- a/repertory/librepertory/src/drives/fuse/fuse_base.cpp +++ b/repertory/librepertory/src/drives/fuse/fuse_base.cpp @@ -102,7 +102,7 @@ fuse_base::~fuse_base() { E_CONSUMER_RELEASE(); } auto fuse_base::access_(const char *path, int mask) -> int { REPERTORY_USES_FUNCTION_NAME(); - return instance().instance().execute_callback( + return instance().execute_callback( function_name, path, [&](std::string api_path) -> api_error { return instance().access_impl(std::move(api_path), mask); }); @@ -112,7 +112,7 @@ auto fuse_base::access_(const char *path, int mask) -> int { auto fuse_base::chflags_(const char *path, uint32_t flags) -> int { REPERTORY_USES_FUNCTION_NAME(); - return instance().instance().execute_callback( + return instance().execute_callback( function_name, path, [&](std::string api_path) -> api_error { return instance().chflags_impl(std::move(api_path), flags); }); diff --git a/repertory/librepertory/src/drives/fuse/fuse_drive.cpp b/repertory/librepertory/src/drives/fuse/fuse_drive.cpp index d9faf864..002a4102 100644 --- a/repertory/librepertory/src/drives/fuse/fuse_drive.cpp +++ b/repertory/librepertory/src/drives/fuse/fuse_drive.cpp @@ -336,6 +336,7 @@ auto fuse_drive::fallocate_impl(std::string /*api_path*/, int mode, i_open_file::native_operation_callback allocator; + auto new_file_size = static_cast(offset + length); #if defined(__APPLE__) fstore_t fstore = {0}; if (not(mode & PREALLOCATE)) { @@ -368,10 +369,13 @@ auto fuse_drive::fallocate_impl(std::string /*api_path*/, int mode, return (fallocate(handle, mode, offset, length) == -1) ? api_error::os_error : api_error::success; }; + + if ((mode & FALLOC_FL_KEEP_SIZE) == FALLOC_FL_KEEP_SIZE) { + new_file_size = open_file->get_file_size(); + } #endif // __APPLE__ - return open_file->native_operation( - static_cast(offset + length), allocator); + return open_file->native_operation(new_file_size, allocator); } auto fuse_drive::fgetattr_impl(std::string api_path, struct stat *u_stat, diff --git a/repertory/repertory_test/src/fuse_drive_fallocate_test.cpp b/repertory/repertory_test/src/fuse_drive_fallocate_test.cpp new file mode 100644 index 00000000..ae71858c --- /dev/null +++ b/repertory/repertory_test/src/fuse_drive_fallocate_test.cpp @@ -0,0 +1,245 @@ +/* + Copyright <2018-2025> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#if !defined(_WIN32) + +#include "fixtures/drive_fixture.hpp" + +namespace repertory { +TYPED_TEST_SUITE(fuse_test, platform_provider_types); + +TYPED_TEST(fuse_test, fallocate_basic_preallocation_platform_semantics) { + std::string name{"fallocate"}; + auto src = this->create_file_and_test(name); + + auto desc = ::open(src.c_str(), O_RDWR); + ASSERT_NE(desc, -1); + + constexpr off_t off = 0; + constexpr off_t len = 64 * 1024; + +#if defined(__APPLE__) + fstore_t store{}; + store.fst_flags = F_ALLOCATECONTIG; + store.fst_posmode = F_PEOFPOSMODE; + store.fst_offset = 0; + store.fst_length = len; + store.fst_bytesalloc = 0; + + auto res = ::fcntl(desc, F_PREALLOCATE, &store); + if (res == -1) { + store.fst_flags = F_ALLOCATEALL; + res = ::fcntl(desc, F_PREALLOCATE, &store); + } + EXPECT_EQ(0, res); + + struct stat st_unix{}; + EXPECT_EQ(0, ::fstat(desc, &st_unix)); + EXPECT_TRUE(S_ISREG(st_unix.st_mode)); + EXPECT_EQ(0, st_unix.st_size); +#else // !defined(__APPLE__) + auto res = ::posix_fallocate(desc, off, len); + EXPECT_EQ(0, res); + + struct stat st_unix{}; + EXPECT_EQ(0, ::fstat(desc, &st_unix)); + EXPECT_TRUE(S_ISREG(st_unix.st_mode)); + EXPECT_EQ(off + len, st_unix.st_size); +#endif // defined(__APPLE__) + + ::close(desc); + this->unlink_file_and_test(src); +} + +TYPED_TEST(fuse_test, fallocate_then_ftruncate_makes_size_visible) { + std::string name{"fallocate"}; + auto src = this->create_file_and_test(name); + + auto desc = ::open(src.c_str(), O_RDWR); + ASSERT_NE(desc, -1); + + constexpr off_t len = 128 * 1024; + +#if defined(__APPLE__) + fstore_t store{}; + store.fst_flags = F_ALLOCATECONTIG; + store.fst_posmode = F_PEOFPOSMODE; + store.fst_offset = 0; + store.fst_length = len; + store.fst_bytesalloc = 0; + auto res = ::fcntl(desc, F_PREALLOCATE, &store); + if (res == -1) { + store.fst_flags = F_ALLOCATEALL; + res = ::fcntl(desc, F_PREALLOCATE, &store); + } + EXPECT_EQ(0, res); + EXPECT_EQ(0, ::ftruncate(desc, len)); +#else // !defined(__APPLE__) + auto res = ::posix_fallocate(desc, 0, len); + EXPECT_EQ(0, res); + EXPECT_EQ(0, ::ftruncate(desc, len / 2)); + EXPECT_EQ(0, ::ftruncate(desc, len)); +#endif // defined(__APPLE__) + + struct stat st_unix{}; + EXPECT_EQ(0, ::fstat(desc, &st_unix)); + EXPECT_TRUE(S_ISREG(st_unix.st_mode)); + EXPECT_EQ(len, st_unix.st_size); + + ::close(desc); + this->unlink_file_and_test(src); +} + +#if !defined(__APPLE__) +TYPED_TEST(fuse_test, + fallocate_does_not_change_size_when_keep_size_is_specified) { + std::string name{"fallocate"}; + auto src = this->create_file_and_test(name); + + auto desc = ::open(src.c_str(), O_RDWR); + ASSERT_NE(desc, -1); + + EXPECT_EQ(0, ::ftruncate(desc, 4096)); + + constexpr off_t len = 64 * 1024; + errno = 0; + auto res = ::fallocate(desc, FALLOC_FL_KEEP_SIZE, 0, len); + if (res == -1 && + (errno == ENOSYS || errno == EOPNOTSUPP || errno == EINVAL)) { + ::close(desc); + this->unlink_file_and_test(src); + return; + } + EXPECT_EQ(0, res); + + struct stat st_unix{}; + EXPECT_EQ(0, ::fstat(desc, &st_unix)); + EXPECT_TRUE(S_ISREG(st_unix.st_mode)); + EXPECT_EQ(4096, st_unix.st_size); + + ::close(desc); + this->unlink_file_and_test(src); +} + +TYPED_TEST( + fuse_test, + fallocate_does_not_change_size_when_keep_size_and_punch_hole_are_specified) { + std::string name{"fallocate"}; + auto src = this->create_file_and_test(name); + + auto desc = ::open(src.c_str(), O_RDWR); + ASSERT_NE(desc, -1); + + constexpr off_t size = 64 * 1024; + EXPECT_EQ(0, ::ftruncate(desc, size)); + + constexpr off_t hole_off = 24 * 1024; + constexpr off_t hole_len = 8 * 1024; + + errno = 0; + auto res = ::fallocate(desc, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, + hole_off, hole_len); + if (res == -1 && + (errno == ENOSYS || errno == EOPNOTSUPP || errno == EINVAL)) { + ::close(desc); + this->unlink_file_and_test(src); + return; + } + EXPECT_EQ(0, res) << "errno: " << errno; + + struct stat st_unix{}; + EXPECT_EQ(0, ::fstat(desc, &st_unix)); + EXPECT_EQ(size, st_unix.st_size); + + ::close(desc); + this->unlink_file_and_test(src); +} +#endif // !defined(__APPLE__) + +TYPED_TEST(fuse_test, fallocate_can_handle_invalid_arguments) { + std::string name{"fallocate"}; + auto src = this->create_file_and_test(name); + + auto desc = ::open(src.c_str(), O_RDWR); + ASSERT_NE(desc, -1); + +#if defined(__APPLE__) + fstore_t store{}; + store.fst_flags = F_ALLOCATEALL; + store.fst_posmode = F_PEOFPOSMODE; + store.fst_offset = 0; + store.fst_length = 0; + errno = 0; + auto res = ::fcntl(desc, F_PREALLOCATE, &store); + if (res == 0) { + ::close(desc); + this->unlink_file_and_test(src); + return; + } + + EXPECT_EQ(-1, res); + EXPECT_TRUE(errno == EINVAL || errno == EOPNOTSUPP || errno == ENOSYS); +#else // !defined(__APPLE__) + auto ret1 = ::posix_fallocate(desc, -1, 4096); + EXPECT_EQ(EINVAL, ret1); + + auto ret2 = ::posix_fallocate(desc, 0, -4096); + EXPECT_EQ(EINVAL, ret2); +#endif // defined(__APPLE__) + + ::close(desc); + this->unlink_file_and_test(src); +} + +TYPED_TEST(fuse_test, fallocate_fails_on_directory) { + std::string dir_name{"dir"}; + auto dir = this->create_directory_and_test(dir_name); + +#if defined(__APPLE__) + auto desc = ::open(dir.c_str(), O_RDONLY); + EXPECT_NE(desc, -1); + + fstore_t store{}; + store.fst_flags = F_ALLOCATEALL; + store.fst_posmode = F_PEOFPOSMODE; + store.fst_offset = 0; + store.fst_length = 4096; + + errno = 0; + auto res = ::fcntl(desc, F_PREALLOCATE, &store); + EXPECT_EQ(-1, res); + EXPECT_TRUE(errno == EISDIR || errno == EBADF || errno == EOPNOTSUPP || + errno == ENOTTY || errno == ENOSYS); + ::close(desc); +#else // !defined(__APPLE__) + auto desc = ::open(dir.c_str(), O_RDONLY | O_DIRECTORY); + EXPECT_NE(desc, -1); + + auto ret = ::posix_fallocate(desc, 0, 4096); + EXPECT_NE(0, ret); + ::close(desc); +#endif // defined(__APPLE__) + + this->rmdir_and_test(dir); +} +} // namespace repertory + +#endif // !defined(_WIN32)