mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
fix(mount): track directory subdirectory count for correct nlink (#9028)
Track subdirectory count per-inode in memory via InodeEntry.subdirCount. Increment on mkdir, decrement on rmdir, adjust on cross-directory rename. applyDirNlink uses this count instead of listing metacache entries, so nlink is correct immediately after mkdir without needing a prior readdir. Remove tests/rename/24.t from known_failures.txt (all 13 subtests now pass).
This commit is contained in:
@@ -6,11 +6,6 @@
|
||||
# A failure in any test NOT listed here will cause the CI job to fail,
|
||||
# catching regressions immediately.
|
||||
|
||||
# ── Directory nlink count before readdir ────────────────────────────────
|
||||
# Directory nlink = 2 + subdirectory count is only accurate after the
|
||||
# directory children have been cached (readdir). Before that, nlink=2.
|
||||
tests/rename/24.t
|
||||
|
||||
# ── Directory rename permission edge case ──────────────────────────────
|
||||
# Cross-directory rename of a subdirectory with restricted permissions
|
||||
# causes cascading test failures within the test file.
|
||||
|
||||
@@ -27,6 +27,7 @@ type InodeEntry struct {
|
||||
lastRefresh time.Time
|
||||
updateWindowStart time.Time
|
||||
updateCount int
|
||||
subdirCount int32 // tracked in-memory for POSIX directory nlink
|
||||
}
|
||||
|
||||
func (ie *InodeEntry) resetCacheState() {
|
||||
@@ -250,6 +251,55 @@ func (i *InodeToPath) InvalidateChildrenCache(fullpath util.FullPath) {
|
||||
entry.resetCacheState()
|
||||
}
|
||||
|
||||
// AdjustSubdirCount adjusts the subdirectory count for a directory inode.
|
||||
// delta is typically +1 (mkdir) or -1 (rmdir).
|
||||
func (i *InodeToPath) AdjustSubdirCount(dirPath util.FullPath, delta int32) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
inode, found := i.path2inode[dirPath]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found || !entry.isDirectory {
|
||||
return
|
||||
}
|
||||
entry.subdirCount += delta
|
||||
if entry.subdirCount < 0 {
|
||||
entry.subdirCount = 0
|
||||
}
|
||||
}
|
||||
|
||||
// GetSubdirCount returns the tracked subdirectory count for a directory.
|
||||
func (i *InodeToPath) GetSubdirCount(dirPath util.FullPath) int32 {
|
||||
i.RLock()
|
||||
defer i.RUnlock()
|
||||
inode, found := i.path2inode[dirPath]
|
||||
if !found {
|
||||
return 0
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found || !entry.isDirectory {
|
||||
return 0
|
||||
}
|
||||
return entry.subdirCount
|
||||
}
|
||||
|
||||
// SetSubdirCount sets the subdirectory count for a directory (used after readdir).
|
||||
func (i *InodeToPath) SetSubdirCount(dirPath util.FullPath, count int32) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
inode, found := i.path2inode[dirPath]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
entry, found := i.inode2path[inode]
|
||||
if !found || !entry.isDirectory {
|
||||
return
|
||||
}
|
||||
entry.subdirCount = count
|
||||
}
|
||||
|
||||
func (i *InodeToPath) TouchDirectory(fullpath util.FullPath) {
|
||||
i.Lock()
|
||||
defer i.Unlock()
|
||||
|
||||
@@ -329,18 +329,11 @@ func (wfs *WFS) applyInMemoryAtime(out *fuse.Attr, inode uint64) {
|
||||
}
|
||||
|
||||
// applyDirNlink sets nlink = 2 + number_of_subdirectories for a directory.
|
||||
// Only counts from the local metacache to avoid expensive filer queries.
|
||||
// When the cache has no entries (e.g. before readdir), keeps nlink=2.
|
||||
// Uses the in-memory subdirectory count tracked by mkdir/rmdir/rename.
|
||||
func (wfs *WFS) applyDirNlink(out *fuse.Attr, dirPath util.FullPath) {
|
||||
var subdirCount uint32
|
||||
wfs.metaCache.ListDirectoryEntries(context.Background(), dirPath, "", false, 100000, func(entry *filer.Entry) (bool, error) {
|
||||
if entry.IsDirectory() {
|
||||
subdirCount++
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
if subdirCount > 0 {
|
||||
out.Nlink = 2 + subdirCount
|
||||
count := wfs.inodeToPath.GetSubdirCount(dirPath)
|
||||
if count > 0 {
|
||||
out.Nlink = 2 + uint32(count)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,7 @@ func (wfs *WFS) Mkdir(cancel <-chan struct{}, in *fuse.MkdirIn, name string, out
|
||||
}
|
||||
wfs.inodeToPath.TouchDirectory(dirFullPath)
|
||||
wfs.touchDirMtimeCtime(dirFullPath)
|
||||
wfs.inodeToPath.AdjustSubdirCount(dirFullPath, 1)
|
||||
}
|
||||
|
||||
glog.V(3).Infof("mkdir %s: %v", entryFullPath, err)
|
||||
@@ -155,6 +156,7 @@ func (wfs *WFS) Rmdir(cancel <-chan struct{}, header *fuse.InHeader, name string
|
||||
wfs.inodeToPath.RemovePath(entryFullPath)
|
||||
wfs.inodeToPath.TouchDirectory(dirFullPath)
|
||||
wfs.touchDirMtimeCtime(dirFullPath)
|
||||
wfs.inodeToPath.AdjustSubdirCount(dirFullPath, -1)
|
||||
|
||||
return fuse.OK
|
||||
|
||||
|
||||
@@ -326,6 +326,11 @@ func (wfs *WFS) Rename(cancel <-chan struct{}, in *fuse.RenameIn, oldName string
|
||||
wfs.touchDirMtimeCtime(oldDir)
|
||||
if oldDir != newDir {
|
||||
wfs.touchDirMtimeCtime(newDir)
|
||||
// Adjust subdirectory counts when moving a directory across parents.
|
||||
if oldEntry != nil && oldEntry.IsDirectory {
|
||||
wfs.inodeToPath.AdjustSubdirCount(oldDir, -1)
|
||||
wfs.inodeToPath.AdjustSubdirCount(newDir, 1)
|
||||
}
|
||||
}
|
||||
|
||||
return fuse.OK
|
||||
|
||||
Reference in New Issue
Block a user