From 8aa58098249b8d5770af182988354c793ef31fe4 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 10 Apr 2026 16:18:29 -0700 Subject: [PATCH] fix(mount): gate directory nlink counting behind -posix.dirNLink option (#9026) The directory nlink counting (2 + subdirectory count) requires listing cached directory entries on every stat, which has a performance cost. Gate it behind the -posix.dirNLink flag (default: off). When disabled, directories report nlink=2 (POSIX baseline). When enabled, directories report nlink=2 + number of subdirectories from cached entries. --- weed/command/mount.go | 6 ++++++ weed/command/mount_std.go | 1 + weed/mount/weedfs.go | 6 ++++++ weed/mount/weedfs_attr.go | 6 ++++-- weed/mount/weedfs_dir_lookup.go | 2 +- 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/weed/command/mount.go b/weed/command/mount.go index c82fbd9ec9..b769179689 100644 --- a/weed/command/mount.go +++ b/weed/command/mount.go @@ -55,6 +55,9 @@ type MountOptions struct { // Distributed lock for cross-mount write coordination distributedLock *bool + // POSIX compliance options + posixDirNlink *bool + // FUSE performance options writebackCache *bool asyncDio *bool @@ -131,6 +134,9 @@ func init() { // Distributed lock for cross-mount write coordination mountOptions.distributedLock = cmdMount.Flag.Bool("dlm", false, "enable distributed lock for cross-mount write coordination (only one mount can write a file at a time)") + // POSIX compliance options + mountOptions.posixDirNlink = cmdMount.Flag.Bool("posix.dirNLink", false, "report POSIX-compliant directory nlink (2 + subdirectory count); costs one directory listing per stat") + // FUSE performance options mountOptions.writebackCache = cmdMount.Flag.Bool("writebackCache", false, "enable FUSE writeback cache for improved write performance (at risk of data loss on crash)") mountOptions.asyncDio = cmdMount.Flag.Bool("asyncDio", false, "enable async direct I/O for better concurrency") diff --git a/weed/command/mount_std.go b/weed/command/mount_std.go index 8228bbcd24..e0c0a6291d 100644 --- a/weed/command/mount_std.go +++ b/weed/command/mount_std.go @@ -354,6 +354,7 @@ func RunMount(option *MountOptions, umask os.FileMode) bool { DirIdleEvictSec: *option.dirIdleEvictSec, EnableDistributedLock: option.distributedLock != nil && *option.distributedLock, WritebackCache: option.writebackCache != nil && *option.writebackCache, + PosixDirNlink: option.posixDirNlink != nil && *option.posixDirNlink, }) // create mount root diff --git a/weed/mount/weedfs.go b/weed/mount/weedfs.go index ba6644f1f2..fbc91f1028 100644 --- a/weed/mount/weedfs.go +++ b/weed/mount/weedfs.go @@ -93,6 +93,12 @@ type Option struct { // When true, Flush() returns immediately and data upload + metadata flush happen in background. WritebackCache bool + // PosixDirNlink enables POSIX-compliant directory nlink counting + // (nlink = 2 + number_of_subdirectories). This requires listing + // cached directory entries on every stat, which has a performance cost. + // When false (default), directories report nlink=2. + PosixDirNlink bool + uniqueCacheDirForRead string uniqueCacheDirForWrite string } diff --git a/weed/mount/weedfs_attr.go b/weed/mount/weedfs_attr.go index cd4e77f6ca..95e39f836d 100644 --- a/weed/mount/weedfs_attr.go +++ b/weed/mount/weedfs_attr.go @@ -17,7 +17,9 @@ func (wfs *WFS) GetAttr(cancel <-chan struct{}, input *fuse.GetAttrIn, out *fuse glog.V(4).Infof("GetAttr %v", input.NodeId) if input.NodeId == 1 { wfs.setRootAttr(out) - wfs.applyDirNlink(&out.Attr, util.FullPath(wfs.option.FilerMountRootPath)) + if wfs.option.PosixDirNlink { + wfs.applyDirNlink(&out.Attr, util.FullPath(wfs.option.FilerMountRootPath)) + } return fuse.OK } @@ -27,7 +29,7 @@ func (wfs *WFS) GetAttr(cancel <-chan struct{}, input *fuse.GetAttrIn, out *fuse out.AttrValid = 1 wfs.setAttrByPbEntry(&out.Attr, inode, entry, true) wfs.applyInMemoryAtime(&out.Attr, inode) - if entry.IsDirectory { + if entry.IsDirectory && wfs.option.PosixDirNlink { wfs.applyDirNlink(&out.Attr, path) } return status diff --git a/weed/mount/weedfs_dir_lookup.go b/weed/mount/weedfs_dir_lookup.go index 43529176d6..2c29d907a3 100644 --- a/weed/mount/weedfs_dir_lookup.go +++ b/weed/mount/weedfs_dir_lookup.go @@ -44,7 +44,7 @@ func (wfs *WFS) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name strin wfs.outputFilerEntry(out, inode, localEntry) - if localEntry.IsDirectory() { + if localEntry.IsDirectory() && wfs.option.PosixDirNlink { wfs.applyDirNlink(&out.Attr, fullFilePath) }