Loading src/client/intercept.cpp +10 −4 Changes for src/client/intercept.cpp: 10 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -125,7 +125,8 @@ is_mpi_runtime_path(const char* path) { } const char* first_path_arg(long syscall_number, long arg0, long arg1, long arg3) { first_path_arg(long syscall_number, long arg0, long arg1, long arg2, long arg3) { switch(syscall_number) { #ifdef SYS_open case SYS_open: Loading Loading @@ -177,7 +178,6 @@ first_path_arg(long syscall_number, long arg0, long arg1, long arg3) { case SYS_statx: #endif case SYS_unlinkat: case SYS_symlinkat: case SYS_faccessat: #ifdef SYS_faccessat2 case SYS_faccessat2: Loading @@ -195,6 +195,10 @@ first_path_arg(long syscall_number, long arg0, long arg1, long arg3) { #endif return reinterpret_cast<const char*>(arg1); case SYS_symlinkat: // oldname is literal link content; classify the destination path. return reinterpret_cast<const char*>(arg2); #ifdef SYS_rename case SYS_rename: return reinterpret_cast<const char*>(arg0); Loading Loading @@ -381,12 +385,14 @@ should_bypass_syscall(long syscall_number, long arg0, long arg1, long arg2, return true; } const char* path1 = first_path_arg(syscall_number, arg0, arg1, arg3); const char* path1 = first_path_arg(syscall_number, arg0, arg1, arg2, arg3); const char* path2 = second_path_arg(syscall_number, arg1, arg3); // Fast path for pathname syscalls if(path1 != nullptr) { int dirfd1 = first_dirfd_arg(syscall_number, arg0); int dirfd1 = syscall_number == SYS_symlinkat ? static_cast<int>(arg1) : first_dirfd_arg(syscall_number, arg0); if(!is_definitely_external_path(dirfd1, path1)) { return false; } Loading tests/integration/rename/test_spawn_dup_actions.py +60 −0 Changes for tests/integration/rename/test_spawn_dup_actions.py: 60 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ import sys import pytest def test_spawn_dup_actions(gkfs_daemon, gkfs_shell, tmp_path): output = tmp_path / "spawn-output" Loading @@ -27,3 +29,61 @@ finally: """ result = gkfs_shell.run(sys.executable, "-c", code) assert result.exit_code == 0, result.stderr.decode() @pytest.mark.parametrize("shell_fixture", ["gkfs_shell", "gkfs_shellLibc"]) def test_spawn_open_close_and_attributes(shell_fixture, request, gkfs_daemon, tmp_path): """Virtual-cwd spawning preserves actions, attributes and POSIX errors.""" shell = request.getfixturevalue(shell_fixture) output = tmp_path / "spawn-actions-output" command = shell.run("env", "LIBGKFS_LOG=none", sys.executable, "-c", """ import errno import fcntl import os import signal import sys mount, output = sys.argv[1:] os.chdir(mount) source = os.open('/dev/null', os.O_RDONLY) # Avoid a low fd being reused by the child's interpreter/preload startup. fd = fcntl.fcntl(source, fcntl.F_DUPFD, 500) os.close(source) child = ''' import errno, os, signal, sys try: os.fstat(int(sys.argv[1])) except OSError as error: assert error.errno == errno.EBADF else: raise AssertionError('close action was not applied') assert os.getpgrp() == os.getpid() assert signal.SIGUSR1 in signal.pthread_sigmask(signal.SIG_BLOCK, []) os.write(1, b'actions-and-attributes') ''' actions = [(os.POSIX_SPAWN_OPEN, 1, output, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600), (os.POSIX_SPAWN_CLOSE, fd)] try: pid = os.posix_spawn(sys.executable, [sys.executable, '-c', child, str(fd)], os.environ, file_actions=actions, setpgroup=0, setsigmask=(signal.SIGUSR1,), setsigdef=(signal.SIGUSR2,)) assert os.waitpid(pid, 0)[1] == 0 for executable, file_actions, expected in ( ('/does-not-exist-gkfs-spawn', [], errno.ENOENT), (sys.executable, [(os.POSIX_SPAWN_OPEN, 1, output + '/missing', os.O_RDONLY, 0)], errno.ENOTDIR), (sys.executable, [(os.POSIX_SPAWN_DUP2, 999999, 1)], errno.EBADF), ): try: os.posix_spawn(executable, [executable], os.environ, file_actions=file_actions) except OSError as error: assert error.errno == expected, (executable, error) else: raise AssertionError('spawn did not report setup failure') finally: os.close(fd) print('PASS spawn actions attributes and errors') """, str(gkfs_daemon.mountdir), str(output), timeout=60) assert command.exit_code == 0, command.stderr.decode(errors="replace") assert b"PASS spawn actions attributes and errors" in command.stdout assert output.read_bytes() == b"actions-and-attributes" tests/integration/syscalls/test_interceptor_runtime_bypass.py +50 −0 Changes for tests/integration/syscalls/test_interceptor_runtime_bypass.py: 50 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -61,3 +61,53 @@ def test_preload_bypasses_mpi_runtime_paths(gkfs_daemon, gkfs_shell): log = client_log.read_text(errors="replace") if client_log.exists() else "" assert str(runtime_file) not in log @pytest.mark.parametrize("shell_fixture", ["gkfs_shell", "gkfs_shellLibc"]) def test_external_dirfd_and_internal_paths(shell_fixture, request, gkfs_daemon, tmp_path): """External *at calls bypass safely without hiding virtual descriptors.""" import sys shell = request.getfixturevalue(shell_fixture) external = tmp_path / "external-at" external.mkdir() command = shell.run("env", "LIBGKFS_LOG=none", sys.executable, "-c", """ import os import sys external, mount = sys.argv[1:] directory = os.open(external, os.O_RDONLY | os.O_DIRECTORY) try: fd = os.open('source', os.O_CREAT | os.O_RDWR, 0o600, dir_fd=directory) os.write(fd, b'external') os.close(fd) assert os.stat('source', dir_fd=directory).st_size == 8 os.rename('source', 'target', src_dir_fd=directory, dst_dir_fd=directory) os.symlink('target', 'link', dir_fd=directory) assert os.readlink('link', dir_fd=directory) == 'target' os.unlink('link', dir_fd=directory) os.unlink('target', dir_fd=directory) os.mkdir('child', dir_fd=directory) os.rmdir('child', dir_fd=directory) os.chdir(mount) fd = os.open('internal', os.O_CREAT | os.O_RDWR, 0o600) os.write(fd, b'internal') assert os.fstat(fd).st_size == 8 os.close(fd) assert os.stat('internal').st_size == 8 os.unlink('internal') fd = os.open('after-chdir', os.O_CREAT | os.O_RDWR, 0o600, dir_fd=directory) os.close(fd) os.unlink('after-chdir', dir_fd=directory) # Link content is literal, not a path relative to the virtual cwd. os.symlink('../literal-target', 'after-chdir-link', dir_fd=directory) assert os.readlink('after-chdir-link', dir_fd=directory) == '../literal-target' os.unlink('after-chdir-link', dir_fd=directory) finally: os.close(directory) print('PASS external dirfd and virtual cwd') """, str(external), str(gkfs_daemon.mountdir), timeout=60) assert command.exit_code == 0, command.stderr.decode(errors="replace") assert b"PASS external dirfd and virtual cwd" in command.stdout assert list(external.iterdir()) == [] tests/unit/CMakeLists.txt +2 −0 Changes for tests/unit/CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -102,6 +102,7 @@ if (GKFS_TESTS_GUIDED_DISTRIBUTION) endif () if (GKFS_BUILD_USER_LIB) target_sources(unit_tests PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test_shm_cas.cpp) target_link_libraries(unit_tests PRIVATE Catch2WithMain Loading @@ -114,6 +115,7 @@ target_link_libraries(unit_tests distributor statistics gkfs_user_lib gkfs_common ) else () target_link_libraries(unit_tests Loading tests/unit/test_shm_cas.cpp 0 → 100644 +162 −0 Changes for tests/unit/test_shm_cas.cpp: 162 added lines, 0 removed lines. Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-3.0-or-later // Copyright 2026, Barcelona Supercomputing Center (BSC), Spain #include <client/shm_cas.hpp> #include <config.hpp> #include "helpers/helpers.hpp" #include <catch2/catch_all.hpp> #include <fmt/format.h> #include <filesystem> #include <fstream> #include <cerrno> #include <functional> #include <unistd.h> namespace { struct CasNamespace { uint64_t id; std::string path; gkfs::shm::ShmCasManager cache; CasNamespace() { // Reserve an unused namespace atomically, without touching another // test. auto pattern = fmt::format("/dev/shm/gkfs-cas-test-{}-XXXXXX", ::getpid()); const auto fd = ::mkstemp(pattern.data()); REQUIRE(fd >= 0); // tmpfs user quotas can be exhausted even when statvfs shows free // space. const char probe = 0; const auto written = ::write(fd, &probe, 1); const auto error = errno; ::close(fd); reservation = pattern; if(written != 1 && (error == ENOSPC || error == EDQUOT)) { ::unlink(reservation.c_str()); SKIP("No writable shared-memory quota available for CAS tests"); } REQUIRE(written == 1); id = std::hash<std::string>{}(pattern); path = fmt::format("/dev/shm/gkfs_cas_ns_{}_{}", ::getuid(), id); REQUIRE_FALSE(std::filesystem::exists(path)); } ~CasNamespace() { cache.destroy(); ::unlink(path.c_str()); ::unlink(reservation.c_str()); } std::string reservation; }; struct Value { mode_t mode{}; size_t size{}; time_t mtime{}; uint64_t high{}, low{}; std::string data; bool get(gkfs::shm::ShmCasManager& cache, const std::string& path) { return cache.get(path, mode, size, mtime, high, low, data); } }; } // namespace TEST_CASE("CAS cache rejects unavailable and invalid keys", "[shm_cas]") { CasNamespace ns; Value value; char buffer[8]{}; REQUIRE_FALSE(ns.cache.initialized()); REQUIRE_FALSE(ns.cache.put("/file", 0600, 1, 2, 3, 4, "x")); REQUIRE_FALSE(value.get(ns.cache, "/file")); REQUIRE_FALSE(ns.cache.invalidate("/file")); REQUIRE(ns.cache.read_chunk("", "/file", buffer, sizeof(buffer), 0) == -1); REQUIRE(ns.cache.init(ns.id)); REQUIRE(ns.cache.init(ns.id)); for(const auto& key : {std::string{}, std::string(gkfs::shm::SHM_CAS_MAX_PATH, 'x')}) { REQUIRE_FALSE(ns.cache.put(key, 0600, 1, 2, 3, 4, "x")); REQUIRE_FALSE(value.get(ns.cache, key)); REQUIRE_FALSE(ns.cache.invalidate(key)); } REQUIRE_FALSE(value.get(ns.cache, "/missing")); REQUIRE(ns.cache.invalidate("/missing")); REQUIRE(ns.cache.read_chunk("", "/missing", buffer, sizeof(buffer), 0) == -1); ns.cache.destroy(); ns.cache.destroy(); REQUIRE_FALSE(ns.cache.initialized()); } TEST_CASE("CAS inline entries can be updated shared and invalidated", "[shm_cas]") { CasNamespace ns; REQUIRE(ns.cache.init(ns.id)); REQUIRE(ns.cache.put("/file", 0640, 7, 1234, 11, 22, std::string("ab\0cdef", 7))); gkfs::shm::ShmCasManager peer; REQUIRE(peer.init(ns.id)); Value value; REQUIRE(value.get(peer, "/file")); REQUIRE(value.mode == 0640); REQUIRE(value.size == 7); REQUIRE(value.mtime == 1234); REQUIRE(value.high == 11); REQUIRE(value.low == 22); REQUIRE(value.data == std::string("ab\0cdef", 7)); char buffer[16]{}; REQUIRE(peer.read_chunk("", "/file", buffer, sizeof(buffer), 2) == 5); REQUIRE(std::string(buffer, 5) == std::string("\0cdef", 5)); REQUIRE(peer.read_chunk("", "/file", buffer, 2, 0) == 2); REQUIRE(std::string(buffer, 2) == "ab"); REQUIRE(peer.read_chunk("", "/file", buffer, sizeof(buffer), -1) == -1); REQUIRE(ns.cache.put("/file", 0600, 0, 4321, 0, 0, "")); REQUIRE(value.get(peer, "/file")); REQUIRE(value.data.empty()); REQUIRE(value.mode == 0600); REQUIRE(value.size == 0); REQUIRE(value.mtime == 4321); REQUIRE(value.high == 0); REQUIRE(value.low == 0); const std::string large(gkfs::shm::SHM_CAS_MAX_INLINE + 10, 'z'); REQUIRE(ns.cache.put("/large", 0600, large.size(), 1, 0, 0, large)); REQUIRE(value.get(peer, "/large")); REQUIRE(value.data == large.substr(0, gkfs::shm::SHM_CAS_MAX_INLINE)); REQUIRE(value.size == large.size()); REQUIRE(peer.invalidate("/file")); REQUIRE_FALSE(value.get(ns.cache, "/file")); REQUIRE(value.get(ns.cache, "/large")); peer.destroy(); REQUIRE(peer.init(ns.id)); REQUIRE(value.get(peer, "/large")); } TEST_CASE("CAS reads local chunks and reports unavailable storage", "[shm_cas]") { CasNamespace ns; REQUIRE(ns.cache.init(ns.id)); helpers::temporary_directory root; const auto chunk = root.dirname() / gkfs::config::data::chunk_dir / ".cas" / "00000000000000010000000000000002"; std::filesystem::create_directories(chunk.parent_path()); { std::ofstream output(chunk, std::ios::binary); output << "local-chunk"; REQUIRE(output.good()); } REQUIRE(ns.cache.put("/chunk", 0600, 11, 1, 1, 2, "")); char buffer[16]{}; REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, 6) == 5); REQUIRE(std::string(buffer, 5) == "chunk"); REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, 11) == 0); REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, -1) == -1); std::filesystem::remove(chunk); REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, 0) == -1); } No newline at end of file Loading
src/client/intercept.cpp +10 −4 Changes for src/client/intercept.cpp: 10 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -125,7 +125,8 @@ is_mpi_runtime_path(const char* path) { } const char* first_path_arg(long syscall_number, long arg0, long arg1, long arg3) { first_path_arg(long syscall_number, long arg0, long arg1, long arg2, long arg3) { switch(syscall_number) { #ifdef SYS_open case SYS_open: Loading Loading @@ -177,7 +178,6 @@ first_path_arg(long syscall_number, long arg0, long arg1, long arg3) { case SYS_statx: #endif case SYS_unlinkat: case SYS_symlinkat: case SYS_faccessat: #ifdef SYS_faccessat2 case SYS_faccessat2: Loading @@ -195,6 +195,10 @@ first_path_arg(long syscall_number, long arg0, long arg1, long arg3) { #endif return reinterpret_cast<const char*>(arg1); case SYS_symlinkat: // oldname is literal link content; classify the destination path. return reinterpret_cast<const char*>(arg2); #ifdef SYS_rename case SYS_rename: return reinterpret_cast<const char*>(arg0); Loading Loading @@ -381,12 +385,14 @@ should_bypass_syscall(long syscall_number, long arg0, long arg1, long arg2, return true; } const char* path1 = first_path_arg(syscall_number, arg0, arg1, arg3); const char* path1 = first_path_arg(syscall_number, arg0, arg1, arg2, arg3); const char* path2 = second_path_arg(syscall_number, arg1, arg3); // Fast path for pathname syscalls if(path1 != nullptr) { int dirfd1 = first_dirfd_arg(syscall_number, arg0); int dirfd1 = syscall_number == SYS_symlinkat ? static_cast<int>(arg1) : first_dirfd_arg(syscall_number, arg0); if(!is_definitely_external_path(dirfd1, path1)) { return false; } Loading
tests/integration/rename/test_spawn_dup_actions.py +60 −0 Changes for tests/integration/rename/test_spawn_dup_actions.py: 60 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -2,6 +2,8 @@ import sys import pytest def test_spawn_dup_actions(gkfs_daemon, gkfs_shell, tmp_path): output = tmp_path / "spawn-output" Loading @@ -27,3 +29,61 @@ finally: """ result = gkfs_shell.run(sys.executable, "-c", code) assert result.exit_code == 0, result.stderr.decode() @pytest.mark.parametrize("shell_fixture", ["gkfs_shell", "gkfs_shellLibc"]) def test_spawn_open_close_and_attributes(shell_fixture, request, gkfs_daemon, tmp_path): """Virtual-cwd spawning preserves actions, attributes and POSIX errors.""" shell = request.getfixturevalue(shell_fixture) output = tmp_path / "spawn-actions-output" command = shell.run("env", "LIBGKFS_LOG=none", sys.executable, "-c", """ import errno import fcntl import os import signal import sys mount, output = sys.argv[1:] os.chdir(mount) source = os.open('/dev/null', os.O_RDONLY) # Avoid a low fd being reused by the child's interpreter/preload startup. fd = fcntl.fcntl(source, fcntl.F_DUPFD, 500) os.close(source) child = ''' import errno, os, signal, sys try: os.fstat(int(sys.argv[1])) except OSError as error: assert error.errno == errno.EBADF else: raise AssertionError('close action was not applied') assert os.getpgrp() == os.getpid() assert signal.SIGUSR1 in signal.pthread_sigmask(signal.SIG_BLOCK, []) os.write(1, b'actions-and-attributes') ''' actions = [(os.POSIX_SPAWN_OPEN, 1, output, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600), (os.POSIX_SPAWN_CLOSE, fd)] try: pid = os.posix_spawn(sys.executable, [sys.executable, '-c', child, str(fd)], os.environ, file_actions=actions, setpgroup=0, setsigmask=(signal.SIGUSR1,), setsigdef=(signal.SIGUSR2,)) assert os.waitpid(pid, 0)[1] == 0 for executable, file_actions, expected in ( ('/does-not-exist-gkfs-spawn', [], errno.ENOENT), (sys.executable, [(os.POSIX_SPAWN_OPEN, 1, output + '/missing', os.O_RDONLY, 0)], errno.ENOTDIR), (sys.executable, [(os.POSIX_SPAWN_DUP2, 999999, 1)], errno.EBADF), ): try: os.posix_spawn(executable, [executable], os.environ, file_actions=file_actions) except OSError as error: assert error.errno == expected, (executable, error) else: raise AssertionError('spawn did not report setup failure') finally: os.close(fd) print('PASS spawn actions attributes and errors') """, str(gkfs_daemon.mountdir), str(output), timeout=60) assert command.exit_code == 0, command.stderr.decode(errors="replace") assert b"PASS spawn actions attributes and errors" in command.stdout assert output.read_bytes() == b"actions-and-attributes"
tests/integration/syscalls/test_interceptor_runtime_bypass.py +50 −0 Changes for tests/integration/syscalls/test_interceptor_runtime_bypass.py: 50 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -61,3 +61,53 @@ def test_preload_bypasses_mpi_runtime_paths(gkfs_daemon, gkfs_shell): log = client_log.read_text(errors="replace") if client_log.exists() else "" assert str(runtime_file) not in log @pytest.mark.parametrize("shell_fixture", ["gkfs_shell", "gkfs_shellLibc"]) def test_external_dirfd_and_internal_paths(shell_fixture, request, gkfs_daemon, tmp_path): """External *at calls bypass safely without hiding virtual descriptors.""" import sys shell = request.getfixturevalue(shell_fixture) external = tmp_path / "external-at" external.mkdir() command = shell.run("env", "LIBGKFS_LOG=none", sys.executable, "-c", """ import os import sys external, mount = sys.argv[1:] directory = os.open(external, os.O_RDONLY | os.O_DIRECTORY) try: fd = os.open('source', os.O_CREAT | os.O_RDWR, 0o600, dir_fd=directory) os.write(fd, b'external') os.close(fd) assert os.stat('source', dir_fd=directory).st_size == 8 os.rename('source', 'target', src_dir_fd=directory, dst_dir_fd=directory) os.symlink('target', 'link', dir_fd=directory) assert os.readlink('link', dir_fd=directory) == 'target' os.unlink('link', dir_fd=directory) os.unlink('target', dir_fd=directory) os.mkdir('child', dir_fd=directory) os.rmdir('child', dir_fd=directory) os.chdir(mount) fd = os.open('internal', os.O_CREAT | os.O_RDWR, 0o600) os.write(fd, b'internal') assert os.fstat(fd).st_size == 8 os.close(fd) assert os.stat('internal').st_size == 8 os.unlink('internal') fd = os.open('after-chdir', os.O_CREAT | os.O_RDWR, 0o600, dir_fd=directory) os.close(fd) os.unlink('after-chdir', dir_fd=directory) # Link content is literal, not a path relative to the virtual cwd. os.symlink('../literal-target', 'after-chdir-link', dir_fd=directory) assert os.readlink('after-chdir-link', dir_fd=directory) == '../literal-target' os.unlink('after-chdir-link', dir_fd=directory) finally: os.close(directory) print('PASS external dirfd and virtual cwd') """, str(external), str(gkfs_daemon.mountdir), timeout=60) assert command.exit_code == 0, command.stderr.decode(errors="replace") assert b"PASS external dirfd and virtual cwd" in command.stdout assert list(external.iterdir()) == []
tests/unit/CMakeLists.txt +2 −0 Changes for tests/unit/CMakeLists.txt: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -102,6 +102,7 @@ if (GKFS_TESTS_GUIDED_DISTRIBUTION) endif () if (GKFS_BUILD_USER_LIB) target_sources(unit_tests PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test_shm_cas.cpp) target_link_libraries(unit_tests PRIVATE Catch2WithMain Loading @@ -114,6 +115,7 @@ target_link_libraries(unit_tests distributor statistics gkfs_user_lib gkfs_common ) else () target_link_libraries(unit_tests Loading
tests/unit/test_shm_cas.cpp 0 → 100644 +162 −0 Changes for tests/unit/test_shm_cas.cpp: 162 added lines, 0 removed lines. Original line number Diff line number Diff line // SPDX-License-Identifier: GPL-3.0-or-later // Copyright 2026, Barcelona Supercomputing Center (BSC), Spain #include <client/shm_cas.hpp> #include <config.hpp> #include "helpers/helpers.hpp" #include <catch2/catch_all.hpp> #include <fmt/format.h> #include <filesystem> #include <fstream> #include <cerrno> #include <functional> #include <unistd.h> namespace { struct CasNamespace { uint64_t id; std::string path; gkfs::shm::ShmCasManager cache; CasNamespace() { // Reserve an unused namespace atomically, without touching another // test. auto pattern = fmt::format("/dev/shm/gkfs-cas-test-{}-XXXXXX", ::getpid()); const auto fd = ::mkstemp(pattern.data()); REQUIRE(fd >= 0); // tmpfs user quotas can be exhausted even when statvfs shows free // space. const char probe = 0; const auto written = ::write(fd, &probe, 1); const auto error = errno; ::close(fd); reservation = pattern; if(written != 1 && (error == ENOSPC || error == EDQUOT)) { ::unlink(reservation.c_str()); SKIP("No writable shared-memory quota available for CAS tests"); } REQUIRE(written == 1); id = std::hash<std::string>{}(pattern); path = fmt::format("/dev/shm/gkfs_cas_ns_{}_{}", ::getuid(), id); REQUIRE_FALSE(std::filesystem::exists(path)); } ~CasNamespace() { cache.destroy(); ::unlink(path.c_str()); ::unlink(reservation.c_str()); } std::string reservation; }; struct Value { mode_t mode{}; size_t size{}; time_t mtime{}; uint64_t high{}, low{}; std::string data; bool get(gkfs::shm::ShmCasManager& cache, const std::string& path) { return cache.get(path, mode, size, mtime, high, low, data); } }; } // namespace TEST_CASE("CAS cache rejects unavailable and invalid keys", "[shm_cas]") { CasNamespace ns; Value value; char buffer[8]{}; REQUIRE_FALSE(ns.cache.initialized()); REQUIRE_FALSE(ns.cache.put("/file", 0600, 1, 2, 3, 4, "x")); REQUIRE_FALSE(value.get(ns.cache, "/file")); REQUIRE_FALSE(ns.cache.invalidate("/file")); REQUIRE(ns.cache.read_chunk("", "/file", buffer, sizeof(buffer), 0) == -1); REQUIRE(ns.cache.init(ns.id)); REQUIRE(ns.cache.init(ns.id)); for(const auto& key : {std::string{}, std::string(gkfs::shm::SHM_CAS_MAX_PATH, 'x')}) { REQUIRE_FALSE(ns.cache.put(key, 0600, 1, 2, 3, 4, "x")); REQUIRE_FALSE(value.get(ns.cache, key)); REQUIRE_FALSE(ns.cache.invalidate(key)); } REQUIRE_FALSE(value.get(ns.cache, "/missing")); REQUIRE(ns.cache.invalidate("/missing")); REQUIRE(ns.cache.read_chunk("", "/missing", buffer, sizeof(buffer), 0) == -1); ns.cache.destroy(); ns.cache.destroy(); REQUIRE_FALSE(ns.cache.initialized()); } TEST_CASE("CAS inline entries can be updated shared and invalidated", "[shm_cas]") { CasNamespace ns; REQUIRE(ns.cache.init(ns.id)); REQUIRE(ns.cache.put("/file", 0640, 7, 1234, 11, 22, std::string("ab\0cdef", 7))); gkfs::shm::ShmCasManager peer; REQUIRE(peer.init(ns.id)); Value value; REQUIRE(value.get(peer, "/file")); REQUIRE(value.mode == 0640); REQUIRE(value.size == 7); REQUIRE(value.mtime == 1234); REQUIRE(value.high == 11); REQUIRE(value.low == 22); REQUIRE(value.data == std::string("ab\0cdef", 7)); char buffer[16]{}; REQUIRE(peer.read_chunk("", "/file", buffer, sizeof(buffer), 2) == 5); REQUIRE(std::string(buffer, 5) == std::string("\0cdef", 5)); REQUIRE(peer.read_chunk("", "/file", buffer, 2, 0) == 2); REQUIRE(std::string(buffer, 2) == "ab"); REQUIRE(peer.read_chunk("", "/file", buffer, sizeof(buffer), -1) == -1); REQUIRE(ns.cache.put("/file", 0600, 0, 4321, 0, 0, "")); REQUIRE(value.get(peer, "/file")); REQUIRE(value.data.empty()); REQUIRE(value.mode == 0600); REQUIRE(value.size == 0); REQUIRE(value.mtime == 4321); REQUIRE(value.high == 0); REQUIRE(value.low == 0); const std::string large(gkfs::shm::SHM_CAS_MAX_INLINE + 10, 'z'); REQUIRE(ns.cache.put("/large", 0600, large.size(), 1, 0, 0, large)); REQUIRE(value.get(peer, "/large")); REQUIRE(value.data == large.substr(0, gkfs::shm::SHM_CAS_MAX_INLINE)); REQUIRE(value.size == large.size()); REQUIRE(peer.invalidate("/file")); REQUIRE_FALSE(value.get(ns.cache, "/file")); REQUIRE(value.get(ns.cache, "/large")); peer.destroy(); REQUIRE(peer.init(ns.id)); REQUIRE(value.get(peer, "/large")); } TEST_CASE("CAS reads local chunks and reports unavailable storage", "[shm_cas]") { CasNamespace ns; REQUIRE(ns.cache.init(ns.id)); helpers::temporary_directory root; const auto chunk = root.dirname() / gkfs::config::data::chunk_dir / ".cas" / "00000000000000010000000000000002"; std::filesystem::create_directories(chunk.parent_path()); { std::ofstream output(chunk, std::ios::binary); output << "local-chunk"; REQUIRE(output.good()); } REQUIRE(ns.cache.put("/chunk", 0600, 11, 1, 1, 2, "")); char buffer[16]{}; REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, 6) == 5); REQUIRE(std::string(buffer, 5) == "chunk"); REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, 11) == 0); REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, -1) == -1); std::filesystem::remove(chunk); REQUIRE(ns.cache.read_chunk(root.dirname().string(), "/chunk", buffer, 5, 0) == -1); } No newline at end of file