mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
fix(mount): add -posixDirNlink option for correct directory nlink
Add optional POSIX-compliant directory nlink counting (nlink = 2 + number_of_subdirectories) behind the -posixDirNlink flag. This requires listing cached directory entries on every stat, which has a performance cost, so it's off by default. When disabled (default), directories report nlink=2, which is the POSIX-compliant baseline for empty directories and a common default in distributed filesystems. - Set nlink baseline to 2 for all directories (was 1) - Add applyDirNlink() gated behind option.PosixDirNlink - Add -posixDirNlink CLI flag
This commit is contained in:
@@ -56,6 +56,7 @@ type MountOptions struct {
|
||||
distributedLock *bool
|
||||
|
||||
// FUSE performance options
|
||||
posixDirNlink *bool
|
||||
writebackCache *bool
|
||||
asyncDio *bool
|
||||
cacheSymlink *bool
|
||||
@@ -132,6 +133,7 @@ func init() {
|
||||
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)")
|
||||
|
||||
// FUSE performance options
|
||||
mountOptions.posixDirNlink = cmdMount.Flag.Bool("posix.dirNLink", false, "report POSIX-compliant directory nlink (2 + subdirectory count); costs one directory listing per stat")
|
||||
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")
|
||||
mountOptions.cacheSymlink = cmdMount.Flag.Bool("cacheSymlink", false, "enable symlink caching to reduce metadata lookups")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package mount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -16,15 +17,21 @@ 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)
|
||||
if wfs.option.PosixDirNlink {
|
||||
wfs.applyDirNlink(&out.Attr, util.FullPath(wfs.option.FilerMountRootPath))
|
||||
}
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
inode := input.NodeId
|
||||
_, _, entry, status := wfs.maybeReadEntry(inode)
|
||||
path, _, entry, status := wfs.maybeReadEntry(inode)
|
||||
if status == fuse.OK {
|
||||
out.AttrValid = 1
|
||||
wfs.setAttrByPbEntry(&out.Attr, inode, entry, true)
|
||||
wfs.applyInMemoryAtime(&out.Attr, inode)
|
||||
if entry.IsDirectory && wfs.option.PosixDirNlink {
|
||||
wfs.applyDirNlink(&out.Attr, path)
|
||||
}
|
||||
return status
|
||||
} else {
|
||||
if fh, found := wfs.fhMap.FindFileHandle(inode); found {
|
||||
@@ -176,7 +183,7 @@ func (wfs *WFS) setRootAttr(out *fuse.AttrOut) {
|
||||
out.Ctime = now
|
||||
out.Atime = now
|
||||
out.Mode = toSyscallType(os.ModeDir) | uint32(wfs.option.MountMode)
|
||||
out.Nlink = 1
|
||||
out.Nlink = 2
|
||||
}
|
||||
|
||||
func (wfs *WFS) setAttrByPbEntry(out *fuse.Attr, inode uint64, entry *filer_pb.Entry, calculateSize bool) {
|
||||
@@ -208,7 +215,9 @@ func (wfs *WFS) setAttrByPbEntry(out *fuse.Attr, inode uint64, entry *filer_pb.E
|
||||
out.Atimensec = uint32(entry.Attributes.MtimeNs)
|
||||
// In-memory atime overlay is applied by the caller via applyInMemoryAtime.
|
||||
out.Mode = toSyscallMode(os.FileMode(entry.Attributes.FileMode))
|
||||
if entry.HardLinkCounter > 0 {
|
||||
if entry.IsDirectory {
|
||||
out.Nlink = 2
|
||||
} else if entry.HardLinkCounter > 0 {
|
||||
out.Nlink = uint32(entry.HardLinkCounter)
|
||||
} else {
|
||||
out.Nlink = 1
|
||||
@@ -238,7 +247,9 @@ func (wfs *WFS) setAttrByFilerEntry(out *fuse.Attr, inode uint64, entry *filer.E
|
||||
out.Ctimensec = uint32(entry.Attr.Mtime.Nanosecond())
|
||||
}
|
||||
out.Mode = toSyscallMode(entry.Attr.Mode)
|
||||
if entry.HardLinkCounter > 0 {
|
||||
if entry.IsDirectory() {
|
||||
out.Nlink = 2
|
||||
} else if entry.HardLinkCounter > 0 {
|
||||
out.Nlink = uint32(entry.HardLinkCounter)
|
||||
} else {
|
||||
out.Nlink = 1
|
||||
@@ -306,6 +317,22 @@ func (wfs *WFS) applyInMemoryAtime(out *fuse.Attr, inode uint64) {
|
||||
wfs.atimeMu.Unlock()
|
||||
}
|
||||
|
||||
// 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.
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func chmod(existing uint32, mode uint32) uint32 {
|
||||
return existing&^07777 | mode&07777
|
||||
}
|
||||
|
||||
@@ -44,6 +44,10 @@ func (wfs *WFS) Lookup(cancel <-chan struct{}, header *fuse.InHeader, name strin
|
||||
|
||||
wfs.outputFilerEntry(out, inode, localEntry)
|
||||
|
||||
if localEntry.IsDirectory() && wfs.option.PosixDirNlink {
|
||||
wfs.applyDirNlink(&out.Attr, fullFilePath)
|
||||
}
|
||||
|
||||
return fuse.OK
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user