mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-19 06:31:54 +00:00
add more posix tests
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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/)
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build !linux
|
||||
//go:build !linux && !windows
|
||||
|
||||
package fuse
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package fuse
|
||||
|
||||
import (
|
||||
|
||||
@@ -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 mapping test - requires platform-specific implementation
|
||||
t.Skip("Memory mapping testing requires platform-specific implementation")
|
||||
// Memory map the file
|
||||
mappedData, err := mmapFile(fd, 0, len(testData), PROT_READ, MAP_SHARED)
|
||||
require.NoError(t, err)
|
||||
defer munmapFile(mappedData)
|
||||
|
||||
// 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)
|
||||
|
||||
// Memory mapping write test - requires platform-specific implementation
|
||||
t.Skip("Memory mapping testing requires platform-specific implementation")
|
||||
// Write test pattern to mapped memory
|
||||
testPattern := []byte("Hello, mmap world!")
|
||||
copy(mappedData, testPattern)
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user