Files
seaweedfs/weed/mount/winfsp/xattr_windows.go
T
Chris Lu a0e278f86f mount: forward extended attributes on windows (#10554)
weed/mount implements all four xattr operations and the filer stores the
values, but the Windows adapter overrode none of them, so cgofuse's
defaults answered every call with 'not implemented'. WinFsp advertises
extended attribute support either way, because cgofuse registers the
callbacks unconditionally, so applications were told the volume has them
and then refused on every use. Attributes written from Linux were
invisible from Windows.

Untested in CI: exercising Windows extended attributes needs the native
NtSetEaFile path rather than anything in os or PowerShell.
2026-08-03 21:55:59 -07:00

91 lines
2.3 KiB
Go

package winfsp
import (
"bytes"
"github.com/seaweedfs/go-fuse/v2/fuse"
)
// xattrBufferSize bounds one attribute value and one name listing. The mount
// stores extended attributes in the entry itself, so they are small by
// construction; this is the ceiling WinFsp will accept in a single reply.
const xattrBufferSize = 64 * 1024
func (w *WinFS) Getxattr(path string, name string) (int, []byte) {
inode, ref, status := w.resolve(path)
if status != fuse.OK {
return toErrno(status), nil
}
defer ref.release()
dest := make([]byte, xattrBufferSize)
size, status := w.wfs.GetXAttr(never, ptr(w.caller(inode)), name, dest)
if status != fuse.OK {
return toErrno(status), nil
}
return 0, dest[:size]
}
func (w *WinFS) Setxattr(path string, name string, value []byte, flags int) int {
if w.denied() {
return -eROFS
}
inode, ref, status := w.resolve(path)
if status != fuse.OK {
return toErrno(status)
}
defer ref.release()
// cgofuse and the raw filesystem number XATTR_CREATE and XATTR_REPLACE the
// same, so the flags pass straight through; TestXattrFlagValues pins that.
in := &fuse.SetXAttrIn{InHeader: w.caller(inode)}
in.Flags = uint32(flags)
in.Size = uint32(len(value))
return toErrno(w.wfs.SetXAttr(never, in, name, value))
}
func (w *WinFS) Removexattr(path string, name string) int {
if w.denied() {
return -eROFS
}
inode, ref, status := w.resolve(path)
if status != fuse.OK {
return toErrno(status)
}
defer ref.release()
return toErrno(w.wfs.RemoveXAttr(never, ptr(w.caller(inode)), name))
}
func (w *WinFS) Listxattr(path string, fill func(name string) bool) int {
inode, ref, status := w.resolve(path)
if status != fuse.OK {
return toErrno(status)
}
defer ref.release()
dest := make([]byte, xattrBufferSize)
size, status := w.wfs.ListXAttr(never, ptr(w.caller(inode)), dest)
if status != fuse.OK {
return toErrno(status)
}
for _, name := range splitXattrNames(dest[:size]) {
if !fill(name) {
break
}
}
return 0
}
// splitXattrNames unpacks the NUL-separated listing the raw filesystem
// produces. A trailing NUL terminates the last name rather than starting an
// empty one.
func splitXattrNames(packed []byte) []string {
var names []string
for _, name := range bytes.Split(packed, []byte{0}) {
if len(name) > 0 {
names = append(names, string(name))
}
}
return names
}