mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-16 04:06:44 +00:00
mount: report . and .. from windows directories (#10556)
* mount: report . and .. from windows directories WinFsp strips the dot entries for the root itself and expects every other directory to report them, the way a real NTFS enumeration does: its dirctl test asserts a subdirectory's first two entries are "." and ".." and that a hundred files enumerate as 102 entries. Dropping them unconditionally is what fails querydir_test. The Go test that guarded the old behaviour went with it: os.File.Readdir filters dot entries itself, so it could never have observed either way. * mount: give the windows dot entries their directory type The readdir fills an attribute block only for real children, so "." and ".." arrived with a zeroed one and were reported with mode 0. Windows refuses to enumerate a directory whose first entry is not marked as a directory, which is the assertion querydir_test fails on with STATUS_OBJECT_NAME_NOT_FOUND. They now carry the type the readdir already knew. The explorer walk also names any unexpected entry rather than only counting, so a dot entry leaking through reads differently from a missing file.
This commit is contained in:
@@ -165,8 +165,13 @@ jobs:
|
||||
Write-Host "::group::explorer-style walk"
|
||||
New-Item -ItemType Directory -Force -Path S:\walk | Out-Null
|
||||
1..200 | ForEach-Object { Set-Content -Path "S:\walk\f$_.txt" -Value "line $_" }
|
||||
$count = (Get-ChildItem S:\walk | Measure-Object).Count
|
||||
if ($count -ne 200) { throw "listed $count files, expected 200" }
|
||||
$names = @(Get-ChildItem S:\walk | ForEach-Object { $_.Name })
|
||||
if ($names.Count -ne 200) {
|
||||
# Name the strays: a dot entry surfacing here is a different problem
|
||||
# from a missing or duplicated file.
|
||||
$unexpected = $names | Where-Object { $_ -notmatch '^f\d+\.txt$' }
|
||||
throw "listed $($names.Count) entries, expected 200; unexpected: $($unexpected -join ', ')"
|
||||
}
|
||||
$body = Get-Content S:\walk\f42.txt
|
||||
if ($body -ne 'line 42') { throw "unexpected content: $body" }
|
||||
Copy-Item S:\walk\f42.txt S:\walk\copy.txt
|
||||
|
||||
@@ -73,10 +73,9 @@ setfileinfo_test
|
||||
|
||||
# Directory enumeration
|
||||
# ---------------------
|
||||
# Marker-based resumption and the buffer-overflow path, neither of which the
|
||||
# hand-written suite reaches.
|
||||
querydir_test
|
||||
querydir_buffer_overflow_test
|
||||
# querydir_namelen only: checkName caps at 255 bytes while WinFsp counts 255
|
||||
# characters. The other two are expected to pass now that "." and ".." are
|
||||
# reported for non-root directories, which is what WinFsp enumerates.
|
||||
querydir_namelen_test
|
||||
|
||||
# Name length
|
||||
|
||||
@@ -270,35 +270,6 @@ func TestAwkwardNames(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Windows enumerates a directory without "." and "..", and shows whatever the
|
||||
// filesystem reports, so they must not reach it. os.ReadDir filters them, so
|
||||
// this reads the handle the way Windows tooling does.
|
||||
func TestNoDotEntriesInListing(t *testing.T) {
|
||||
dir := testRoot(t)
|
||||
for i := 0; i < 3; i++ {
|
||||
if err := os.WriteFile(filepath.Join(dir, fmt.Sprintf("f%d", i)), []byte("x"), 0644); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
}
|
||||
f, err := os.Open(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("open dir: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
t.Fatalf("readdirnames: %v", err)
|
||||
}
|
||||
for _, name := range names {
|
||||
if name == "." || name == ".." {
|
||||
t.Errorf("listing includes %q", name)
|
||||
}
|
||||
}
|
||||
if len(names) != 3 {
|
||||
t.Fatalf("listing has %d entries (%v), want 3", len(names), names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepDirectoryNesting(t *testing.T) {
|
||||
dir := testRoot(t)
|
||||
deep := dir
|
||||
|
||||
@@ -593,6 +593,7 @@ type readdirSink struct {
|
||||
names []string
|
||||
offsets []uint64
|
||||
inodes []uint64
|
||||
modes []uint32
|
||||
attrs []*fuse.EntryOut
|
||||
limit int
|
||||
|
||||
@@ -600,31 +601,20 @@ type readdirSink struct {
|
||||
// dot entries still moves the enumeration along.
|
||||
lastOffset uint64
|
||||
seen int
|
||||
|
||||
// discard absorbs the attributes of an entry that is being dropped; the
|
||||
// raw filesystem fills the block after handing it back.
|
||||
discard fuse.EntryOut
|
||||
}
|
||||
|
||||
// The kernel expects readdir to report "." and "..", but Windows enumerates a
|
||||
// directory without them and shows whatever it is given, so they are dropped
|
||||
// rather than surfaced as two extra children.
|
||||
func isDotEntry(name string) bool {
|
||||
return name == "." || name == ".."
|
||||
}
|
||||
|
||||
// WinFsp strips "." and ".." for the root directory itself and expects every
|
||||
// other directory to report them, the way a real NTFS enumeration does.
|
||||
func (s *readdirSink) AddEntry(entry fuse.DirEntry) bool {
|
||||
if len(s.names) >= s.limit {
|
||||
return false
|
||||
}
|
||||
s.seen++
|
||||
s.lastOffset = entry.Off
|
||||
if isDotEntry(entry.Name) {
|
||||
return true
|
||||
}
|
||||
s.names = append(s.names, entry.Name)
|
||||
s.offsets = append(s.offsets, entry.Off)
|
||||
s.inodes = append(s.inodes, entry.Ino)
|
||||
s.modes = append(s.modes, entry.Mode)
|
||||
s.attrs = append(s.attrs, nil)
|
||||
return true
|
||||
}
|
||||
@@ -635,13 +625,11 @@ func (s *readdirSink) AddEntryPlus(entry fuse.DirEntry) *fuse.EntryOut {
|
||||
}
|
||||
s.seen++
|
||||
s.lastOffset = entry.Off
|
||||
if isDotEntry(entry.Name) {
|
||||
return &s.discard
|
||||
}
|
||||
out := &fuse.EntryOut{}
|
||||
s.names = append(s.names, entry.Name)
|
||||
s.offsets = append(s.offsets, entry.Off)
|
||||
s.inodes = append(s.inodes, entry.Ino)
|
||||
s.modes = append(s.modes, entry.Mode)
|
||||
s.attrs = append(s.attrs, out)
|
||||
return out
|
||||
}
|
||||
@@ -676,9 +664,19 @@ func (w *WinFS) Readdir(path string, fill func(name string, stat *cgofuse.Stat_t
|
||||
for i, name := range sink.names {
|
||||
var stat cgofuse.Stat_t
|
||||
var statp *cgofuse.Stat_t
|
||||
if attr := sink.attrs[i]; attr != nil {
|
||||
if attr := sink.attrs[i]; attr != nil && attr.Attr.Mode != 0 {
|
||||
w.attrToStat(&attr.Attr, &stat)
|
||||
statp = &stat
|
||||
} else if sink.modes[i] != 0 {
|
||||
// "." and ".." are reported without attributes: the readdir
|
||||
// only fills a block for real children. Windows still needs
|
||||
// their type, and enumerating a directory whose first entry is
|
||||
// not marked as one fails outright.
|
||||
stat.Mode = sink.modes[i]
|
||||
stat.Ino = sink.inodes[i]
|
||||
stat.Nlink = 1
|
||||
stat.Uid, stat.Gid = w.uid, w.gid
|
||||
statp = &stat
|
||||
}
|
||||
if filled && !fill(name, statp, int64(sink.offsets[i])) {
|
||||
filled = false
|
||||
|
||||
Reference in New Issue
Block a user