Loading docker/0.9.6/deps/Dockerfile +1 −0 Viewed Changes for docker/0.9.6/deps/Dockerfile: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ RUN apt-get update && \ python3-venv \ python3-setuptools \ libnuma-dev libyaml-dev libcurl4-openssl-dev \ libfuse3-dev fuse3 \ procps && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && apt-get autoclean Loading docker/0.9.6/release/Dockerfile +4 −2 Viewed Changes for docker/0.9.6/release/Dockerfile: 4 added lines, 2 removed lines. Original line number Diff line number Diff line # syntax=docker/dockerfile:1 # Builder stage FROM debian:bookworm-slim AS builder FROM debian:trixie-slim AS builder # Install build dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ Loading @@ -25,6 +25,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcurl4-openssl-dev \ libffi-dev \ zlib1g-dev \ libfuse3-dev \ python3 \ perl \ patch \ Loading Loading @@ -82,7 +83,7 @@ RUN cmake \ make install # Runtime stage FROM debian:bookworm-slim FROM debian:trixie-slim # Install runtime dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ Loading @@ -94,6 +95,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcap2 \ libzmq5 \ liburing2 \ fuse3 \ && rm -rf /var/lib/apt/lists/* # Copy GekkoFS artifacts Loading src/daemon/handler/srv_metadata.cpp +4 −2 Viewed Changes for src/daemon/handler/srv_metadata.cpp: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ * @endinternal */ #include <cerrno> #include <daemon/handler/rpc_defs.hpp> #include <daemon/handler/rpc_util.hpp> #include <daemon/backend/metadata/db.hpp> Loading Loading @@ -1070,9 +1071,10 @@ rpc_srv_read_data_inline(const tl::request& req, if(md.size() > 0 && stored_data.empty()) { // Inline data key missing despite non-zero metadata size // Treat as empty file or error state // It might be that the file is in chunks (e.g., after a // truncate) out.err = EAGAIN; out.count = 0; out.err = 0; } else if(in.offset >= stored_data.size()) { // EOF out.count = 0; Loading tests/integration/fuse/test_basic_operations.py +3 −3 Viewed Changes for tests/integration/fuse/test_basic_operations.py: 3 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -58,13 +58,13 @@ def test_read(gkfs_daemon, fuse_client): assert sh.wc("-c", str(file2)) == "20 " + str(file2) + "\n" sh.mkdir(str(dir)) assert sh.ls(fuse_client.mountdir) == "dir file file2\n" sh.cd(str(dir)) os.chdir(str(dir)) assert sh.pwd() == str(dir) + "\n" sh.mkdir("-p", "foo/bar") assert sh.ls() == "foo\n" sh.cd("foo") os.chdir("foo") sh.rmdir("bar") sh.cd("..") os.chdir("..") sh.rmdir("foo") sh.rm(str(file2)) assert sh.ls(fuse_client.mountdir) == "dir file\n" Loading tests/integration/fuse/test_standard_compliance.py 0 → 100644 +172 −0 Viewed Changes for tests/integration/fuse/test_standard_compliance.py: 172 added lines, 0 removed lines. Original line number Diff line number Diff line import harness from pathlib import Path import errno import stat import os import ctypes import sh import sys import pytest import time import hashlib import random import string from harness.logger import logger def calculate_md5(file_path): hash_md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() def generate_random_data(size): return ''.join(random.choices(string.ascii_letters + string.digits, k=size)).encode() def test_large_io(gkfs_daemon, fuse_client): """Test reading and writing large files spanning multiple chunks.""" # 2MB file (assuming default chunksize of 512KB, this covers multiple chunks) file_size = 2 * 1024 * 1024 file_path = gkfs_daemon.mountdir / "large_file" # Generate random data data = generate_random_data(file_size) # Write data with open(file_path, "wb") as f: f.write(data) # Verify size via stat st = os.stat(file_path) assert st.st_size == file_size # Verify content md5 assert calculate_md5(file_path) == hashlib.md5(data).hexdigest() # Read back and verify with open(file_path, "rb") as f: read_data = f.read() assert read_data == data def test_truncate(gkfs_daemon, fuse_client): """Test file truncation (extend and shrink).""" file_path = gkfs_daemon.mountdir / "truncate_file" # Start with small content initial_data = b"start" with open(file_path, "wb") as f: f.write(initial_data) # Extend new_size = 100 * 1024 # 100KB os.truncate(file_path, new_size) assert os.stat(file_path).st_size == new_size # Verify hole is zero-filled with open(file_path, "rb") as f: content = f.read() assert content[:len(initial_data)] == initial_data assert content[len(initial_data):] == b'\0' * (new_size - len(initial_data)) # Shrink shrink_size = 3 os.truncate(file_path, shrink_size) assert os.stat(file_path).st_size == shrink_size # Force flush to ensure consistency fd = os.open(file_path, os.O_RDONLY) os.fsync(fd) os.close(fd) with open(file_path, "rb") as f: content = f.read() assert content == initial_data[:shrink_size] def test_metadata(gkfs_daemon, fuse_client): """Verify metadata correctness.""" file_path = gkfs_daemon.mountdir / "meta_file" # Create file with open(file_path, "w") as f: f.write("test") # Check stat st = os.stat(file_path) assert stat.S_ISREG(st.st_mode) assert st.st_size == 4 # Check permissions (basic check) # Note: FUSE permissions depend on mount options, but should be consistent original_mode = st.st_mode new_mode = 0o777 try: os.chmod(file_path, new_mode) # Refresh stat st = os.stat(file_path) # Mask with 0o777 to check permission bits only # GekkoFS might not support chmod fully in all backends or FUSE might mask it if (st.st_mode & 0o777) != new_mode: logger.warning(f"chmod mismatch: expected {oct(new_mode)}, got {oct(st.st_mode & 0o777)}") except OSError: pass # Optional support def test_directories_nested(gkfs_daemon, fuse_client): """Test nested directory operations.""" top_dir = gkfs_daemon.mountdir / "top" nested_dir = top_dir / "nested" / "deep" os.makedirs(nested_dir) assert os.path.exists(nested_dir) assert os.path.isdir(nested_dir) # Create file in deep dir file_path = nested_dir / "deep_file" with open(file_path, "w") as f: f.write("deep") assert os.path.exists(file_path) # List directory entries = os.listdir(nested_dir) assert "deep_file" in entries # Remove os.remove(file_path) os.removedirs(nested_dir) # Should remove all empty parents up to 'top' if empty? # removedirs removes parents if they become empty. # 'top' is in mountdir. assert not os.path.exists(nested_dir) def test_overwrite_middle(gkfs_daemon, fuse_client): """Test overwriting data in the middle of a file.""" file_path = gkfs_daemon.mountdir / "overwrite_file" # Write 10KB size = 10 * 1024 data = b'A' * size with open(file_path, "wb") as f: f.write(data) # Overwrite middle 2KB with 'B' offset = 4 * 1024 overwrite_len = 2 * 1024 new_data = b'B' * overwrite_len with open(file_path, "r+b") as f: f.seek(offset) f.write(new_data) # Verify expected = b'A' * offset + b'B' * overwrite_len + b'A' * (size - offset - overwrite_len) with open(file_path, "rb") as f: content = f.read() assert content == expected Loading
docker/0.9.6/deps/Dockerfile +1 −0 Viewed Changes for docker/0.9.6/deps/Dockerfile: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ RUN apt-get update && \ python3-venv \ python3-setuptools \ libnuma-dev libyaml-dev libcurl4-openssl-dev \ libfuse3-dev fuse3 \ procps && \ rm -rf /var/lib/apt/lists/* && \ apt-get clean && apt-get autoclean Loading
docker/0.9.6/release/Dockerfile +4 −2 Viewed Changes for docker/0.9.6/release/Dockerfile: 4 added lines, 2 removed lines. Original line number Diff line number Diff line # syntax=docker/dockerfile:1 # Builder stage FROM debian:bookworm-slim AS builder FROM debian:trixie-slim AS builder # Install build dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ Loading @@ -25,6 +25,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcurl4-openssl-dev \ libffi-dev \ zlib1g-dev \ libfuse3-dev \ python3 \ perl \ patch \ Loading Loading @@ -82,7 +83,7 @@ RUN cmake \ make install # Runtime stage FROM debian:bookworm-slim FROM debian:trixie-slim # Install runtime dependencies RUN apt-get update && apt-get install -y --no-install-recommends \ Loading @@ -94,6 +95,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libcap2 \ libzmq5 \ liburing2 \ fuse3 \ && rm -rf /var/lib/apt/lists/* # Copy GekkoFS artifacts Loading
src/daemon/handler/srv_metadata.cpp +4 −2 Viewed Changes for src/daemon/handler/srv_metadata.cpp: 4 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ * @endinternal */ #include <cerrno> #include <daemon/handler/rpc_defs.hpp> #include <daemon/handler/rpc_util.hpp> #include <daemon/backend/metadata/db.hpp> Loading Loading @@ -1070,9 +1071,10 @@ rpc_srv_read_data_inline(const tl::request& req, if(md.size() > 0 && stored_data.empty()) { // Inline data key missing despite non-zero metadata size // Treat as empty file or error state // It might be that the file is in chunks (e.g., after a // truncate) out.err = EAGAIN; out.count = 0; out.err = 0; } else if(in.offset >= stored_data.size()) { // EOF out.count = 0; Loading
tests/integration/fuse/test_basic_operations.py +3 −3 Viewed Changes for tests/integration/fuse/test_basic_operations.py: 3 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -58,13 +58,13 @@ def test_read(gkfs_daemon, fuse_client): assert sh.wc("-c", str(file2)) == "20 " + str(file2) + "\n" sh.mkdir(str(dir)) assert sh.ls(fuse_client.mountdir) == "dir file file2\n" sh.cd(str(dir)) os.chdir(str(dir)) assert sh.pwd() == str(dir) + "\n" sh.mkdir("-p", "foo/bar") assert sh.ls() == "foo\n" sh.cd("foo") os.chdir("foo") sh.rmdir("bar") sh.cd("..") os.chdir("..") sh.rmdir("foo") sh.rm(str(file2)) assert sh.ls(fuse_client.mountdir) == "dir file\n" Loading
tests/integration/fuse/test_standard_compliance.py 0 → 100644 +172 −0 Viewed Changes for tests/integration/fuse/test_standard_compliance.py: 172 added lines, 0 removed lines. Original line number Diff line number Diff line import harness from pathlib import Path import errno import stat import os import ctypes import sh import sys import pytest import time import hashlib import random import string from harness.logger import logger def calculate_md5(file_path): hash_md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() def generate_random_data(size): return ''.join(random.choices(string.ascii_letters + string.digits, k=size)).encode() def test_large_io(gkfs_daemon, fuse_client): """Test reading and writing large files spanning multiple chunks.""" # 2MB file (assuming default chunksize of 512KB, this covers multiple chunks) file_size = 2 * 1024 * 1024 file_path = gkfs_daemon.mountdir / "large_file" # Generate random data data = generate_random_data(file_size) # Write data with open(file_path, "wb") as f: f.write(data) # Verify size via stat st = os.stat(file_path) assert st.st_size == file_size # Verify content md5 assert calculate_md5(file_path) == hashlib.md5(data).hexdigest() # Read back and verify with open(file_path, "rb") as f: read_data = f.read() assert read_data == data def test_truncate(gkfs_daemon, fuse_client): """Test file truncation (extend and shrink).""" file_path = gkfs_daemon.mountdir / "truncate_file" # Start with small content initial_data = b"start" with open(file_path, "wb") as f: f.write(initial_data) # Extend new_size = 100 * 1024 # 100KB os.truncate(file_path, new_size) assert os.stat(file_path).st_size == new_size # Verify hole is zero-filled with open(file_path, "rb") as f: content = f.read() assert content[:len(initial_data)] == initial_data assert content[len(initial_data):] == b'\0' * (new_size - len(initial_data)) # Shrink shrink_size = 3 os.truncate(file_path, shrink_size) assert os.stat(file_path).st_size == shrink_size # Force flush to ensure consistency fd = os.open(file_path, os.O_RDONLY) os.fsync(fd) os.close(fd) with open(file_path, "rb") as f: content = f.read() assert content == initial_data[:shrink_size] def test_metadata(gkfs_daemon, fuse_client): """Verify metadata correctness.""" file_path = gkfs_daemon.mountdir / "meta_file" # Create file with open(file_path, "w") as f: f.write("test") # Check stat st = os.stat(file_path) assert stat.S_ISREG(st.st_mode) assert st.st_size == 4 # Check permissions (basic check) # Note: FUSE permissions depend on mount options, but should be consistent original_mode = st.st_mode new_mode = 0o777 try: os.chmod(file_path, new_mode) # Refresh stat st = os.stat(file_path) # Mask with 0o777 to check permission bits only # GekkoFS might not support chmod fully in all backends or FUSE might mask it if (st.st_mode & 0o777) != new_mode: logger.warning(f"chmod mismatch: expected {oct(new_mode)}, got {oct(st.st_mode & 0o777)}") except OSError: pass # Optional support def test_directories_nested(gkfs_daemon, fuse_client): """Test nested directory operations.""" top_dir = gkfs_daemon.mountdir / "top" nested_dir = top_dir / "nested" / "deep" os.makedirs(nested_dir) assert os.path.exists(nested_dir) assert os.path.isdir(nested_dir) # Create file in deep dir file_path = nested_dir / "deep_file" with open(file_path, "w") as f: f.write("deep") assert os.path.exists(file_path) # List directory entries = os.listdir(nested_dir) assert "deep_file" in entries # Remove os.remove(file_path) os.removedirs(nested_dir) # Should remove all empty parents up to 'top' if empty? # removedirs removes parents if they become empty. # 'top' is in mountdir. assert not os.path.exists(nested_dir) def test_overwrite_middle(gkfs_daemon, fuse_client): """Test overwriting data in the middle of a file.""" file_path = gkfs_daemon.mountdir / "overwrite_file" # Write 10KB size = 10 * 1024 data = b'A' * size with open(file_path, "wb") as f: f.write(data) # Overwrite middle 2KB with 'B' offset = 4 * 1024 overwrite_len = 2 * 1024 new_data = b'B' * overwrite_len with open(file_path, "r+b") as f: f.seek(offset) f.write(new_data) # Verify expected = b'A' * offset + b'B' * overwrite_len + b'A' * (size - offset - overwrite_len) with open(file_path, "rb") as f: content = f.read() assert content == expected