mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-15 02:50:45 +02:00
* mount: remember the entry of a directory removed while still referenced A directory removed while a descriptor is open on it keeps its inode until the kernel's final forget, but unlike a file it has no handle to live on through: OpenDir hands out only a listing cursor. Keep the last-known entry in memory, keyed by inode, from rmdir until that forget. Claude-Session: https://claude.ai/code/session_01GYqLENjZzbV5hgt4L8cSAK * mount: serve metadata ops on a removed open directory from its remembered entry fchmod, futimens, and the f*xattr calls on a descriptor whose directory was removed failed with ENOENT: maybeReadEntry resolved the inode to a path, and rmdir had already dropped it. Fall back to the remembered entry the same way an unlinked file falls back to its open handle. Mutations publish a changed copy back rather than editing in place, so a concurrent reader never sees a half-applied change, and the empty path keeps nlink 0 in every reply. Claude-Session: https://claude.ai/code/session_01GYqLENjZzbV5hgt4L8cSAK * mount: stash the entry the delete itself returned, not an earlier snapshot A chmod landing between Rmdir's entry load and the delete RPC would be resurrected pre-change: the remembered entry was the earlier local snapshot. The filer serializes the delete against updates under the path lock and hands the entry back in the delete event, so prefer that, keeping the local load for the sticky-bit check and as fallback when no event comes back. Claude-Session: https://claude.ai/code/session_01GYqLENjZzbV5hgt4L8cSAK * mount: drop a remembered entry whose insert lost to the final forget The forget's cleanup runs between RemovePath and the insert when the kernel evicts the inode concurrently, finds nothing, and the entry would sit in the map for the life of the mount. Re-check the inode after inserting and take the entry back out; every interleaving now ends with the map empty. Claude-Session: https://claude.ai/code/session_01GYqLENjZzbV5hgt4L8cSAK * mount: insert the remembered entry under the inode table lock The post-insert HasInode re-check could be fooled by inode number reuse: a lookup landing between the forget and the check makes the number look alive and the stale entry stays, keyed to someone else's inode. Do not check after the fact — RemovePath now runs the retention callback inside its critical section, where the forget that releases under the same lock cannot have run and cannot be missed. Publishes need no such fence: their open descriptor keeps the kernel from issuing the final forget in the first place. Claude-Session: https://claude.ai/code/session_01GYqLENjZzbV5hgt4L8cSAK
235 lines
5.3 KiB
Go
235 lines
5.3 KiB
Go
//go:build !freebsd
|
|
|
|
package mount
|
|
|
|
import (
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/seaweedfs/go-fuse/v2/fuse"
|
|
)
|
|
|
|
const (
|
|
// https://man7.org/linux/man-pages/man7/xattr.7.html#:~:text=The%20VFS%20imposes%20limitations%20that,in%20listxattr(2)).
|
|
MAX_XATTR_NAME_SIZE = 255
|
|
MAX_XATTR_VALUE_SIZE = 65536
|
|
XATTR_PREFIX = "xattr-" // same as filer
|
|
)
|
|
|
|
// GetXAttr reads an extended attribute, and should return the
|
|
// number of bytes. If the buffer is too small, return ERANGE,
|
|
// with the required buffer size.
|
|
func (wfs *WFS) GetXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string, dest []byte) (size uint32, code fuse.Status) {
|
|
|
|
if wfs.option.DisableXAttr {
|
|
return 0, fuse.Status(syscall.ENOTSUP)
|
|
}
|
|
|
|
//validate attr name
|
|
if len(attr) > MAX_XATTR_NAME_SIZE {
|
|
if runtime.GOOS == "darwin" {
|
|
return 0, fuse.EPERM
|
|
} else {
|
|
return 0, fuse.ERANGE
|
|
}
|
|
}
|
|
if len(attr) == 0 {
|
|
return 0, fuse.EINVAL
|
|
}
|
|
|
|
_, _, entry, status := wfs.maybeReadEntry(header.NodeId)
|
|
if status != fuse.OK {
|
|
return 0, status
|
|
}
|
|
if entry == nil {
|
|
return 0, fuse.ENOENT
|
|
}
|
|
if entry.Extended == nil {
|
|
return 0, fuse.ENOATTR
|
|
}
|
|
data, found := entry.Extended[XATTR_PREFIX+attr]
|
|
if !found {
|
|
return 0, fuse.ENOATTR
|
|
}
|
|
if len(dest) < len(data) {
|
|
return uint32(len(data)), fuse.ERANGE
|
|
}
|
|
copy(dest, data)
|
|
|
|
return uint32(len(data)), fuse.OK
|
|
}
|
|
|
|
// SetXAttr writes an extended attribute.
|
|
// https://man7.org/linux/man-pages/man2/setxattr.2.html
|
|
//
|
|
// By default (i.e., flags is zero), the extended attribute will be
|
|
// created if it does not exist, or the value will be replaced if
|
|
// the attribute already exists. To modify these semantics, one of
|
|
// the following values can be specified in flags:
|
|
//
|
|
// XATTR_CREATE
|
|
// Perform a pure create, which fails if the named attribute
|
|
// exists already.
|
|
//
|
|
// XATTR_REPLACE
|
|
// Perform a pure replace operation, which fails if the named
|
|
// attribute does not already exist.
|
|
func (wfs *WFS) SetXAttr(cancel <-chan struct{}, input *fuse.SetXAttrIn, attr string, data []byte) fuse.Status {
|
|
|
|
if wfs.option.DisableXAttr {
|
|
return fuse.Status(syscall.ENOTSUP)
|
|
}
|
|
|
|
if wfs.IsOverQuotaWithUncommitted() {
|
|
return fuse.Status(syscall.ENOSPC)
|
|
}
|
|
|
|
//validate attr name
|
|
if len(attr) > MAX_XATTR_NAME_SIZE {
|
|
if runtime.GOOS == "darwin" {
|
|
return fuse.EPERM
|
|
} else {
|
|
return fuse.ERANGE
|
|
}
|
|
}
|
|
if len(attr) == 0 {
|
|
return fuse.EINVAL
|
|
}
|
|
//validate attr value
|
|
if len(data) > MAX_XATTR_VALUE_SIZE {
|
|
if runtime.GOOS == "darwin" {
|
|
return fuse.Status(syscall.E2BIG)
|
|
} else {
|
|
return fuse.ERANGE
|
|
}
|
|
}
|
|
|
|
path, fh, entry, status := wfs.maybeReadEntry(input.NodeId)
|
|
if status != fuse.OK {
|
|
return status
|
|
}
|
|
if entry == nil {
|
|
return fuse.ENOENT
|
|
}
|
|
if fh != nil {
|
|
fh.entryLock.Lock()
|
|
defer fh.entryLock.Unlock()
|
|
}
|
|
|
|
if entry.Extended == nil {
|
|
entry.Extended = make(map[string][]byte)
|
|
}
|
|
_, exists := entry.Extended[XATTR_PREFIX+attr]
|
|
switch input.Flags {
|
|
case xattr_CREATE:
|
|
if exists {
|
|
return fuse.Status(syscall.EEXIST)
|
|
}
|
|
case xattr_REPLACE:
|
|
if !exists {
|
|
return fuse.ENODATA
|
|
}
|
|
}
|
|
|
|
// data aliases the FUSE request's pooled input buffer, which is
|
|
// recycled once this handler returns. Copy before storing so a
|
|
// later request reusing the buffer cannot corrupt the value.
|
|
entry.Extended[XATTR_PREFIX+attr] = append([]byte(nil), data...)
|
|
|
|
if fh != nil {
|
|
fh.dirtyMetadata = true
|
|
return fuse.OK
|
|
}
|
|
if path == "" {
|
|
// removed while open: the remembered entry is all there is to update
|
|
wfs.rememberRemovedDir(input.NodeId, entry)
|
|
return fuse.OK
|
|
}
|
|
|
|
return wfs.saveEntry(path, entry)
|
|
|
|
}
|
|
|
|
// ListXAttr lists extended attributes as '\0' delimited byte
|
|
// slice, and return the number of bytes. If the buffer is too
|
|
// small, return ERANGE, with the required buffer size.
|
|
func (wfs *WFS) ListXAttr(cancel <-chan struct{}, header *fuse.InHeader, dest []byte) (n uint32, code fuse.Status) {
|
|
|
|
if wfs.option.DisableXAttr {
|
|
return 0, fuse.Status(syscall.ENOTSUP)
|
|
}
|
|
|
|
_, _, entry, status := wfs.maybeReadEntry(header.NodeId)
|
|
if status != fuse.OK {
|
|
return 0, status
|
|
}
|
|
if entry == nil {
|
|
return 0, fuse.ENOENT
|
|
}
|
|
if entry.Extended == nil {
|
|
return 0, fuse.OK
|
|
}
|
|
|
|
var data []byte
|
|
for k := range entry.Extended {
|
|
if strings.HasPrefix(k, XATTR_PREFIX) {
|
|
data = append(data, k[len(XATTR_PREFIX):]...)
|
|
data = append(data, 0)
|
|
}
|
|
}
|
|
if len(dest) < len(data) {
|
|
return uint32(len(data)), fuse.ERANGE
|
|
}
|
|
|
|
copy(dest, data)
|
|
|
|
return uint32(len(data)), fuse.OK
|
|
}
|
|
|
|
// RemoveXAttr removes an extended attribute.
|
|
func (wfs *WFS) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, attr string) fuse.Status {
|
|
|
|
if wfs.option.DisableXAttr {
|
|
return fuse.Status(syscall.ENOTSUP)
|
|
}
|
|
|
|
if len(attr) == 0 {
|
|
return fuse.EINVAL
|
|
}
|
|
path, fh, entry, status := wfs.maybeReadEntry(header.NodeId)
|
|
if status != fuse.OK {
|
|
return status
|
|
}
|
|
if entry == nil {
|
|
return fuse.OK
|
|
}
|
|
if fh != nil {
|
|
fh.entryLock.Lock()
|
|
defer fh.entryLock.Unlock()
|
|
}
|
|
|
|
if entry.Extended == nil {
|
|
return fuse.ENOATTR
|
|
}
|
|
_, found := entry.Extended[XATTR_PREFIX+attr]
|
|
|
|
if !found {
|
|
return fuse.ENOATTR
|
|
}
|
|
|
|
delete(entry.Extended, XATTR_PREFIX+attr)
|
|
|
|
if fh != nil {
|
|
fh.dirtyMetadata = true
|
|
return fuse.OK
|
|
}
|
|
if path == "" {
|
|
// removed while open: the remembered entry is all there is to update
|
|
wfs.rememberRemovedDir(header.NodeId, entry)
|
|
return fuse.OK
|
|
}
|
|
|
|
return wfs.saveEntry(path, entry)
|
|
}
|