Files
seaweedfs/weed/mount/weedfs_xattr.go
Chris Lu 4f692bf9c3 mount: build the package on windows (#10535)
* mount: drop the unused go-fuse fs package dependency

WFS embedded fs.Inode but never used any of its methods, and the only
other reference was RENAME_EXCHANGE, a constant sitting next to three
literals. Removing both drops fs and five internal packages from the
mount build graph.

* mount: build the package on windows

Windows has no fcntl lock types, no O_ACCMODE and no x/sys/unix, so a
handful of constants kept weed/mount pinned to unix even though the code
using them is portable in-memory logic. Route them through per-OS shims
and give setBlksize a windows no-op.

The POSIX lock table now compiles on windows but stays unreachable:
WinFsp resolves byte-range locks in its own kernel driver, so nothing
will feed it there.

go.mod points at a go-fuse branch commit and needs repinning to a release
tag once that lands.

* ci: cross-compile for windows

Nothing caught the unix-only constants creeping into weed/mount until a
release build failed.

* mount: let readdir feed a sink instead of the kernel buffer

doReadDirectory wrote directly into fuse.DirEntryList, which is the
kernel's wire format. A front end that is not the kernel would have to
pack entries only to parse them straight back out.

Route it through DirEntrySink instead. ReadDir and ReadDirPlus pass the
reply buffer, so nothing changes for the FUSE server.

* mount: pin go-fuse v2.9.4 for the windows build
2026-08-03 01:07:49 -07:00

225 lines
5.0 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
}
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
}
return wfs.saveEntry(path, entry)
}