diff --git a/test/fuse_integration/POSIX_COMPLIANCE.md b/test/fuse_integration/POSIX_COMPLIANCE.md index b166870e7..d7586aa23 100644 --- a/test/fuse_integration/POSIX_COMPLIANCE.md +++ b/test/fuse_integration/POSIX_COMPLIANCE.md @@ -8,12 +8,14 @@ This comprehensive test suite provides full POSIX compliance testing for Seaweed ### ✅ **Comprehensive Test Coverage** - **Basic POSIX Operations**: File/directory create, read, write, delete, rename -- **Advanced Features**: Extended attributes, file locking, memory mapping -- **I/O Operations**: Synchronous/asynchronous I/O, direct I/O, vectored I/O +- **Advanced Features**: Extended attributes, file locking, memory mapping *(see roadmap)* +- **I/O Operations**: Synchronous/asynchronous I/O, direct I/O, vectored I/O *(see roadmap)* - **Error Handling**: Comprehensive error condition testing - **Concurrent Operations**: Multi-threaded stress testing - **External Integration**: pjdfstest, nfstest, FIO integration +> **📋 Implementation Status**: Some advanced features are currently skipped pending platform-specific implementations. See [`POSIX_IMPLEMENTATION_ROADMAP.md`](./POSIX_IMPLEMENTATION_ROADMAP.md) for a detailed implementation plan and current status. + ### 📊 **Performance Analysis** - **Benchmarking**: Built-in performance benchmarks - **Profiling**: CPU and memory profiling support diff --git a/test/fuse_integration/POSIX_IMPLEMENTATION_ROADMAP.md b/test/fuse_integration/POSIX_IMPLEMENTATION_ROADMAP.md new file mode 100644 index 000000000..59d6430e8 --- /dev/null +++ b/test/fuse_integration/POSIX_IMPLEMENTATION_ROADMAP.md @@ -0,0 +1,271 @@ +# POSIX Compliance Implementation Roadmap + +This document tracks the implementation status of POSIX features in the SeaweedFS FUSE mount compliance test suite and provides a roadmap for completing comprehensive POSIX compliance testing. + +## Overview + +The POSIX compliance test suite currently has several tests that are skipped due to requiring platform-specific implementations. This roadmap outlines the steps needed to implement these tests and achieve comprehensive POSIX compliance validation. + +## Current Status + +### ✅ Implemented Features +- Basic file operations (create, read, write, delete) +- Directory operations (mkdir, rmdir, rename) +- File permissions and ownership +- Symbolic and hard links +- Basic I/O operations (seek, append, positioned I/O) +- File descriptors and atomic operations +- Concurrent access patterns +- Error handling compliance +- Timestamp operations + +### 🚧 Partially Implemented +- Cross-platform compatibility (basic implementation with platform-specific access time handling) + +### ❌ Missing Implementations + +#### 1. Extended Attributes (xattr) +**Priority: High** +**Platforms: Linux, macOS, FreeBSD** + +**Current Status:** All xattr tests are skipped +- `TestExtendedAttributes/SetExtendedAttribute` +- `TestExtendedAttributes/ListExtendedAttributes` +- `TestExtendedAttributes/RemoveExtendedAttribute` + +**Implementation Plan:** +```go +// Linux implementation +//go:build linux +func setXattr(path, name string, value []byte) error { + return syscall.Setxattr(path, name, value, 0) +} + +// macOS implementation +//go:build darwin +func setXattr(path, name string, value []byte) error { + return syscall.Setxattr(path, name, value, 0, 0) +} +``` + +**Required Files:** +- `xattr_linux.go` - Linux syscall implementations +- `xattr_darwin.go` - macOS syscall implementations +- `xattr_freebsd.go` - FreeBSD syscall implementations +- `xattr_unsupported.go` - Fallback for unsupported platforms + +#### 2. Memory Mapping (mmap) +**Priority: High** +**Platforms: All POSIX systems** + +**Current Status:** All mmap tests are skipped +- `TestMemoryMapping/MemoryMappedRead` +- `TestMemoryMapping/MemoryMappedWrite` + +**Implementation Plan:** +```go +func testMmap(t *testing.T, filePath string) { + fd, err := syscall.Open(filePath, syscall.O_RDWR, 0) + require.NoError(t, err) + defer syscall.Close(fd) + + // Platform-specific mmap implementation + data, err := syscall.Mmap(fd, 0, size, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED) + require.NoError(t, err) + defer syscall.Munmap(data) +} +``` + +**Required Files:** +- `mmap_unix.go` - Unix-like systems implementation +- `mmap_windows.go` - Windows implementation (if needed) + +#### 3. Vectored I/O (readv/writev) +**Priority: Medium** +**Platforms: All POSIX systems** + +**Current Status:** Vectored I/O test is skipped +- `TestAdvancedIO/VectoredIO` + +**Implementation Plan:** +```go +func testVectoredIO(t *testing.T, filePath string) { + // Use syscall.Syscall for readv/writev + // Linux: SYS_READV, SYS_WRITEV + // Other platforms: platform-specific syscall numbers +} +``` + +#### 4. Direct I/O +**Priority: Medium** +**Platforms: Linux, some Unix variants** + +**Current Status:** Direct I/O test is skipped +- `TestDirectIO/DirectIO` + +**Implementation Plan:** +```go +//go:build linux +func testDirectIO(t *testing.T, filePath string) { + fd, err := syscall.Open(filePath, syscall.O_RDWR|syscall.O_DIRECT, 0) + // Test with aligned buffers +} +``` + +#### 5. File Preallocation (fallocate) +**Priority: Medium** +**Platforms: Linux, some Unix variants** + +**Current Status:** fallocate test is skipped +- `TestFilePreallocation/Fallocate` + +**Implementation Plan:** +```go +//go:build linux +func testFallocate(t *testing.T, filePath string) { + fd, err := syscall.Open(filePath, syscall.O_RDWR, 0) + err = syscall.Syscall6(syscall.SYS_FALLOCATE, uintptr(fd), 0, 0, uintptr(size), 0, 0) +} +``` + +#### 6. Zero-Copy Transfer (sendfile) +**Priority: Low** +**Platforms: Linux, FreeBSD, some Unix variants** + +**Current Status:** sendfile test is skipped +- `TestZeroCopyTransfer/Sendfile` + +**Implementation Plan:** +```go +//go:build linux +func testSendfile(t *testing.T, srcPath, dstPath string) { + // Use syscall.Syscall for sendfile + // Platform-specific implementations +} +``` + +#### 7. File Sealing (Linux-specific) +**Priority: Low** +**Platforms: Linux only** + +**Current Status:** File sealing test is skipped +- `TestFileSealing` + +**Implementation Plan:** +```go +//go:build linux +func testFileSealing(t *testing.T) { + // Use fcntl with F_ADD_SEALS, F_GET_SEALS + // Test various seal types: F_SEAL_WRITE, F_SEAL_SHRINK, etc. +} +``` + +## Implementation Strategy + +### Phase 1: Core Features (High Priority) +1. **Extended Attributes** - Essential for many applications +2. **Memory Mapping** - Critical for performance-sensitive applications + +### Phase 2: Advanced I/O (Medium Priority) +3. **Vectored I/O** - Important for efficient bulk operations +4. **Direct I/O** - Needed for database and high-performance applications +5. **File Preallocation** - Important for preventing fragmentation + +### Phase 3: Specialized Features (Low Priority) +6. **Zero-Copy Transfer** - Performance optimization feature +7. **File Sealing** - Security feature, Linux-specific + +## Platform Support Matrix + +| Feature | Linux | macOS | FreeBSD | Windows | Notes | +|---------|-------|-------|---------|---------|-------| +| Extended Attributes | ✅ | ✅ | ✅ | ❌ | Different syscall signatures | +| Memory Mapping | ✅ | ✅ | ✅ | ✅ | Standard POSIX | +| Vectored I/O | ✅ | ✅ | ✅ | ❌ | readv/writev syscalls | +| Direct I/O | ✅ | ❌ | ✅ | ❌ | O_DIRECT flag | +| fallocate | ✅ | ❌ | ❌ | ❌ | Linux-specific | +| sendfile | ✅ | ❌ | ✅ | ❌ | Platform-specific | +| File Sealing | ✅ | ❌ | ❌ | ❌ | Linux-only | + +## Development Guidelines + +### 1. Platform-Specific Implementation Pattern +```go +// feature_linux.go +//go:build linux +package fuse +func platformSpecificFunction() { /* Linux implementation */ } + +// feature_darwin.go +//go:build darwin +package fuse +func platformSpecificFunction() { /* macOS implementation */ } + +// feature_unsupported.go +//go:build !linux && !darwin +package fuse +func platformSpecificFunction() { /* Skip or error */ } +``` + +### 2. Test Structure Pattern +```go +func (s *POSIXExtendedTestSuite) TestFeature(t *testing.T) { + if !isFeatureSupported() { + t.Skip("Feature not supported on this platform") + return + } + + t.Run("FeatureTest", func(t *testing.T) { + // Actual test implementation + }) +} +``` + +### 3. Error Handling +- Use platform-specific error checking +- Provide meaningful error messages +- Gracefully handle unsupported features + +## Testing Strategy + +### 1. Continuous Integration +- Run tests on multiple platforms (Linux, macOS) +- Use build tags to enable/disable platform-specific tests +- Ensure graceful degradation on unsupported platforms + +### 2. Feature Detection +- Implement runtime feature detection where possible +- Skip tests gracefully when features are unavailable +- Log clear messages about skipped functionality + +### 3. Documentation +- Document platform-specific behavior +- Provide examples for each implemented feature +- Maintain compatibility matrices + +## Contributing + +When implementing these features: + +1. **Start with high-priority items** (Extended Attributes, Memory Mapping) +2. **Follow the platform-specific pattern** outlined above +3. **Add comprehensive tests** for each feature +4. **Update this roadmap** as features are implemented +5. **Document any platform-specific quirks** or limitations + +## Future Enhancements + +Beyond the current roadmap, consider: +- **File locking** (flock, fcntl locks) +- **Asynchronous I/O** (aio_read, aio_write) +- **File change notifications** (inotify, kqueue) +- **POSIX ACLs** (Access Control Lists) +- **Sparse file operations** +- **File hole punching** + +## References + +- [POSIX.1-2017 Standard](https://pubs.opengroup.org/onlinepubs/9699919799/) +- [Linux Programmer's Manual](https://man7.org/linux/man-pages/) +- [FreeBSD System Calls](https://www.freebsd.org/cgi/man.cgi) +- [macOS System Calls](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/) diff --git a/test/fuse_integration/atime_nonlinux.go b/test/fuse_integration/atime_nonlinux.go index 6bafa4cd4..c3b7cda16 100644 --- a/test/fuse_integration/atime_nonlinux.go +++ b/test/fuse_integration/atime_nonlinux.go @@ -1,4 +1,4 @@ -//go:build !linux +//go:build !linux && !windows package fuse diff --git a/test/fuse_integration/atime_windows.go b/test/fuse_integration/atime_windows.go new file mode 100644 index 000000000..4b8a7d9c6 --- /dev/null +++ b/test/fuse_integration/atime_windows.go @@ -0,0 +1,12 @@ +//go:build windows + +package fuse + +import ( + "syscall" +) + +// getAtimeNano returns the access time in nanoseconds for Windows systems +func getAtimeNano(stat *syscall.Win32FileAttributeData) int64 { + return stat.LastAccessTime.Nanoseconds() +} diff --git a/test/fuse_integration/directio_darwin.go b/test/fuse_integration/directio_darwin.go new file mode 100644 index 000000000..67ccaf9a0 --- /dev/null +++ b/test/fuse_integration/directio_darwin.go @@ -0,0 +1,39 @@ +//go:build darwin + +package fuse + +import ( + "syscall" +) + +// Direct I/O support for macOS + +const ( + // macOS doesn't have O_DIRECT, but we can use fcntl with F_NOCACHE + F_NOCACHE = 48 +) + +func openDirectIO(path string, flags int, mode uint32) (int, error) { + // Open file normally first + fd, err := syscall.Open(path, flags, mode) + if err != nil { + return -1, err + } + + // Set F_NOCACHE to bypass buffer cache (similar to O_DIRECT) + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, + uintptr(fd), + F_NOCACHE, + 1) // enable + + if errno != 0 { + syscall.Close(fd) + return -1, errno + } + + return fd, nil +} + +func isDirectIOSupported() bool { + return true +} diff --git a/test/fuse_integration/directio_linux.go b/test/fuse_integration/directio_linux.go new file mode 100644 index 000000000..862146f9d --- /dev/null +++ b/test/fuse_integration/directio_linux.go @@ -0,0 +1,21 @@ +//go:build linux + +package fuse + +import ( + "syscall" +) + +// Direct I/O support for Linux + +const ( + O_DIRECT = 0x4000 // Direct I/O flag for Linux +) + +func openDirectIO(path string, flags int, mode uint32) (int, error) { + return syscall.Open(path, flags|O_DIRECT, mode) +} + +func isDirectIOSupported() bool { + return true +} diff --git a/test/fuse_integration/directio_unsupported.go b/test/fuse_integration/directio_unsupported.go new file mode 100644 index 000000000..dc1168b1c --- /dev/null +++ b/test/fuse_integration/directio_unsupported.go @@ -0,0 +1,29 @@ +//go:build !linux && !darwin + +package fuse + +import ( + "errors" + "syscall" +) + +// Direct I/O support for unsupported platforms + +var ErrDirectIONotSupported = errors.New("direct I/O not supported on this platform") + +const ( + O_DIRECT = 0x4000 // Dummy flag for compatibility +) + +func openDirectIO(path string, flags int, mode uint32) (int, error) { + // Fall back to regular open + fd, err := syscall.Open(path, flags, mode) + if err != nil { + return -1, err + } + return int(fd), nil +} + +func isDirectIOSupported() bool { + return false +} diff --git a/test/fuse_integration/fallocate_darwin.go b/test/fuse_integration/fallocate_darwin.go new file mode 100644 index 000000000..94f5de7b9 --- /dev/null +++ b/test/fuse_integration/fallocate_darwin.go @@ -0,0 +1,73 @@ +//go:build darwin + +package fuse + +import ( + "syscall" + "unsafe" +) + +// File allocation support for macOS using fcntl + +const ( + // macOS doesn't have fallocate, but we can use fcntl with F_PREALLOCATE + F_ALLOCATECONTIG = 0x02 + F_ALLOCATEALL = 0x04 + + // Dummy flags for compatibility + FALLOC_FL_KEEP_SIZE = 0x01 + FALLOC_FL_PUNCH_HOLE = 0x02 + FALLOC_FL_NO_HIDE_STALE = 0x04 + FALLOC_FL_COLLAPSE_RANGE = 0x08 + FALLOC_FL_ZERO_RANGE = 0x10 + FALLOC_FL_INSERT_RANGE = 0x20 + FALLOC_FL_UNSHARE_RANGE = 0x40 +) + +// fstore_t structure for F_PREALLOCATE +type fstore struct { + flags uint32 + posmode int16 + offset int64 + length int64 + bytesalloc int64 +} + +func fallocateFile(fd int, mode int, offset int64, length int64) error { + // On macOS, we use fcntl with F_PREALLOCATE + store := fstore{ + flags: F_ALLOCATECONTIG, + posmode: syscall.F_PEOFPOSMODE, // Allocate from EOF + offset: 0, + length: length, + } + + _, _, errno := syscall.Syscall(syscall.SYS_FCNTL, + uintptr(fd), + syscall.F_PREALLOCATE, + uintptr(unsafe.Pointer(&store))) + + if errno != 0 { + // If contiguous allocation fails, try non-contiguous + store.flags = F_ALLOCATEALL + _, _, errno = syscall.Syscall(syscall.SYS_FCNTL, + uintptr(fd), + syscall.F_PREALLOCATE, + uintptr(unsafe.Pointer(&store))) + + if errno != 0 { + return errno + } + } + + // Set file size if not keeping size + if mode&FALLOC_FL_KEEP_SIZE == 0 { + return syscall.Ftruncate(fd, offset+length) + } + + return nil +} + +func isFallocateSupported() bool { + return true +} diff --git a/test/fuse_integration/fallocate_linux.go b/test/fuse_integration/fallocate_linux.go new file mode 100644 index 000000000..7b1feaa4e --- /dev/null +++ b/test/fuse_integration/fallocate_linux.go @@ -0,0 +1,38 @@ +//go:build linux + +package fuse + +import ( + "syscall" +) + +// File allocation support for Linux + +const ( + // Fallocate flags + FALLOC_FL_KEEP_SIZE = 0x01 + FALLOC_FL_PUNCH_HOLE = 0x02 + FALLOC_FL_NO_HIDE_STALE = 0x04 + FALLOC_FL_COLLAPSE_RANGE = 0x08 + FALLOC_FL_ZERO_RANGE = 0x10 + FALLOC_FL_INSERT_RANGE = 0x20 + FALLOC_FL_UNSHARE_RANGE = 0x40 +) + +func fallocateFile(fd int, mode int, offset int64, length int64) error { + _, _, errno := syscall.Syscall6(syscall.SYS_FALLOCATE, + uintptr(fd), + uintptr(mode), + uintptr(offset), + uintptr(length), + 0, 0) + + if errno != 0 { + return errno + } + return nil +} + +func isFallocateSupported() bool { + return true +} diff --git a/test/fuse_integration/fallocate_unsupported.go b/test/fuse_integration/fallocate_unsupported.go new file mode 100644 index 000000000..8be54785d --- /dev/null +++ b/test/fuse_integration/fallocate_unsupported.go @@ -0,0 +1,30 @@ +//go:build !linux && !darwin + +package fuse + +import ( + "errors" +) + +// File allocation support for unsupported platforms + +var ErrFallocateNotSupported = errors.New("fallocate not supported on this platform") + +const ( + // Dummy flags for compatibility + FALLOC_FL_KEEP_SIZE = 0x01 + FALLOC_FL_PUNCH_HOLE = 0x02 + FALLOC_FL_NO_HIDE_STALE = 0x04 + FALLOC_FL_COLLAPSE_RANGE = 0x08 + FALLOC_FL_ZERO_RANGE = 0x10 + FALLOC_FL_INSERT_RANGE = 0x20 + FALLOC_FL_UNSHARE_RANGE = 0x40 +) + +func fallocateFile(fd int, mode int, offset int64, length int64) error { + return ErrFallocateNotSupported +} + +func isFallocateSupported() bool { + return false +} diff --git a/test/fuse_integration/mmap_unix.go b/test/fuse_integration/mmap_unix.go new file mode 100644 index 000000000..702a1c032 --- /dev/null +++ b/test/fuse_integration/mmap_unix.go @@ -0,0 +1,85 @@ +//go:build unix + +package fuse + +import ( + "syscall" + "unsafe" +) + +// Memory mapping support for Unix-like systems + +func mmapFile(fd int, offset int64, length int, prot int, flags int) ([]byte, error) { + addr, _, errno := syscall.Syscall6(syscall.SYS_MMAP, + 0, // addr (let kernel choose) + uintptr(length), + uintptr(prot), + uintptr(flags), + uintptr(fd), + uintptr(offset)) + + if errno != 0 { + return nil, errno + } + + // Convert the address to a byte slice + return (*[1 << 30]byte)(unsafe.Pointer(addr))[:length:length], nil +} + +func munmapFile(data []byte) error { + if len(data) == 0 { + return nil + } + + _, _, errno := syscall.Syscall(syscall.SYS_MUNMAP, + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + 0) + + if errno != 0 { + return errno + } + return nil +} + +func msyncFile(data []byte, flags int) error { + if len(data) == 0 { + return nil + } + + _, _, errno := syscall.Syscall(syscall.SYS_MSYNC, + uintptr(unsafe.Pointer(&data[0])), + uintptr(len(data)), + uintptr(flags)) + + if errno != 0 { + return errno + } + return nil +} + +func isMmapSupported() bool { + return true +} + +// Memory protection flags +const ( + PROT_READ = syscall.PROT_READ + PROT_WRITE = syscall.PROT_WRITE + PROT_EXEC = syscall.PROT_EXEC + PROT_NONE = syscall.PROT_NONE +) + +// Memory mapping flags +const ( + MAP_SHARED = syscall.MAP_SHARED + MAP_PRIVATE = syscall.MAP_PRIVATE + MAP_ANONYMOUS = syscall.MAP_ANON +) + +// Memory sync flags +const ( + MS_ASYNC = syscall.MS_ASYNC + MS_SYNC = syscall.MS_SYNC + MS_INVALIDATE = syscall.MS_INVALIDATE +) diff --git a/test/fuse_integration/mmap_unsupported.go b/test/fuse_integration/mmap_unsupported.go new file mode 100644 index 000000000..082f9333c --- /dev/null +++ b/test/fuse_integration/mmap_unsupported.go @@ -0,0 +1,47 @@ +//go:build !unix + +package fuse + +import ( + "errors" +) + +// Memory mapping support for unsupported platforms + +var ErrMmapNotSupported = errors.New("memory mapping not supported on this platform") + +func mmapFile(fd int, offset int64, length int, prot int, flags int) ([]byte, error) { + return nil, ErrMmapNotSupported +} + +func munmapFile(data []byte) error { + return ErrMmapNotSupported +} + +func msyncFile(data []byte, flags int) error { + return ErrMmapNotSupported +} + +func isMmapSupported() bool { + return false +} + +// Dummy constants for unsupported platforms +const ( + PROT_READ = 0x1 + PROT_WRITE = 0x2 + PROT_EXEC = 0x4 + PROT_NONE = 0x0 +) + +const ( + MAP_SHARED = 0x01 + MAP_PRIVATE = 0x02 + MAP_ANONYMOUS = 0x20 +) + +const ( + MS_ASYNC = 0x1 + MS_SYNC = 0x4 + MS_INVALIDATE = 0x2 +) diff --git a/test/fuse_integration/posix_compliance_test.go b/test/fuse_integration/posix_compliance_test.go index b6068c76a..bb3f2a0a1 100644 --- a/test/fuse_integration/posix_compliance_test.go +++ b/test/fuse_integration/posix_compliance_test.go @@ -1,3 +1,5 @@ +//go:build !windows + package fuse import ( diff --git a/test/fuse_integration/posix_extended_test.go b/test/fuse_integration/posix_extended_test.go index 29326f99b..76f3f115d 100644 --- a/test/fuse_integration/posix_extended_test.go +++ b/test/fuse_integration/posix_extended_test.go @@ -1,3 +1,5 @@ +//go:build !windows + package fuse import ( @@ -10,7 +12,11 @@ import ( ) // POSIXExtendedTestSuite provides additional POSIX compliance tests -// covering extended attributes, file locking, and advanced features +// covering extended attributes, file locking, and advanced features. +// +// NOTE: Many tests in this suite are currently skipped due to requiring +// platform-specific implementations. See POSIX_IMPLEMENTATION_ROADMAP.md +// for a comprehensive plan to implement these missing features. type POSIXExtendedTestSuite struct { framework *FuseTestFramework t *testing.T @@ -54,36 +60,99 @@ func (s *POSIXExtendedTestSuite) TestExtendedAttributes(t *testing.T) { mountPoint := s.framework.GetMountPoint() t.Run("SetAndGetXattr", func(t *testing.T) { + if !isXattrSupported() { + t.Skip("Extended attributes not supported on this platform") + return + } + testFile := filepath.Join(mountPoint, "xattr_test.txt") // Create test file err := os.WriteFile(testFile, []byte("xattr test"), 0644) require.NoError(t, err) - // Extended attributes test - platform dependent - t.Skip("Extended attributes testing requires platform-specific implementation") + // Set extended attribute + attrName := "user.test_attr" + attrValue := []byte("test_value") + err = setXattr(testFile, attrName, attrValue, 0) + require.NoError(t, err) + + // Verify attribute was set + readValue := make([]byte, 256) + size, err := getXattr(testFile, attrName, readValue) + require.NoError(t, err) + require.Equal(t, len(attrValue), size) + require.Equal(t, attrValue, readValue[:size]) }) t.Run("ListXattrs", func(t *testing.T) { + if !isXattrSupported() { + t.Skip("Extended attributes not supported on this platform") + return + } + testFile := filepath.Join(mountPoint, "xattr_list_test.txt") // Create test file err := os.WriteFile(testFile, []byte("list xattr test"), 0644) require.NoError(t, err) - // List extended attributes test - platform dependent - t.Skip("Extended attributes testing requires platform-specific implementation") + // Set multiple extended attributes + attrs := map[string][]byte{ + "user.attr1": []byte("value1"), + "user.attr2": []byte("value2"), + "user.attr3": []byte("value3"), + } + + for name, value := range attrs { + err = setXattr(testFile, name, value, 0) + require.NoError(t, err) + } + + // List all attributes + listBuf := make([]byte, 1024) + size, err := listXattr(testFile, listBuf) + require.NoError(t, err) + require.Greater(t, size, 0) + + // Parse the null-separated list + attrList := string(listBuf[:size]) + for name := range attrs { + require.Contains(t, attrList, name) + } }) t.Run("RemoveXattr", func(t *testing.T) { + if !isXattrSupported() { + t.Skip("Extended attributes not supported on this platform") + return + } + testFile := filepath.Join(mountPoint, "xattr_remove_test.txt") // Create test file err := os.WriteFile(testFile, []byte("remove xattr test"), 0644) require.NoError(t, err) - // Remove extended attributes test - platform dependent - t.Skip("Extended attributes testing requires platform-specific implementation") + // Set extended attribute + attrName := "user.remove_test" + attrValue := []byte("to_be_removed") + err = setXattr(testFile, attrName, attrValue, 0) + require.NoError(t, err) + + // Verify attribute exists + readValue := make([]byte, 256) + size, err := getXattr(testFile, attrName, readValue) + require.NoError(t, err) + require.Equal(t, len(attrValue), size) + + // Remove the attribute + err = removeXattr(testFile, attrName) + require.NoError(t, err) + + // Verify attribute is gone + _, err = getXattr(testFile, attrName, readValue) + require.Error(t, err) // Should fail with ENODATA or similar }) } @@ -183,6 +252,11 @@ func (s *POSIXExtendedTestSuite) TestAdvancedIO(t *testing.T) { mountPoint := s.framework.GetMountPoint() t.Run("ReadWriteV", func(t *testing.T) { + if !isVectoredIOSupported() { + t.Skip("Vectored I/O not supported on this platform") + return + } + testFile := filepath.Join(mountPoint, "readwritev_test.txt") // Create file @@ -190,8 +264,46 @@ func (s *POSIXExtendedTestSuite) TestAdvancedIO(t *testing.T) { require.NoError(t, err) defer syscall.Close(fd) - // Vectored I/O test - requires platform-specific implementation - t.Skip("Vectored I/O testing requires platform-specific implementation") + // Prepare test data in multiple buffers + writeBuffers := [][]byte{ + []byte("Hello "), + []byte("vectored "), + []byte("I/O "), + []byte("world!"), + } + + // Write using writev + writeIOVs := makeIOVecs(writeBuffers) + totalWritten, err := writevFile(fd, writeIOVs) + require.NoError(t, err) + + expectedTotal := 0 + for _, buf := range writeBuffers { + expectedTotal += len(buf) + } + require.Equal(t, expectedTotal, totalWritten) + + // Seek back to beginning + _, err = syscall.Seek(fd, 0, 0) + require.NoError(t, err) + + // Read using readv into multiple buffers + readBuffers := [][]byte{ + make([]byte, 6), // "Hello " + make([]byte, 9), // "vectored " + make([]byte, 3), // "I/O " + make([]byte, 6), // "world!" + } + + readIOVs := makeIOVecs(readBuffers) + totalRead, err := readvFile(fd, readIOVs) + require.NoError(t, err) + require.Equal(t, expectedTotal, totalRead) + + // Verify data matches + for i, expected := range writeBuffers { + require.Equal(t, expected, readBuffers[i]) + } }) } @@ -281,6 +393,11 @@ func (s *POSIXExtendedTestSuite) TestMemoryMapping(t *testing.T) { mountPoint := s.framework.GetMountPoint() t.Run("MmapFile", func(t *testing.T) { + if !isMmapSupported() { + t.Skip("Memory mapping not supported on this platform") + return + } + testFile := filepath.Join(mountPoint, "mmap_test.txt") testData := make([]byte, 4096) for i := range testData { @@ -291,38 +408,98 @@ func (s *POSIXExtendedTestSuite) TestMemoryMapping(t *testing.T) { err := os.WriteFile(testFile, testData, 0644) require.NoError(t, err) - // Open file - file, err := os.Open(testFile) + // Open file for reading + fd, err := syscall.Open(testFile, syscall.O_RDONLY, 0) require.NoError(t, err) - defer file.Close() + defer syscall.Close(fd) + + // Memory map the file + mappedData, err := mmapFile(fd, 0, len(testData), PROT_READ, MAP_SHARED) + require.NoError(t, err) + defer munmapFile(mappedData) - // Memory mapping test - requires platform-specific implementation - t.Skip("Memory mapping testing requires platform-specific implementation") + // Verify mapped content matches original + require.Equal(t, testData, mappedData) }) t.Run("MmapWrite", func(t *testing.T) { + if !isMmapSupported() { + t.Skip("Memory mapping not supported on this platform") + return + } + testFile := filepath.Join(mountPoint, "mmap_write_test.txt") size := 4096 // Create empty file of specific size fd, err := syscall.Open(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644) require.NoError(t, err) + defer syscall.Close(fd) err = syscall.Ftruncate(fd, int64(size)) require.NoError(t, err) - syscall.Close(fd) + // Memory map the file for writing + mappedData, err := mmapFile(fd, 0, size, PROT_READ|PROT_WRITE, MAP_SHARED) + require.NoError(t, err) + defer munmapFile(mappedData) + + // Write test pattern to mapped memory + testPattern := []byte("Hello, mmap world!") + copy(mappedData, testPattern) - // Memory mapping write test - requires platform-specific implementation - t.Skip("Memory mapping testing requires platform-specific implementation") + // Sync changes to disk + err = msyncFile(mappedData, MS_SYNC) + require.NoError(t, err) + + // Verify changes were written by reading file directly + readData, err := os.ReadFile(testFile) + require.NoError(t, err) + require.True(t, len(readData) >= len(testPattern)) + require.Equal(t, testPattern, readData[:len(testPattern)]) }) } // TestDirectIO tests direct I/O operations func (s *POSIXExtendedTestSuite) TestDirectIO(t *testing.T) { + mountPoint := s.framework.GetMountPoint() + t.Run("DirectIO", func(t *testing.T) { - // Direct I/O is platform dependent and may not be supported - t.Skip("Direct I/O testing requires platform-specific implementation") + if !isDirectIOSupported() { + t.Skip("Direct I/O not supported on this platform") + return + } + + testFile := filepath.Join(mountPoint, "directio_test.txt") + + // Open file with direct I/O + fd, err := openDirectIO(testFile, syscall.O_CREAT|syscall.O_RDWR, 0644) + require.NoError(t, err) + defer syscall.Close(fd) + + // For direct I/O, data must be aligned to sector boundaries + // Use 4KB aligned buffer (common sector size) + blockSize := 4096 + testData := make([]byte, blockSize) + for i := range testData { + testData[i] = byte(i % 256) + } + + // Write data using direct I/O + n, err := syscall.Write(fd, testData) + require.NoError(t, err) + require.Equal(t, len(testData), n) + + // Seek back to beginning + _, err = syscall.Seek(fd, 0, 0) + require.NoError(t, err) + + // Read data using direct I/O + readData := make([]byte, blockSize) + n, err = syscall.Read(fd, readData) + require.NoError(t, err) + require.Equal(t, len(testData), n) + require.Equal(t, testData, readData) }) } @@ -337,6 +514,11 @@ func (s *POSIXExtendedTestSuite) TestFallocate(t *testing.T) { mountPoint := s.framework.GetMountPoint() t.Run("FallocateSpace", func(t *testing.T) { + if !isFallocateSupported() { + t.Skip("fallocate not supported on this platform") + return + } + testFile := filepath.Join(mountPoint, "fallocate_test.txt") // Open file @@ -344,8 +526,22 @@ func (s *POSIXExtendedTestSuite) TestFallocate(t *testing.T) { require.NoError(t, err) defer syscall.Close(fd) - // File preallocation test - requires platform-specific implementation - t.Skip("fallocate testing requires platform-specific implementation") + // Preallocate 1MB of space + allocSize := int64(1024 * 1024) + err = fallocateFile(fd, 0, 0, allocSize) + require.NoError(t, err) + + // Verify file size was extended + var stat syscall.Stat_t + err = syscall.Fstat(fd, &stat) + require.NoError(t, err) + require.GreaterOrEqual(t, stat.Size, allocSize) + + // Write some data and verify it works + testData := []byte("fallocate test data") + n, err := syscall.Write(fd, testData) + require.NoError(t, err) + require.Equal(t, len(testData), n) }) } @@ -376,8 +572,17 @@ func (s *POSIXExtendedTestSuite) TestSendfile(t *testing.T) { require.NoError(t, err) defer syscall.Close(dstFd) - // Sendfile test - requires platform-specific implementation - t.Skip("sendfile testing requires platform-specific implementation") + // Sendfile test + if !isSendfileSupported() { + t.Skip("sendfile not supported on this platform") + return + } + + // Use sendfile to copy data + var offset int64 = 0 + transferred, err := sendfileTransfer(dstFd, srcFd, &offset, len(testData)) + require.NoError(t, err) + require.Equal(t, len(testData), transferred) // Verify copy copiedData, err := os.ReadFile(targetFile) diff --git a/test/fuse_integration/sendfile_darwin.go b/test/fuse_integration/sendfile_darwin.go new file mode 100644 index 000000000..38551bc6e --- /dev/null +++ b/test/fuse_integration/sendfile_darwin.go @@ -0,0 +1,43 @@ +//go:build darwin + +package fuse + +import ( + "syscall" + "unsafe" +) + +// Sendfile support for macOS + +func sendfileTransfer(outFd int, inFd int, offset *int64, count int) (int, error) { + // macOS sendfile has different signature: sendfile(in_fd, out_fd, offset, len, hdtr, flags) + var off int64 + if offset != nil { + off = *offset + } + + length := int64(count) + + _, _, errno := syscall.Syscall6(syscall.SYS_SENDFILE, + uintptr(inFd), // input fd + uintptr(outFd), // output fd + uintptr(off), // offset + uintptr(unsafe.Pointer(&length)), // length (in/out parameter) + 0, // hdtr (headers/trailers) + 0) // flags + + if errno != 0 { + return 0, errno + } + + // Update offset if provided + if offset != nil { + *offset += length + } + + return int(length), nil +} + +func isSendfileSupported() bool { + return true +} diff --git a/test/fuse_integration/sendfile_linux.go b/test/fuse_integration/sendfile_linux.go new file mode 100644 index 000000000..b5a0c0d61 --- /dev/null +++ b/test/fuse_integration/sendfile_linux.go @@ -0,0 +1,33 @@ +//go:build linux + +package fuse + +import ( + "syscall" + "unsafe" +) + +// Sendfile support for Linux + +func sendfileTransfer(outFd int, inFd int, offset *int64, count int) (int, error) { + var offsetPtr uintptr + if offset != nil { + offsetPtr = uintptr(unsafe.Pointer(offset)) + } + + n, _, errno := syscall.Syscall6(syscall.SYS_SENDFILE, + uintptr(outFd), + uintptr(inFd), + offsetPtr, + uintptr(count), + 0, 0) + + if errno != 0 { + return 0, errno + } + return int(n), nil +} + +func isSendfileSupported() bool { + return true +} diff --git a/test/fuse_integration/sendfile_unsupported.go b/test/fuse_integration/sendfile_unsupported.go new file mode 100644 index 000000000..64a7818dd --- /dev/null +++ b/test/fuse_integration/sendfile_unsupported.go @@ -0,0 +1,19 @@ +//go:build !linux && !darwin + +package fuse + +import ( + "errors" +) + +// Sendfile support for unsupported platforms + +var ErrSendfileNotSupported = errors.New("sendfile not supported on this platform") + +func sendfileTransfer(outFd int, inFd int, offset *int64, count int) (int, error) { + return 0, ErrSendfileNotSupported +} + +func isSendfileSupported() bool { + return false +} diff --git a/test/fuse_integration/vectored_io_unix.go b/test/fuse_integration/vectored_io_unix.go new file mode 100644 index 000000000..59b2b2cc4 --- /dev/null +++ b/test/fuse_integration/vectored_io_unix.go @@ -0,0 +1,126 @@ +//go:build unix + +package fuse + +import ( + "syscall" + "unsafe" +) + +// Vectored I/O support for Unix-like systems + +// IOVec represents an I/O vector for readv/writev operations +type IOVec struct { + Base *byte + Len uint64 +} + +func readvFile(fd int, iovs []IOVec) (int, error) { + if len(iovs) == 0 { + return 0, nil + } + + n, _, errno := syscall.Syscall(syscall.SYS_READV, + uintptr(fd), + uintptr(unsafe.Pointer(&iovs[0])), + uintptr(len(iovs))) + + if errno != 0 { + return 0, errno + } + return int(n), nil +} + +func writevFile(fd int, iovs []IOVec) (int, error) { + if len(iovs) == 0 { + return 0, nil + } + + n, _, errno := syscall.Syscall(syscall.SYS_WRITEV, + uintptr(fd), + uintptr(unsafe.Pointer(&iovs[0])), + uintptr(len(iovs))) + + if errno != 0 { + return 0, errno + } + return int(n), nil +} + +func preadvFile(fd int, iovs []IOVec, offset int64) (int, error) { + if len(iovs) == 0 { + return 0, nil + } + + // preadv/pwritev may not be available on all Unix systems + // Fall back to individual pread calls + totalRead := 0 + currentOffset := offset + + for _, iov := range iovs { + if iov.Len == 0 { + continue + } + + buf := (*[1 << 30]byte)(unsafe.Pointer(iov.Base))[:iov.Len:iov.Len] + n, err := syscall.Pread(fd, buf, currentOffset) + if err != nil { + return totalRead, err + } + totalRead += n + currentOffset += int64(n) + + if n < int(iov.Len) { + break // EOF or partial read + } + } + + return totalRead, nil +} + +func pwritevFile(fd int, iovs []IOVec, offset int64) (int, error) { + if len(iovs) == 0 { + return 0, nil + } + + // preadv/pwritev may not be available on all Unix systems + // Fall back to individual pwrite calls + totalWritten := 0 + currentOffset := offset + + for _, iov := range iovs { + if iov.Len == 0 { + continue + } + + buf := (*[1 << 30]byte)(unsafe.Pointer(iov.Base))[:iov.Len:iov.Len] + n, err := syscall.Pwrite(fd, buf, currentOffset) + if err != nil { + return totalWritten, err + } + totalWritten += n + currentOffset += int64(n) + + if n < int(iov.Len) { + break // Partial write + } + } + + return totalWritten, nil +} + +// Helper function to create IOVec from byte slices +func makeIOVecs(buffers [][]byte) []IOVec { + iovs := make([]IOVec, len(buffers)) + for i, buf := range buffers { + if len(buf) > 0 { + iovs[i].Base = &buf[0] + iovs[i].Len = uint64(len(buf)) + } + } + return iovs +} + +func isVectoredIOSupported() bool { + return true +} diff --git a/test/fuse_integration/vectored_io_unsupported.go b/test/fuse_integration/vectored_io_unsupported.go new file mode 100644 index 000000000..963c6e2d4 --- /dev/null +++ b/test/fuse_integration/vectored_io_unsupported.go @@ -0,0 +1,41 @@ +//go:build !unix + +package fuse + +import ( + "errors" +) + +// Vectored I/O support for unsupported platforms + +var ErrVectoredIONotSupported = errors.New("vectored I/O not supported on this platform") + +// IOVec represents an I/O vector for readv/writev operations +type IOVec struct { + Base *byte + Len uint64 +} + +func readvFile(fd int, iovs []IOVec) (int, error) { + return 0, ErrVectoredIONotSupported +} + +func writevFile(fd int, iovs []IOVec) (int, error) { + return 0, ErrVectoredIONotSupported +} + +func preadvFile(fd int, iovs []IOVec, offset int64) (int, error) { + return 0, ErrVectoredIONotSupported +} + +func pwritevFile(fd int, iovs []IOVec, offset int64) (int, error) { + return 0, ErrVectoredIONotSupported +} + +func makeIOVecs(buffers [][]byte) []IOVec { + return nil +} + +func isVectoredIOSupported() bool { + return false +} diff --git a/test/fuse_integration/xattr_darwin.go b/test/fuse_integration/xattr_darwin.go new file mode 100644 index 000000000..6bd6f2a90 --- /dev/null +++ b/test/fuse_integration/xattr_darwin.go @@ -0,0 +1,125 @@ +//go:build darwin + +package fuse + +import ( + "syscall" + "unsafe" +) + +// Extended attributes support for macOS + +const ( + // macOS-specific flags + XATTR_NOFOLLOW = 0x0001 + XATTR_CREATE = 0x0002 + XATTR_REPLACE = 0x0004 +) + +func setXattr(path, name string, value []byte, flags int) error { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + nameBytes, err := syscall.BytePtrFromString(name) + if err != nil { + return err + } + + var valuePtr unsafe.Pointer + if len(value) > 0 { + valuePtr = unsafe.Pointer(&value[0]) + } + + _, _, errno := syscall.Syscall6(syscall.SYS_SETXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(unsafe.Pointer(nameBytes)), + uintptr(valuePtr), + uintptr(len(value)), + uintptr(0), // position (not used for regular files) + uintptr(flags)) + + if errno != 0 { + return errno + } + return nil +} + +func getXattr(path, name string, value []byte) (int, error) { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + nameBytes, err := syscall.BytePtrFromString(name) + if err != nil { + return 0, err + } + + var valuePtr unsafe.Pointer + if len(value) > 0 { + valuePtr = unsafe.Pointer(&value[0]) + } + + size, _, errno := syscall.Syscall6(syscall.SYS_GETXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(unsafe.Pointer(nameBytes)), + uintptr(valuePtr), + uintptr(len(value)), + uintptr(0), // position + uintptr(0)) // options + + if errno != 0 { + return 0, errno + } + return int(size), nil +} + +func listXattr(path string, list []byte) (int, error) { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + + var listPtr unsafe.Pointer + if len(list) > 0 { + listPtr = unsafe.Pointer(&list[0]) + } + + size, _, errno := syscall.Syscall6(syscall.SYS_LISTXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(listPtr), + uintptr(len(list)), + uintptr(0), // options + 0, 0) + + if errno != 0 { + return 0, errno + } + return int(size), nil +} + +func removeXattr(path, name string) error { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + nameBytes, err := syscall.BytePtrFromString(name) + if err != nil { + return err + } + + _, _, errno := syscall.Syscall6(syscall.SYS_REMOVEXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(unsafe.Pointer(nameBytes)), + uintptr(0), // options + 0, 0, 0) + + if errno != 0 { + return errno + } + return nil +} + +func isXattrSupported() bool { + return true +} diff --git a/test/fuse_integration/xattr_linux.go b/test/fuse_integration/xattr_linux.go new file mode 100644 index 000000000..82f40d1c7 --- /dev/null +++ b/test/fuse_integration/xattr_linux.go @@ -0,0 +1,115 @@ +//go:build linux + +package fuse + +import ( + "syscall" + "unsafe" +) + +// Extended attributes support for Linux + +func setXattr(path, name string, value []byte, flags int) error { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + nameBytes, err := syscall.BytePtrFromString(name) + if err != nil { + return err + } + + var valuePtr unsafe.Pointer + if len(value) > 0 { + valuePtr = unsafe.Pointer(&value[0]) + } + + _, _, errno := syscall.Syscall6(syscall.SYS_SETXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(unsafe.Pointer(nameBytes)), + uintptr(valuePtr), + uintptr(len(value)), + uintptr(flags), + 0) + + if errno != 0 { + return errno + } + return nil +} + +func getXattr(path, name string, value []byte) (int, error) { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + nameBytes, err := syscall.BytePtrFromString(name) + if err != nil { + return 0, err + } + + var valuePtr unsafe.Pointer + if len(value) > 0 { + valuePtr = unsafe.Pointer(&value[0]) + } + + size, _, errno := syscall.Syscall6(syscall.SYS_GETXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(unsafe.Pointer(nameBytes)), + uintptr(valuePtr), + uintptr(len(value)), + 0, + 0) + + if errno != 0 { + return 0, errno + } + return int(size), nil +} + +func listXattr(path string, list []byte) (int, error) { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return 0, err + } + + var listPtr unsafe.Pointer + if len(list) > 0 { + listPtr = unsafe.Pointer(&list[0]) + } + + size, _, errno := syscall.Syscall(syscall.SYS_LISTXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(listPtr), + uintptr(len(list))) + + if errno != 0 { + return 0, errno + } + return int(size), nil +} + +func removeXattr(path, name string) error { + pathBytes, err := syscall.BytePtrFromString(path) + if err != nil { + return err + } + nameBytes, err := syscall.BytePtrFromString(name) + if err != nil { + return err + } + + _, _, errno := syscall.Syscall(syscall.SYS_REMOVEXATTR, + uintptr(unsafe.Pointer(pathBytes)), + uintptr(unsafe.Pointer(nameBytes)), + 0) + + if errno != 0 { + return errno + } + return nil +} + +func isXattrSupported() bool { + return true +} diff --git a/test/fuse_integration/xattr_unsupported.go b/test/fuse_integration/xattr_unsupported.go new file mode 100644 index 000000000..945fc2ff6 --- /dev/null +++ b/test/fuse_integration/xattr_unsupported.go @@ -0,0 +1,31 @@ +//go:build !linux && !darwin + +package fuse + +import ( + "errors" +) + +// Extended attributes support for unsupported platforms + +var ErrXattrNotSupported = errors.New("extended attributes not supported on this platform") + +func setXattr(path, name string, value []byte, flags int) error { + return ErrXattrNotSupported +} + +func getXattr(path, name string, value []byte) (int, error) { + return 0, ErrXattrNotSupported +} + +func listXattr(path string, list []byte) (int, error) { + return 0, ErrXattrNotSupported +} + +func removeXattr(path, name string) error { + return ErrXattrNotSupported +} + +func isXattrSupported() bool { + return false +}