mirror of
https://github.com/rclone/rclone.git
synced 2026-07-10 15:55:03 -04:00
cmd/mount2: fix NFS directory listings by supporting non-zero Seekdir offsets
Seekdir handled only a rewind to offset 0 and returned ENOTSUP otherwise.
The stateless kernel NFS server opens a fresh directory handle and seeks to
the last returned cookie on every readdir continuation, so any listing
spanning more than one readdir batch failed over NFS. dirStream is a
snapshot taken at Readdir time and go-fuse assigns each entry a sequential
offset, so seeking to off positions the stream at index off; off == 0 still
resets to the start, preserving the rewind/re-read behaviour.
Before: ls of a directory that spans more than one readdir batch failed
over NFS with "Unknown error 524".
After: it lists correctly.
Adds TestDirStreamSeekdir covering rewind, mid-stream resume and the EOF
clamp. The full NFS path was validated against a real Linux
nfs-kernel-server export over NFSv3, NFSv4.0 and NFSv4.2.
Fixes #9547
(cherry picked from commit 22aba81074)
This commit is contained in:
committed by
Nick Craig-Wood
parent
b9fd40d182
commit
2a6591d1cd
@@ -271,16 +271,22 @@ func (ds *dirStream) Next() (de fuse.DirEntry, errno syscall.Errno) {
|
||||
func (ds *dirStream) Close() {
|
||||
}
|
||||
|
||||
// Seekdir implements fusefs.FileSeekdirer so go-fuse can rewind the directory
|
||||
// stream when the kernel calls lseek(fd, 0, SEEK_SET) before a second getdents.
|
||||
// Without this, go-fuse returns ENOTSUP and ls returns empty on every call after
|
||||
// the first. See: https://github.com/hanwen/go-fuse/issues/549
|
||||
// Seekdir implements fusefs.FileSeekdirer so go-fuse can reposition the
|
||||
// directory stream. The kernel calls this both to rewind (lseek(fd, 0,
|
||||
// SEEK_SET) before a second getdents) and, for a kernel-NFS-exported mount,
|
||||
// to resume from a previously returned directory cookie: nfsd is stateless,
|
||||
// so it opens a fresh handle and seeks to the last offset on every readdir
|
||||
// continuation. Handling only offset 0 made go-fuse return ENOTSUP for those
|
||||
// resumes, so any listing spanning more than one readdir batch failed over NFS.
|
||||
//
|
||||
// dirStream is a snapshot taken at Readdir time and go-fuse assigns each entry
|
||||
// a sequential offset in Next() order (the entry yielded at index i carries
|
||||
// offset i+1), so repositioning to off means the next entry is the one at
|
||||
// index off. Values past the end clamp to EOF via HasNext.
|
||||
// See: https://github.com/hanwen/go-fuse/issues/549
|
||||
func (ds *dirStream) Seekdir(_ context.Context, off uint64) syscall.Errno {
|
||||
if off == 0 {
|
||||
ds.i = 0
|
||||
return 0
|
||||
}
|
||||
return syscall.ENOTSUP
|
||||
ds.i = int(off)
|
||||
return 0
|
||||
}
|
||||
|
||||
var _ fusefs.DirStream = (*dirStream)(nil)
|
||||
|
||||
45
cmd/mount2/node_test.go
Normal file
45
cmd/mount2/node_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
//go:build linux || (darwin && amd64)
|
||||
|
||||
package mount2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestDirStreamSeekdir checks that Seekdir repositions the snapshot directory
|
||||
// stream to an arbitrary offset. A kernel-NFS-exported mount needs this for
|
||||
// readdir continuation: the stateless server opens a fresh handle and seeks to
|
||||
// the last returned cookie on every batch. Offset 0 must still reset to the
|
||||
// start (the rewind / re-read case) and offsets at or past the end must clamp
|
||||
// to EOF.
|
||||
func TestDirStreamSeekdir(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// 3 real entries plus the synthesized "." and ".." => 5 entries total,
|
||||
// occupying internal indices 0..4 (go-fuse offsets 1..5). HasNext is true
|
||||
// while i < len(nodes)+2, i.e. i < 5.
|
||||
ds := &dirStream{nodes: make([]os.FileInfo, 3)}
|
||||
|
||||
for _, tc := range []struct {
|
||||
off uint64
|
||||
wantI int
|
||||
wantNext bool
|
||||
}{
|
||||
{0, 0, true}, // rewind to start (the re-read case)
|
||||
{2, 2, true}, // resume at the first real entry
|
||||
{4, 4, true}, // last entry
|
||||
{5, 5, false}, // exactly at end => EOF
|
||||
{99, 99, false}, // past end => clamped to EOF via HasNext
|
||||
} {
|
||||
if errno := ds.Seekdir(ctx, tc.off); errno != 0 {
|
||||
t.Fatalf("Seekdir(%d) returned errno %v, want 0", tc.off, errno)
|
||||
}
|
||||
if ds.i != tc.wantI {
|
||||
t.Errorf("Seekdir(%d): i = %d, want %d", tc.off, ds.i, tc.wantI)
|
||||
}
|
||||
if got := ds.HasNext(); got != tc.wantNext {
|
||||
t.Errorf("Seekdir(%d): HasNext() = %v, want %v", tc.off, got, tc.wantNext)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user